¿Cómo puedo publicar datos como datos de formulario en lugar de una carga de solicitud?

523

En el siguiente código, el $httpmétodo AngularJS llama a la URL y envía el objeto xsrf como "Solicitar carga útil" (como se describe en la pestaña de red del depurador de Chrome). El $.ajaxmétodo jQuery realiza la misma llamada, pero envía xsrf como "Datos de formulario".

¿Cómo puedo hacer que AngularJS envíe xsrf como datos de formulario en lugar de una carga útil de solicitud?

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() {}
});
mjibson
fuente
1
Esta fue una pregunta muy útil. Me permite enviar una carga útil como una cadena (cambiando el tipo de contenido), lo que me impide tener que lidiar con las OPCIONES antes de POST / GET.
earthmeLon
Tengo una misma pregunta, es después de solicitar la URL, pero no puedo obtener el parámetro que envío
黄伟杰

Respuestas:

614

La siguiente línea debe agregarse al objeto $ http que se pasa:

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

Y los datos pasados ​​deben convertirse a una cadena codificada en URL:

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

Entonces tienes algo como:

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

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

ACTUALIZAR

Para usar los nuevos servicios agregados con AngularJS V1.4, vea

mjibson
fuente
3
¿Hay alguna manera de que la codificación json> url de los datos ocurra automáticamente o para especificar que esto suceda para cada método POST o PUT?
Dogoku
51
+1 @mjibson, incluso para mí pasar los encabezados no funcionaba, hasta que vi tu respuesta que contenía esto: var xsrf = $.param({fkey: "key"});Eso es estúpido, ¿por qué no puede hacerlo angularmente internamente?
naikus
12
Para seguir de cerca el comportamiento predeterminado de $ .ajax, el juego de caracteres también debe especificarse en el encabezado del tipo de contenidoheaders: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre
25
En lugar de usar la función param de jQuery, simplemente configure la propiedad params en la solicitud $ http y hará lo que hace el método jQuery.param siempre que el encabezado Content-Type sea 'application / x-www-form-urlencoded' - stackoverflow .com / preguntas / 18967307 / ...
spig
13
@spig Sí, hará lo que hace jQuery.param, pero, si usa la propiedad params, sus propiedades se codificarán como parte de la URL de solicitud en lugar de en el cuerpo, incluso si ha especificado la aplicación / x-www- encabezado codificado por formulario.
stian
194

Si no desea utilizar jQuery en la solución, puede probar esto. Solución atrapada desde aquí 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 () {});
Antonio
fuente
77
Este método funciona para mí en angular 1.2.x, y creo que es la mejor respuesta porque es elegante, funciona en núcleo angular y no depende de ninguna biblioteca externa como jQuery.
gregtczap
2
Encontré un problema al usar este método dentro de una acción $ resource. Los datos del formulario también incluían funciones para $ get, $ save, etc. La solución fue alterar forun poco la declaración para usarla angular.forEach.
Anthony
10
Tenga en cuenta que, a diferencia de $ .param (), este método no funciona de forma recursiva en matrices / objetos.
MazeChaZer
1
Verificaría que obj[p]no sea nulo o indefinido . De lo contrario, terminará enviando una cadena "nula" o "indefinida" como valor.
tamir
1
No entendí transformRequest: function(obj)Como obj no está definido, ¿suponemos pasar el xsrf? Me gustatransformRequest: function(xsrf)
Akshay Taru
92

La continua confusión en torno a este tema me inspiró a escribir una publicación de blog al respecto. La solución que propongo en esta publicación es mejor que su solución actual mejor calificada porque no lo restringe a parametrizar su objeto de datos para llamadas de servicio $ http; es decir, con mi solución, simplemente puede continuar pasando objetos de datos reales a $ http.post (), etc. y aún así lograr el resultado deseado.

Además, la respuesta mejor calificada se basa en la inclusión de jQuery completo en la página para la función $ .param (), mientras que mi solución es jQuery agnóstico, listo para AngularJS puro.

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

Espero que esto ayude.

Ezequiel Victor
fuente
10
+1 para el blog detallado, pero el hecho de que sea necesario es horrible ...
iwein 05 de
44
Sí, tal vez horrible en dos niveles: 1) que AngularJS decidió cambiar un estándar de facto (aunque ciertamente equivocado), y 2) que PHP (y quién sabe cualquier otro lenguaje del lado del servidor) de alguna manera no detecta automáticamente la aplicación / json entrada. : P
Ezequiel Victor
¿Sería posible que angularjs se adapte automáticamente al tipo de contenido y codifique en consecuencia? ¿Está previsto?
deshacer el
44
Yo (como muchos otros) me encontré con esto que mi backend ASP.NETno soportaba 'nativamente' esto. Si no desea cambiar el comportamiento de AngularJS (lo cual no hice, porque mi API devuelve JSON, ¿por qué no hacer que acepte también JSON, es más flexible que los datos de formulario?) Puede leerlo Request.InputStreamy luego manejarlo de cualquier manera lo quieres. (Elegí deserializarlo dynamicpara facilitar su uso.)
Aidiakapi
2
Votados en contra porque esta no es una respuesta independiente . Las buenas respuestas no solo enlazan a otro lugar. De Cómo responder : "Siempre cite la parte más relevante de un enlace importante, en caso de que no se pueda acceder al sitio de destino o se desconecte permanentemente".
James
83

Tomé algunas de las otras respuestas e hice algo un poco más limpio, puse esta .config()llamada al final de su angular.module en su app.js:

.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("&");
  });
}]);
kzar
fuente
1
Funciona de maravilla, incluso si se agrega a una definición de recurso.
Kai Mattern
3
También se cuidó de usar unshift()para que las otras transformaciones permanezcan intactas. Buen trabajo.
Aditya MP
2
¡Perfecto! funcionó bien para mí! triste angular no está apoyando esto de forma nativa.
spierala 05 de
2
Esta respuesta debería ser la correcta en la parte superior, las otras están equivocadas, ¡gracias amigo!
Jose Ignacio Hita
2
¿Qué tal la codificación recursiva?
Petah
58

A partir de AngularJS v1.4.0, hay un servicio incorporado $httpParamSerializerque convierte cualquier objeto en una parte de una solicitud HTTP de acuerdo con las reglas que se enumeran en la página de documentos .

Se puede usar así:

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

Recuerde que para una publicación de formulario correcta, se Content-Typedebe cambiar el encabezado. Para hacer esto globalmente para todas las solicitudes POST, se puede usar este código (tomado de la media respuesta de Albireo):

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

Para hacer esto solo para la publicación actual, la headerspropiedad del objeto de solicitud debe modificarse:

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

$http(req);
Mitja
fuente
¿Cómo podemos hacer lo mismo en una fábrica personalizada de $ resource?
stilllife
Nota: Actualizo una aplicación de Angular 1.3 a 1.5. Cambió los encabezados en transformRequest. Por alguna razón, el método anterior no funciona para mí, Angular agrega comillas dobles alrededor de la cadena codificada en URL. Resuelto con transformRequest: $httpParamSerializer, data: formDataObj. Gracias por la solucion.
PhiLho
24

Puede definir el comportamiento globalmente:

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

Para que no tenga que redefinirlo cada vez:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
fuente
46
Su ejemplo es tan incorrecto ... Todo lo que está modificando es el encabezado. Los datos mismos seguirán codificados en JSON y no podrán leerlos los servidores más antiguos que no puedan leer JSON.
alexk
victorblog.com/2012/12/20/… : este es un buen ejemplo en el que anula el encabezado predeterminado $ http y convierte el objeto en datos de formulario serializados.
Federico
20

Como solución alternativa, simplemente puede hacer que el código que recibe la POST responda a los datos de la aplicación / json. Para PHP, agregué el siguiente código, lo que me permite enviarlo en formato codificado o 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
James Bell
fuente
este es uno de los buenos ejemplos de corrección del lado del servidor, porque el verdadero problema de este problema está en la API del lado del servidor ... bravo
Vignesh
16

Estas respuestas parecen una exageración loca, a veces, simple es simplemente mejor:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
fuente
1
Para mí, todavía tenía que especificar el encabezado Content-Typey configurarlo application/x-www-form-urlencoded.
Victor Ramos
9

Puedes probar con la siguiente solución

$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 */
        });
tmquang6805
fuente
8

Cree un servicio de adaptador para publicación:

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'}
        })
    }

}) 

Úselo en sus controladores o lo que sea:

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)
        })
    }

})
Ozgur
fuente
$ .param solo en jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer es equivalente a Angularjs.
Dexter
7

Hay un tutorial realmente bueno que repasa esto y otras cosas relacionadas: envío de formularios AJAX: la forma AngularJS .

Básicamente, debe establecer el encabezado de la solicitud POST para indicar que está enviando datos de formulario como una cadena codificada de URL, y configurar los datos para que se envíen con el mismo formato

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

Tenga en cuenta que la función auxiliar param () de jQuery se usa aquí para serializar los datos en una cadena, pero también puede hacerlo manualmente si no usa jQuery.

robinmitra
fuente
1
Los moderadores simplemente eliminaron mi respuesta anterior porque no proporcioné detalles de la implementación real mencionada en el enlace. ¡Hubiera sido mejor si me hubieran pedido primero que proporcionara más detalles, en lugar de eliminarlos, ya que ya estaba editando mi respuesta para proporcionar los detalles como se ve en esta respuesta!
robinmitra
El $.paramhacen la magia. solución perfecta para quién tiene la aplicación basada en jQuery + AngularJS.
dashtinejad
4

Para usuarios de Symfony2:

Si no desea cambiar nada en su javascript para que esto funcione, puede hacer estas modificaciones en su aplicación Symfony:

Cree una clase que extienda la clase 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;

}

}

Ahora use su clase en app_dev.php (o cualquier archivo de índice que use)

// 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);
carmelo
fuente
Esto fue realmente útil para mí, el nuevo createFromGlobals funciona ahora perfectamente. No sé por qué recibiste un voto negativo, pero lo eliminé.
Richard
3

Simplemente configure Content-Type no es suficiente, codifique los datos del formulario antes de enviarlos. $http.post(url, jQuery.param(data))

Merlin Ran
fuente
3

Actualmente estoy usando la siguiente solución que encontré en el grupo de Google AngularJS.

$ http
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (datos)), {
    encabezados: {
        'Content-Type': 'application / x-www-form-urlencoded; juego de caracteres = UTF-8 '
    }
}). success (function (data) {
    $ scope.data = data;
});

Tenga en cuenta que si está usando PHP, necesitará usar algo como los componentes HTTP de Symfony 2 Request::createFromGlobals()para leer esto, ya que $ _POST no se cargará automáticamente con él.

Aditya MP
fuente
2

AngularJS lo está haciendo bien al hacer el siguiente tipo de contenido dentro del encabezado de solicitud http:

Content-Type: application/json

Si va con php como yo, o incluso con Symfony2, simplemente puede extender la compatibilidad de su servidor para el estándar json como se describe aquí: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

La forma Symfony2 (por ejemplo, dentro de su 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());

La ventaja sería que no necesita usar jQuery param y podría usar AngularJS, su forma nativa de hacer tales solicitudes.

Miguel
fuente
2

Respuesta completa (desde angular 1.4). Debe incluir la dependencia $ 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) { 

            });
Sebastián Rojas
fuente
1

En la configuración de tu aplicación:

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

Con su solicitud de recursos:

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

Esta no es una respuesta directa, sino una dirección de diseño ligeramente diferente:

No publique los datos como un formulario, sino como un objeto JSON que se asignará directamente al objeto del lado del servidor, o use la variable de ruta de estilo REST

Ahora sé que ninguna de las opciones podría ser adecuada en su caso, ya que está tratando de pasar una clave XSRF. Mapearlo en una variable de ruta como esta es un diseño terrible:

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

Porque, por naturaleza, también querría pasar la clave xsrf a otra ruta /login,/book-appointment etc. y no querrás desordenar tu bonita URL

Curiosamente, agregarlo como un campo de objeto tampoco es apropiado, porque ahora en cada objeto json que pasa al servidor, debe agregar el campo

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

Ciertamente no desea agregar otro campo en su clase del lado del servidor que no tenga una asociación semántica directa con el objeto de dominio.

En mi opinión, la mejor manera de pasar su clave xsrf es a través de un encabezado HTTP. Muchas bibliotecas de marco web del lado del servidor de protección xsrf lo admiten. Por ejemplo, en Java Spring, puede pasarlo usando el X-CSRF-TOKENencabezado .

La excelente capacidad de Angular de vincular objetos JS a objetos UI significa que podemos deshacernos de la práctica de publicar formularios todos juntos y publicar JSON en su lugar. JSON se puede deserializar fácilmente en objetos del lado del servidor y admitir estructuras de datos complejas como mapas, matrices, objetos anidados, etc.

¿Cómo publicar matriz en una carga útil de formulario? Tal vez así:

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

o esto:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Ambos tienen un diseño pobre.

gerrytan
fuente
0

Esto es lo que estoy haciendo para mi necesidad, donde necesito enviar los datos de inicio de sesión a la API como datos de formulario y el objeto Javascript (userData) se convierte automáticamente en datos codificados en 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);
        });

Así es como son mis datos de usuario

var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }
Shubham
fuente
-1

Lo único que debe cambiar es usar "parámetros" de propiedad en lugar de "datos" cuando cree su objeto $ http:

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

En el ejemplo anterior, los clientes [i] son ​​solo objetos JSON (no serializados de ninguna manera). Si usa "params" en lugar de "data" angular, serializará el objeto por usted usando $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Rafal Zajac
fuente
2
Al usar parámetros en lugar de datos, Angular coloca los datos en los parámetros de URL en lugar del cuerpo de la solicitud. Esto no es lo que se espera de una publicación de formulario.
partir del
-3

Use el $httpservicio AngularJS y use su postmétodo o $httpfunción de configuración .

Shivang Gupta
fuente