关于ajax:如何将数据作为表单数据而不是请求有效负载发布?

How can I post data as form data instead of a request payload?

在下面的代码中,AngularJS $http方法调用URL,并将xsrf对象作为"请求有效负载"提交(如Chrome调试器网络选项卡中所述)。 jQuery $.ajax方法执行相同的调用,但将xsrf提交为"Form Data"。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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() {}
});


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

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

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

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

所以你有类似的东西:

1
2
3
4
5
6
$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

UPDATE

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

  • 仅使用AngularJS服务的URL编码变量


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

1
2
3
4
5
6
7
8
9
10
11
12
$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 () {});


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

此外,最受欢迎的答案依赖于在$ .param()函数的页面中包含完整的jQuery,而我的解决方案是jQuery不可知,纯粹的AngularJS就绪。

Make AngularJS $http service behave like jQuery.ajax()

希望这可以帮助。


我拿了一些其他的答案并做了一些更清洁的东西,把这个.config()调用放在app.js中angular.module的末尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.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("&");
  });
}]);


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

它可以像这样使用:

1
2
3
$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的半答案):

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

要仅对当前帖子执行此操作,需要修改request-object的headers属性:

1
2
3
4
5
6
7
8
9
10
var req = {
 method: 'POST',
 url: 'http://example.com',
 headers: {
   'Content-Type': 'application/x-www-form-urlencoded'
 },
 data: $httpParamSerializer(formDataObj)
};

$http(req);


您可以全局定义行为:

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

所以你不必每次都重新定义它:

1
2
3
4
5
6
7
8
$http.post("/handle/post", {
    foo:"FOO",
    bar:"BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});


作为一种解决方法,您可以简单地使接收POST的代码响应application / json数据。对于PHP,我添加了下面的代码,允许我以form-encoded或JSON对它进行POST。

1
2
3
4
5
6
7
//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


这些答案看起来像疯狂的矫枉过正,有时,简单就是更好:

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


您可以尝试以下解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$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 */
        });


为帖子创建适配器服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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'}
        })
    }

})

在控制器或其他任何地方使用它:

1
2
3
4
5
6
7
8
9
10
11
12
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)
        })
    }

})


有一个非常好的教程可以解释这个和其他相关的东西 - 提交AJAX表单:AngularJS方式。

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

1
2
3
4
5
6
$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
2
3
4
5
6
7
8
9
10
var fd = new FormData();
    fd.append('file', file);
    $http.post(uploadUrl, fd, {
        transformRequest: angular.identity,
        headers: {'Content-Type': undefined}
    })
    .success(function(){
    })
    .error(function(){
    });

请结账!
https://uncorkedstudios.com/blog/multipartformdata-file-upload-with-angularjs


对于Symfony2用户:

如果您不想更改javascript中的任何内容以使其工作,您可以在symfony app中进行以下修改:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php

namespace Acme\Test\MyRequest;

use Symfony\Component\HttpFoundation
equest;
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(或您使用的任何索引文件)中使用您的类

1
2
3
4
5
6
7
8
9
10
11
// 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);


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

1
2
3
4
5
6
7
8
$http
.post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(data)), {
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }
}).success(function(data) {
    $scope.data = data;
});

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


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


AngularJS正在执行它,因为它在http-request标头内执行以下内容类型:

1
Content-Type: application/json

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

Symfony2方式(例如在DefaultController中):

1
2
3
4
5
6
$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以其本机方式执行此类请求。


完整答案(自角度1.4)。您需要包含de dependency $ httpParamSerializer

1
2
3
4
5
6
7
8
9
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
2
3
4
5
6
7
8
9
10
$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);
    };

根据您的资源请求 -

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        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);
        });

这是我的Userdata的方式

1
2
3
4
5
var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }


这不是一个直接的答案,而是一个略有不同的设计方向:

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

现在我知道在您尝试传递XSRF密钥时,这两种选项都不适用于您的情况。将它映射到这样的路径变量是一个可怕的设计:

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

因为本质上你也希望将xsrf键传递给其他路径,/login/book-appointment等等,你不想弄乱你漂亮的URL

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

1
2
3
4
5
{
  appointmentId : 23,
  name : 'Joe Citizen',
  xsrf : '...'
}

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

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

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

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

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

或这个:

1
shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

两者都是糟糕的设计..


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

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

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


使用AngularJS $http服务并使用其post方法或配置$http函数。