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

Posted

技术标签:

【中文标题】如何将数据作为表单数据而不是请求有效负载发布?【英文标题】:How can I post data as form data instead of a request payload? 【发布时间】:2012-07-11 15:50:31 【问题描述】:

在下面的代码中,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() 
);

【问题讨论】:

这是一个非常有用的问题。它允许我将有效负载作为字符串发送(通过更改 Content-Type),这使我不必在 POST/GET 之前处理 OPTIONS。 我也有同样的问题,是在我请求url之后,但我无法获取我提交的参数 【参考方案1】:

下面这行需要添加到传递的$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 添加的新服务,请参阅

URL-encoding variables using only AngularJS services

【讨论】:

有没有办法让数据的 json > url 编码自动发生或为每个 POST 或 PUT 方法指定发生这种情况? +1 @mjibson,对我来说,即使传递标题也行不通,直到我看到您的答案包含以下内容:var xsrf = $.param(fkey: "key"); 太愚蠢了,为什么 Angular 不能在内部进行? 为了更接近 $.ajax 默认行为,还应在内容类型标头中指定字符集 - headers: Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' 不使用 jQuery 的 param 函数,只需在 $http 请求上设置 params 属性,只要 Content-Type 标头为 'application/x- www-form-urlencoded' - ***.com/questions/18967307/… @spig 是的,它会做 jQuery.param 所做的事情,但是,如果您使用 params 属性,您的属性将被编码为请求 URL 的一部分,而不是在正文中 - 即使您有指定 application/x-www-form-urlencoded 标头。【参考方案2】:

您可以全局定义行为:

$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
);

【讨论】:

您的示例非常错误...您要修改的只是标题。数据本身仍然是 JSON 编码的,并且无法被无法读取 JSON 的旧服务器读取。 victorblog.com/2012/12/20/… -- 这是一个很好的示例,您可以覆盖 $http 默认标头,并将对象转换为序列化的表单数据。【参考方案3】:

如果你不想在解决方案中使用 jQuery,你可以试试这个。解决方案从这里https://***.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 () );

【讨论】:

这个方法在 angular 1.2.x 中适用于我,我认为这是最好的答案,因为它很优雅,它适用于核心 angular 并且不依赖于任何外部库,如 jQuery。跨度> 我在 $resource 操作中使用此方法时遇到了一个问题。表单数据还包括 $get、$save 等函数。解决方案是稍微更改 for 语句以使用 angular.forEach 请注意,与 $.param() 相比,此方法不适用于数组/对象递归。 我会检查 obj[p] 不是 nullundefined。否则,您最终将发送“null”或“undefined”字符串作为值。 我不明白transformRequest: function(obj) 因为obj 是未定义的,我们假设要通过xsrf 吗?喜欢transformRequest: function(xsrf)【参考方案4】:

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 参数,您可以使用 AngularJS 其原生方式来执行此类请求。

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

您可以尝试以下解决方案

$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 */
        );

【讨论】:

【参考方案7】:

我目前在 AngularJS google 组中使用以下解决方案 I found。

$http .post('/echo/json/', 'json=' + encodeURIComponent(angular.toJson(data)), 标题: '内容类型': 'application/x-www-form-urlencoded;字符集=UTF-8' ).成功(函数(数据) $scope.data = 数据; );

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

【讨论】:

【参考方案8】:

我拿了一些其他的答案,做了一些更干净的东西,把这个.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("&");
  );
]);

【讨论】:

像魅力一样工作 - 即使附加到资源定义。 还注意使用unshift(),因此其他转换不会受到干扰。干得好。 完美!对我来说很好!悲伤的角度本身并不支持这一点。 这个答案应该是最上面的那个,其他都是错的,谢谢大佬!! 递归编码怎么样?【参考方案9】:

对于 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 现在运行良好。我不知道你为什么投了反对票,但我删除了它。【参考方案10】:

作为一种解决方法,您可以简单地使接收 POST 的代码响应 application/json 数据。对于 PHP,我添加了下面的代码,允许我以表单编码或 JSON 的形式发布到它。

//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.. bravo【参考方案11】:

有一个非常好的教程,涵盖了这个和其他相关的东西 - Submitting AJAX Forms: The AngularJS Way。

基本上,你需要设置POST请求的header来表明你是用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,您也可以手动执行此操作。

【讨论】:

版主只是删除了我之前的答案,因为我没有提供链接中提到的实际实现的详细信息。如果他们先要求我提供更多详细信息而不是删除它会更好,因为我已经在编辑我的答案以提供此答案中看到的详细信息! $.param 施展魔法。拥有基于 jQuery+AngularJS 应用程序的完美解决方案。【参考方案12】:

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

【讨论】:

【参考方案13】:

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

不要将数据作为表单发布,而是作为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 框架库都支持这一点。 For example in Java Spring, you can pass it using X-CSRF-TOKEN header.

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

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

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

或者这个:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

两者都是糟糕的设计..

【讨论】:

【参考方案14】:

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

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

【讨论】:

对我来说,我仍然需要指定标题Content-Type 并将其设置为application/x-www-form-urlencoded【参考方案15】:
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

【讨论】:

它是最新的。【参考方案16】:

从 AngularJS v1.4.0 开始,有一个内置的$httpParamSerializer 服务,可以根据docs page 上列出的规则将任何对象转换为 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 工厂上做同样的事情? 注意:我将应用从 Angular 1.3 升级到 1.5。它更改了 transformRequest 中的标头。出于某种原因,上面的方法对我不起作用,Angular 在 URL 编码的字符串周围添加了双引号。已解决transformRequest: $httpParamSerializer, data: formDataObj。感谢您的解决方案。【参考方案17】:

在您的应用配置中 -

$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'
            

【讨论】:

【参考方案18】:

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

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

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

【讨论】:

通过使用参数而不是数据,Angular 将数据放在 URL 参数而不是请求正文中。这不是表单帖子所期望的。【参考方案19】:

完整答案(自角度 1.4 起)。您需要包含 de 依赖 $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)  

            );

【讨论】:

【参考方案20】:

为 post 创建一个适配器服务:

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。【参考方案21】:

这是我正在做的,我需要将登录数据作为表单数据发送到 API,并且 Javascript 对象(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
            

【讨论】:

以上是关于如何将数据作为表单数据而不是请求有效负载发布?的主要内容,如果未能解决你的问题,请参考以下文章

PDF 表单提交 - 请求有效负载 PHP 解析器

带有有效负载或表单数据的 DELETE 请求导致错误请求

在 Chrome 开发工具网络选项卡中看到的“请求有效负载”与“表单数据”有啥区别

在行动中反应 Dispatcher.dispatch,而不是将有效负载分派到存储

使用发布请求获取身份验证令牌

JMeter 重用以前的线程有效负载而不是新的线程有效负载