如何将数据发布为表单数据而不是请求有效载荷?


522

在下面的代码中,AngularJS $http方法调用URL,然后将xsrf对象作为“请求有效负载”提交(如Chrome调试器网络标签中所述)。jQuery $.ajax方法执行相同的调用,但是将xsrf提交为“表单数据”。

如何使AngularJS将xsrf提交为表单数据而不是请求有效载荷?

var url = 'http://somewhere.com/';
var xsrf = {fkey: 'xsrf key'};

$http({
    method: 'POST',
    url: url,
    data: xsrf
}).success(function () {});

$.ajax({
    type: 'POST',
    url: url,
    data: xsrf,
    dataType: 'json',
    success: function() {}
});

1
这是一个非常有用的问题。它允许我将有效载荷作为字符串发送(通过更改Content-Type),这使我不必在POST / GET之前处理OPTIONS。
EarthmeLon 2014年

我有一个同样的问题,它是在我请求URL之后,但是我无法获取我提交的参数
黄伟杰

Answers:


614

需要将以下行添加到传递的$ http对象中:

headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}

并且传递的数据应转换为URL编码的字符串:

> $.param({fkey: "key"})
'fkey=key'

所以你有这样的事情:

$http({
    method: 'POST',
    url: url,
    data: $.param({fkey: "key"}),
    headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
})

发件人:https : //groups.google.com/forum/#!msg/angular/5nAedJ1LyO0/4Vj_72EZcDsJ

更新

要使用AngularJS V1.4中添加的新服务,请参阅


3
有没有一种方法可以自动发生数据的json> url编码,或为每种POST或PUT方法指定发生这种情况?
Dogoku 2012年

51
+1 @mjibson,对我来说,即使通过标头也无法正常工作,直到我看到您的答案包含以下内容: var xsrf = $.param({fkey: "key"});那真是愚蠢,为什么不能在内部进行倾斜处理?
naikus

12
为了更严格地遵循$ .ajax的默认行为,还应在内容类型标头中指定字符集headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre

25
而不是使用jQuery的param函数,只需在$ http请求上设置params属性,只要Content-Type标头为'application / x-www-form-urlencoded',它将执行jQuery.param方法的操作-代码日志.com / questions / 18967307 /…
spig 2013年

13
@spig是的,它将执行jQuery.param的操作,但是,如果您使用params属性,则您的属性将被编码为请求URL的一部分,而不是在正文中-即使您已指定application / x-www-表单编码的标头。
stian 2014年

194

如果您不想在解决方案中使用jQuery,可以尝试一下。从这里获得的解决方案https://stackoverflow.com/a/1714899/1784301

$http({
    method: 'POST',
    url: url,
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    transformRequest: function(obj) {
        var str = [];
        for(var p in obj)
        str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
        return str.join("&");
    },
    data: xsrf
}).success(function () {});

7
这个方法对我来说适用于angular 1.2.x,我认为这是最好的答案,因为它很优雅,它可以在核心angular中使用,并且不依赖于jQuery之类的任何外部库。
gregtczap 2013年

2
在$ resource动作中使用此方法时,遇到一个问题。表单数据还包括$ get,$ save等函数。解决方案是for稍微修改一下语句以angular.forEach代替使用。
安东尼

10
请注意,与$ .param()相比,此方法不适用于数组/对象。
MazeChaZer 2014年

1
我会检查它obj[p]是否不是nullundefined。否则,您最终将发送“ null”或“ undefined”字符串作为值。
塔米尔

1
我不明白,transformRequest: function(obj)因为obj是未定义的,我们是否应该传递xsrf?喜欢transformRequest: function(xsrf)
Akshay Taru

92

围绕这个问题的持续困惑促使我写了一篇有关它的博客文章。我在这篇文章中提出的解决方案比您目前评价最高的解决方案要好,因为它不会限制您为$ http服务调用参数化数据对象。即,使用我的解决方案,您可以继续将实际数据对象继续传递到$ http.post()等,仍然可以实现所需的结果。

另外,评价最高的答案取决于在页面中为$ .param()函数包含完整的jQuery,而我的解决方案是与jQuery无关的纯AngularJS。

http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/

希望这可以帮助。


10
+1为详细的博客提供服务,但事实证明这很可怕……
iwein

4
是的,可能在两个层面上令人恐惧:1)AngularJS决定颠覆事实上的(尽管公认是被误导的)标准,以及2)PHP(以及其他任何知道服务器端语言的人)以某种方式无法自动检测到application / json输入。:P
Ezekiel Victor

angularjs是否有可能自动适应内容类型并进行相应编码?可以预见吗?
unludo

4
我(和其他许多人一样)遇到了这个问题,我的后端ASP.NET并不“本机”支持这一点。如果您不想更改AngularJS的行为(我没有这样做,因为我的API返回JSON,为什么不让它也接受JSON,它比表单数据更灵活),则可以从中读取内容Request.InputStream,然后以任何方式进行处理你想要它。(我选择将其反序列化以dynamic使其易于使用。)
Aidiakapi 2013年

2
不赞成投票,因为这不是一个独立的答案。好的答案不只是链接到其他地方。来自“ 如何回答”:“万一引用目标链接中最相关的部分,以防目标站点无法访问或永久脱机。”
詹姆斯

83

我采取了其他一些答案,并做了一些更.config()简洁的处理,将此调用放在app.js中angular.module的末尾:

.config(['$httpProvider', function ($httpProvider) {
  // Intercept POST requests, convert to standard form encoding
  $httpProvider.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
  $httpProvider.defaults.transformRequest.unshift(function (data, headersGetter) {
    var key, result = [];

    if (typeof data === "string")
      return data;

    for (key in data) {
      if (data.hasOwnProperty(key))
        result.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
    }
    return result.join("&");
  });
}]);

1
就像魅力一样工作-即使附加到资源定义中也是如此。
Kai Mattern 2014年

3
还请小心使用,unshift()以使其他转换不受干扰。辛苦了
Aditya MP

2
完善!对我来说很好!伤心的角度并不能自然地支持这一点。
spierala 2015年

2
这个答案应该在顶部是正确的,其他答案是错误的,谢谢队友!
Jose Ignacio Hita 2015年

2
递归编码怎么样?
Petah 2015年

58

从AngularJS v1.4.0开始,有一个内置$httpParamSerializer服务,可根据docs页面上列出的规则将任何对象转换为HTTP请求的一部分。

可以这样使用:

$http.post('http://example.com', $httpParamSerializer(formDataObj)).
    success(function(data){/* response status 200-299 */}).
    error(function(data){/* response status 400-999 */});

请记住,对于正确的表单发布,Content-Type必须更改标题。要针对所有POST请求全局执行此操作,可以使用以下代码(摘自Albireo的半答案):

$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

要仅对当前帖子执行此操作,headers需要修改请求对象的属性:

var req = {
 method: 'POST',
 url: 'http://example.com',
 headers: {
   'Content-Type': 'application/x-www-form-urlencoded'
 },
 data: $httpParamSerializer(formDataObj)
};

$http(req);

我们如何在自定义$ resource工厂上执行相同操作?
stilllife 2015年

注意:我将应用程序从Angular 1.3升级到1.5。它更改了transformRequest中的标头。由于某种原因,上面的方法对我不起作用,Angular在URL编码的字符串周围添加了双引号。解决了transformRequest: $httpParamSerializer, data: formDataObj。感谢您的解决方案。
PhiLho

24

您可以全局定义行为:

$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

因此,您不必每次都重新定义它:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});

46
您的示例太错误了...您要修改的只是标题。数据本身仍将进行JSON编码,并且无法读取JSON的旧服务器无法读取。
alexk 2013年

victorblog.com/2012/12/20/…-这是一个很好的示例,其中您覆盖$ http默认标头,并将该对象转换为序列化的表单数据。
Federico 2014年

20

作为一种解决方法,您可以简单地使接收POST的代码对application / json数据做出响应。对于PHP,我添加了以下代码,允许我以格式编码或JSON的形式对其进行POST。

//handles JSON posted arguments and stuffs them into $_POST
//angular's $http makes JSON posts (not normal "form encoded")
$content_type_args = explode(';', $_SERVER['CONTENT_TYPE']); //parse content_type string
if ($content_type_args[0] == 'application/json')
  $_POST = json_decode(file_get_contents('php://input'),true);

//now continue to reference $_POST vars as usual

这是服务器端修复的一个很好的例子,因为此问题的真正问题在于服务器端API。
Vignesh 2015年

16

这些答案看起来像是疯狂的过度杀伤力,有时候,简单就更好了:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...

1
对于我来说,我仍然必须指定标题Content-Type并将其设置为application/x-www-form-urlencoded
维克多·拉莫斯

9

您可以尝试以下解决方案

$http({
        method: 'POST',
        url: url-post,
        data: data-post-object-json,
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        transformRequest: function(obj) {
            var str = [];
            for (var key in obj) {
                if (obj[key] instanceof Array) {
                    for(var idx in obj[key]){
                        var subObj = obj[key][idx];
                        for(var subKey in subObj){
                            str.push(encodeURIComponent(key) + "[" + idx + "][" + encodeURIComponent(subKey) + "]=" + encodeURIComponent(subObj[subKey]));
                        }
                    }
                }
                else {
                    str.push(encodeURIComponent(key) + "=" + encodeURIComponent(obj[key]));
                }
            }
            return str.join("&");
        }
    }).success(function(response) {
          /* Do something */
        });

8

创建用于发布的适配器服务:

services.service('Http', function ($http) {

    var self = this

    this.post = function (url, data) {
        return $http({
            method: 'POST',
            url: url,
            data: $.param(data),
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        })
    }

}) 

在您的控制器中使用它或其他:

ctrls.controller('PersonCtrl', function (Http /* our service */) {
    var self = this
    self.user = {name: "Ozgur", eMail: null}

    self.register = function () {
        Http.post('/user/register', self.user).then(function (r) {
            //response
            console.log(r)
        })
    }

})

$ .param仅在jquery abi中。jsfiddle.net/4n9fao9q/27 $ httpParamSerializer与Angularjs等效。
德克斯特(Dexter)

7

有一个非常不错的教程,介绍了此以及其他相关内容- 提交AJAX表单:AngularJS方式

基本上,您需要设置POST请求的标头,以表明您要以URL编码的字符串形式发送表单数据,并设置要以相同格式发送的数据

$http({
  method  : 'POST',
  url     : 'url',
  data    : $.param(xsrf),  // pass in data as strings
  headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
});

请注意,此处使用了jQuery的param()帮助器函数将数据序列化为字符串,但是如果不使用jQuery,也可以手动执行此操作。


1
主持人只是删除了我以前的答案,因为我没有提供链接中提到的实际实现的详细信息。如果他们先要求我提供更多详细信息,而不是删除它,那就更好了,因为我已经在编辑我的答案以提供此答案中所示的详细信息!
robinmitra 2014年

$.param做的魔力。谁拥有基于jQuery + AngularJS的应用程序的完美解决方案。
dashtinejad


4

对于Symfony2用户:

如果您不想更改您的javascript中的任何功能以使其正常工作,则可以在symfony应用中进行以下修改:

创建一个扩展Symfony \ Component \ HttpFoundation \ Request类的类:

<?php

namespace Acme\Test\MyRequest;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;

class MyRequest extends Request{


/**
* Override and extend the createFromGlobals function.
* 
* 
*
* @return Request A new request
*
* @api
*/
public static function createFromGlobals()
{
  // Get what we would get from the parent
  $request = parent::createFromGlobals();

  // Add the handling for 'application/json' content type.
  if(0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/json')){

    // The json is in the content
    $cont = $request->getContent();

    $json = json_decode($cont);

    // ParameterBag must be an Array.
    if(is_object($json)) {
      $json = (array) $json;
  }
  $request->request = new ParameterBag($json);

}

return $request;

}

}

现在在app_dev.php(或您使用的任何索引文件)中使用您的类

// web/app_dev.php

$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$request = ForumBundleRequest::createFromGlobals();

// use your class instead
// $request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

这对我真的很有用,新的createFromGlobals现在可以正常工作。我不知道你为什么要投票,但我删除了。
理查德

3

仅仅设置Content-Type是不够的,发送前对表单数据进行url编码。 $http.post(url, jQuery.param(data))


3

我目前正在使用以下在AngularJS google组中找到的解决方案。

$ http
.post('/ echo / json /','json ='+ encodeURIComponent(angular.toJson(data)),{
    标头:{
        'Content-Type':'application / x-www-form-urlencoded; charset = UTF-8'
    }
})。success(函数(数据){
    $ scope.data =数据;
});

请注意,如果您使用的是PHP,则需要使用Symfony 2 HTTP组件之类的内容Request::createFromGlobals()来读取它,因为$ _POST不会自动加载该组件。


2

AngularJS正确地执行了此操作,因为它在http-request标头中执行了以下内容类型:

Content-Type: application/json

如果您要使用像我这样的php,甚至使用Symfony2,您都可以像下面描述的那样简单地扩展json标准的服务器兼容性:http : //silex.sensiolabs.org/doc/cookbook/json_request_body.html

Symfony2的方式(例如在DefaultController内部):

$request = $this->getRequest();
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
    $data = json_decode($request->getContent(), true);
    $request->request->replace(is_array($data) ? $data : array());
}
var_dump($request->request->all());

这样做的好处是,您不需要使用jQuery param,而可以使用AngularJS的本机方式来执行此类请求。


2

完整答案(角度为1.4)。您需要包含依赖项$ httpParamSerializer

var res = $resource(serverUrl + 'Token', { }, {
                save: { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
            });

            res.save({ }, $httpParamSerializer({ param1: 'sdsd', param2: 'sdsd' }), function (response) {

            }, function (error) { 

            });

1

在您的应用程序配置中-

$httpProvider.defaults.transformRequest = function (data) {
        if (data === undefined)
            return data;
        var clonedData = $.extend(true, {}, data);
        for (var property in clonedData)
            if (property.substr(0, 1) == '$')
                delete clonedData[property];

        return $.param(clonedData);
    };

根据您的资源请求-

 headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }

0

这不是直接的答案,而是稍微不同的设计方向:

不要将数据发布为表单,而是发布为JSON对象以直接映射到服务器端对象,或者使用REST样式路径变量

现在,我知道这两种方法都不适合您的情况,因为您正尝试传递XSRF密钥。像这样将其映射到路径变量是一个糟糕的设计:

http://www.someexample.com/xsrf/{xsrfKey}

因为从本质上讲,您也希望将xsrf密钥传递给其他路径/login/book-appointment等等,而您又不想弄乱您的漂亮URL

有趣的是,将其添加为对象字段也不适当,因为现在在传递给服务器的每个json对象上,您都必须添加字段

{
  appointmentId : 23,
  name : 'Joe Citizen',
  xsrf : '...'
}

您当然不想在服务器端类上添加另一个与域对象没有直接语义关联的字段。

我认为,传递xsrf密钥的最佳方法是通过HTTP标头。许多xsrf保护服务器端Web框架库都支持此功能。例如,在Java Spring中,可以使用X-CSRF-TOKENheader 传递它

Angular出色的将JS对象绑定到UI对象的能力意味着我们可以摆脱将表单全部发布在一起,而改为发布JSON的做法。JSON可以很容易地反序列化为服务器端对象,并支持复杂的数据结构,例如地图,数组,嵌套对象等。

您如何在表单有效载荷中发布数组?也许是这样的:

shopLocation=downtown&daysOpen=Monday&daysOpen=Tuesday&daysOpen=Wednesday

或这个:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

两者都是糟糕的设计。


0

这就是我需要做的,我需要将登录数据作为表单数据发送到API,并且Javascript Object(userData)自动转换为URL编码数据

        var deferred = $q.defer();
        $http({
            method: 'POST',
            url: apiserver + '/authenticate',
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            transformRequest: function (obj) {
                var str = [];
                for (var p in obj)
                    str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
                return str.join("&");
            },
            data: userData
        }).success(function (response) {
            //logics
            deferred.resolve(response);
        }).error(function (err, status) {
           deferred.reject(err);
        });

这是我的用户数据

var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }

-1

创建$ http对象时,唯一需要更改的是使用属性“ params”而不是“ data”:

$http({
   method: 'POST',
   url: serviceUrl + '/ClientUpdate',
   params: { LangUserId: userId, clientJSON: clients[i] },
})

在上面的示例中,clients [i]只是JSON对象(不以任何方式序列化)。如果您使用“参数”而不是“数据”,那么angular将使用$ httpParamSerializer为您序列化对象:https : //docs.angularjs.org/api/ng/service/ $ httpParamSerializer


2
通过使用参数而不是数据,Angular将数据放置在URL参数而不是请求正文中。这不是表单发布所期望的。
cmenning

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.