Como posso postar dados como dados do formulário em vez de uma carga útil de solicitação?

523

No código abaixo, o $httpmétodo AngularJS chama o URL e envia o objeto xsrf como uma "Carga útil da solicitação" (conforme descrito na guia Rede do depurador do Chrome). O $.ajaxmétodo jQuery faz a mesma chamada, mas envia xsrf como "Form Data".

Como posso fazer o AngularJS enviar xsrf como dados do formulário em vez de uma carga útil de solicitação?

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
fonte
1
Esta foi uma pergunta muito útil. Ele permite que eu envie uma carga útil como uma string (alterando o Tipo de conteúdo), o que me impede de lidar com OPTIONS antes do POST / GET.
usar o seguinte código
Eu tenho a mesma pergunta, é depois que eu solicito o URL, mas não consigo obter o parâmetro que eu envio # #
31116

Respostas:

614

A seguinte linha precisa ser adicionada ao objeto $ http que é passado:

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

E os dados transmitidos devem ser convertidos em uma string codificada em URL:

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

Então você tem 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

ATUALIZAR

Para usar novos serviços adicionados ao AngularJS V1.4, consulte

mjibson
fonte
3
Existe uma maneira de a codificação json> url dos dados ocorrer automaticamente ou especificar isso para todos os métodos POST ou PUT?
31412 Dogoku
51
+1 @mjibson, para mim, mesmo passando os cabeçalhos não estava funcionando, até que vi sua resposta contendo o seguinte: var xsrf = $.param({fkey: "key"});Isso é estúpido, por que o angular não pode fazer isso internamente?
Naikus
12
Para seguir mais perto $ .ajax comportamento padrão, charset também deve ser especificado no cabeçalho do tipo de conteúdo -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre
25
Em vez de usar a função param do jQuery, basta definir a propriedade params na solicitação $ http e ele fará o que o método jQuery.param faz, desde que o cabeçalho Content-Type seja 'application / x-www-form-urlencoded' - stackoverflow .com / questions / 18967307 /…
spig
13
@spig Sim, ele fará o que o jQuery.param faz, mas, se você usar a propriedade params, suas propriedades serão codificadas como parte da URL da solicitação e não no corpo - mesmo se você tiver especificado o aplicativo / x-www- cabeçalho com código de formulário.
stian
194

Se você não quiser usar o jQuery na solução, tente isso. Solução aqui retirada 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 () {});
Anthony
fonte
7
Esse método funciona para mim no angular 1.2.x, e acho que é a melhor resposta porque é elegante, funciona no angular central e não depende de nenhuma biblioteca externa como o jQuery.
precisa saber é o seguinte
2
Me deparei com um problema ao usar esse método dentro de uma ação $ resource. Os dados do formulário também incluíam funções para $ get, $ save, etc. A solução foi alterar forum pouco a instrução a ser usada angular.forEach.
Anthony
10
Observe que, ao contrário de $ .param (), esse método não funciona recursivamente em matrizes / objetos.
MazeChaZer
1
Eu verificaria que obj[p]não é nulo ou indefinido . Caso contrário, você acabará enviando uma string "nula" ou "indefinida" como o valor.
tamir
1
Eu não entendi transformRequest: function(obj)Como obj é indefinido, devemos passar o xsrf? Gosto #transformRequest: function(xsrf)
Akshay Taru
92

A confusão contínua em torno desse problema me inspirou a escrever um post sobre o assunto. A solução que proponho neste post é melhor que a sua melhor solução atual, porque não restringe você a parametrizar seu objeto de dados para chamadas de serviço $ http; ou seja, com a minha solução, você pode simplesmente continuar transmitindo objetos de dados reais para $ http.post () etc., e ainda alcançar o resultado desejado.

Além disso, a resposta mais bem avaliada depende da inclusão de jQuery completo na página para a função $ .param (), enquanto minha solução é independente de jQuery, pronta para AngularJS pura.

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

Espero que isto ajude.

Ezekiel Victor
fonte
10
+1 para o blog detalhado, mas o fato de que há uma necessidade para isso é horrível ...
Iwein
4
Sim, talvez seja horrível em dois níveis: 1) que o AngularJS decidiu alterar um padrão de fato (embora admitido incorretamente) e 2) que o PHP (e quem sabe quaisquer outras linguagens do servidor) de alguma forma não detecte automaticamente application / json entrada. : P
Ezekiel Victor
Seria possível que o angularjs se adapte automaticamente ao tipo de conteúdo e codifique de acordo? Está previsto?
unludo
4
Eu (como muitos outros) me deparei com isso que meu back-end ASP.NETnão apoiava 'nativamente' isso. Se você não quiser alterar o comportamento do AngularJS (o que eu não fiz, porque minha API retorna JSON, por que não aceitar o JSON também, é mais flexível do que os dados do formulário), você pode ler a partir Request.InputStreamdela e manipulá-la de qualquer maneira você quer isso. (Eu escolhi para desserializar-lo para dynamicpara facilidade de uso.)
Aidiakapi
2
Voto negativo porque esta não é uma resposta independente . Boas respostas não se vinculam apenas a outro lugar. De Como responder : "Sempre cite a parte mais relevante de um link importante, caso o site de destino esteja inacessível ou fique permanentemente offline."
James
83

Peguei algumas das outras respostas e fiz algo um pouco mais limpo. Coloque essa .config()chamada no final do seu angular.module no seu 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
fonte
1
Funciona como um encanto - mesmo se anexado a uma definição de recurso.
Kai Mattern
3
Também teve o cuidado de usar unshift()para que as outras transformações permaneçam imperturbáveis. Bom trabalho.
Aditya MP
2
perfeito! funcionou bem para mim! triste angular não está apoiando nativamente isso.
Spierala
2
Esta resposta deve ser a correta no topo, os outros estão errados, obrigado companheiro !!
Jose Ignacio Hita
2
E a codificação recursiva?
Petah
58

A partir do AngularJS v1.4.0, há um $httpParamSerializerserviço interno que converte qualquer objeto em uma parte de uma solicitação HTTP de acordo com as regras listadas na página de documentos .

Pode ser usado assim:

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

Lembre-se de que, para uma postagem de formulário correta, o Content-Typecabeçalho deve ser alterado. Para fazer isso globalmente em todas as solicitações POST, esse código (extraído da meia resposta de Albireo) pode ser usado:

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

Para fazer isso apenas na postagem atual, a headerspropriedade do objeto de solicitação precisa ser modificada:

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

$http(req);
Mitja
fonte
Como podemos fazer o mesmo em uma fábrica de recursos $ personalizada?
stilllife
Nota: atualizo um aplicativo do Angular 1.3 para 1.5. Ele alterou os cabeçalhos no transformRequest. Por alguma razão, o método acima não funciona para mim, o Angular adiciona aspas duplas ao redor da string codificada em URL. Resolvido com transformRequest: $httpParamSerializer, data: formDataObj. Obrigado pela solução.
PhiLho 24/06
24

Você pode definir o comportamento globalmente:

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

Portanto, você não precisa redefini-lo sempre:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
fonte
46
Seu exemplo está tão errado ... Tudo o que você está modificando é o cabeçalho. Os dados em si ainda serão codificados em JSON e ilegíveis por servidores mais antigos que não podem ler JSON.
Alexk
victorblog.com/2012/12/20/… - aqui está um bom exemplo em que você substitui o cabeçalho padrão $ http e converte o objeto em dados de formulário serializados.
Federico
20

Como solução alternativa, você pode simplesmente fazer o código que recebe o POST responder aos dados do aplicativo / json. Para o PHP, eu adicionei o código abaixo, permitindo-me postar nele em formato codificado ou 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
fonte
este é um dos bom exemplo de correção do lado do servidor, porque o verdadeiro problema sobre esta questão é sobre a API do lado do servidor .. bravo
Vignesh
16

Essas respostas parecem um exagero insano; às vezes, simples é apenas melhor:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
fonte
1
Para mim, eu ainda tinha que especificar o cabeçalho Content-Typee configurá-lo como application/x-www-form-urlencoded.
Victor Ramos
9

Você pode tentar com a solução abaixo

$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
fonte
8

Crie um serviço de adaptador para postagem:

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

}) 

Use-o em seus controladores ou o que for:

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
fonte
$ .param apenas em jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer é equivalente ao Angularjs.
Dexter
7

Há um tutorial muito bom que aborda esse e outros assuntos relacionados - Enviando formulários AJAX: a maneira do AngularJS .

Basicamente, você precisa definir o cabeçalho da solicitação POST para indicar que está enviando dados do formulário como uma string codificada em URL e definir os dados para que sejam enviados no mesmo 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)
});

Observe que a função auxiliar param () do jQuery é usada aqui para serializar os dados em uma string, mas você pode fazer isso manualmente também se não estiver usando o jQuery.

robinmitra
fonte
1
Os moderadores simplesmente excluíram minha resposta anterior porque não forneci detalhes da implementação realmente mencionada no link. Teria sido melhor se eles tivessem me pedido primeiro para fornecer mais detalhes, em vez de excluí-lo, pois eu já estava editando minha resposta para fornecer os detalhes, como visto nesta resposta!
Robinmitra
O $.paramfazer a mágica. solução perfeita para quem possui o aplicativo baseado em jQuery + AngularJS.
Dashtinejad 02/07
4

Para usuários do Symfony2:

Se você não deseja alterar nada no seu javascript para que isso funcione, você pode fazer estas modificações no aplicativo symfony:

Crie uma classe que estenda a classe 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;

}

}

Agora use sua classe em app_dev.php (ou qualquer arquivo de índice que você 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);
carmel
fonte
isso foi realmente útil para mim, o novo createFromGlobals está funcionando agora perfeitamente. Não sei por que você recebeu um voto negativo, mas eu o removi.
Richard
3

Basta definir o tipo de conteúdo não é suficiente, codificar os dados do formulário antes de enviar. $http.post(url, jQuery.param(data))

Merlin Ran
fonte
3

Atualmente, estou usando a seguinte solução que encontrei no grupo do Google AngularJS.

$ http
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (dados)), {
    cabeçalhos: {
        'Content-Type': 'application / x-www-form-urlencoded; charset = UTF-8 '
    }
}). success (function (data) {
    $ scope.data = dados;
});

Observe que se você estiver usando PHP, precisará usar algo como o componente HTTP do Symfony 2 Request::createFromGlobals()para ler isso, pois o $ _POST não será carregado automaticamente com ele.

Aditya MP
fonte
2

O AngularJS está fazendo isso da mesma maneira que faz o seguinte tipo de conteúdo dentro do cabeçalho da solicitação http:

Content-Type: application/json

Se você estiver usando php como eu, ou mesmo com o Symfony2, pode simplesmente estender a compatibilidade do servidor para o padrão json como descrito aqui: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

A maneira Symfony2 (por exemplo, dentro do seu Controlador Padrão):

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

A vantagem seria que você não precisa usar o jQuery param e pode usar o AngularJS como sua maneira nativa de fazer essas solicitações.

Michael
fonte
2

Resposta completa (desde o angular 1.4). Você precisa incluir a dependência $ 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
fonte
1

Na configuração do seu aplicativo -

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

Com sua solicitação de recurso -

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

Esta não é uma resposta direta, mas uma direção de design um pouco diferente:

Não publique os dados como um formulário, mas como um objeto JSON para ser mapeado diretamente para o objeto do lado do servidor ou use a variável de caminho de estilo REST

Agora eu sei que nenhuma opção pode ser adequada no seu caso, pois você está tentando passar uma chave XSRF. Mapear isso em uma variável de caminho como esta é um design terrível:

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

Porque, por natureza, você gostaria de passar a chave xsrf para outro caminho também /login, /book-appointmentetc. , e não quer mexer na sua URL bonita

Curiosamente, adicioná-lo como um campo de objeto também não é apropriado, porque agora em cada objeto json que você passa para o servidor, é necessário adicionar o campo

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

Você certamente não deseja adicionar outro campo à sua classe do servidor que não tenha uma associação semântica direta com o objeto de domínio.

Na minha opinião, a melhor maneira de passar sua chave xsrf é através de um cabeçalho HTTP. Muitas bibliotecas de estruturas da web do lado do servidor do xsrf protection suportam isso. Por exemplo, no Java Spring, você pode transmiti-lo usando o X-CSRF-TOKENcabeçalho .

A excelente capacidade do Angular de vincular o objeto JS ao objeto da interface do usuário significa que podemos nos livrar da prática de postar todos os formulários juntos e, em vez disso, publicar o JSON. O JSON pode ser facilmente desserializado no objeto do lado do servidor e suportar estruturas de dados complexas, como mapa, matrizes, objetos aninhados, etc.

Como você publica uma matriz em uma carga útil de formulário? Talvez assim:

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

ou isto:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Ambos têm um design ruim.

gerrytan
fonte
0

É o que estou fazendo para a minha necessidade. Onde preciso enviar os dados de login para a API como dados do formulário e o objeto Javascript (userData) está sendo convertido automaticamente em dados codificados em 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);
        });

É assim que meus dados de usuário são

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

O único problema que você precisa alterar é usar a propriedade "params" em vez de "data" quando você cria seu objeto $ http:

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

No exemplo acima, clients [i] é apenas um objeto JSON (não serializado de forma alguma). Se você usar "params" em vez de "angular de dados", serializará o objeto para você usando $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Rafal Zajac
fonte
2
Usando parâmetros em vez de dados, o Angular coloca os dados nos parâmetros da URL em vez do corpo da solicitação. Não é o que se espera de uma postagem de formulário.
22715 cmenning
-3

Use o $httpserviço AngularJS e use seu postmétodo ou configure a $httpfunção.

Shivang Gupta
fonte