Come posso pubblicare i dati come dati del modulo anziché come payload di richiesta?


523

Nel codice seguente, il $httpmetodo AngularJS chiama l'URL e invia l'oggetto xsrf come "Richiesta payload" (come descritto nella scheda Rete del debugger di Chrome). Il $.ajaxmetodo jQuery esegue la stessa chiamata, ma invia xsrf come "Dati modulo".

Come posso fare in modo che AngularJS invii xsrf come dati del modulo anziché come payload della richiesta?

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
Questa è stata una domanda molto utile. Mi consente di inviare un payload come stringa (modificando il tipo di contenuto), il che mi impedisce di dover gestire OPZIONI prima di POST / GET.
earthmeLon

Ho una stessa domanda, è dopo che ho richiesto l'URL, ma non riesco a ottenere il parametro che invio
黄伟杰

Risposte:


614

La seguente riga deve essere aggiunta all'oggetto $ http che viene passato:

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

E i dati passati devono essere convertiti in una stringa codificata URL:

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

Quindi hai qualcosa del tipo:

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

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

AGGIORNARE

Per utilizzare i nuovi servizi aggiunti con AngularJS V1.4, vedere


3
C'è un modo in cui la codifica json> url dei dati può avvenire automaticamente o per specificare che ciò accada per ogni metodo POST o PUT?
Dogoku,

51
+1 @mjibson, anche per me passare le intestazioni non funzionava, fino a quando non ho visto la tua risposta contenente questo: è var xsrf = $.param({fkey: "key"});stupido, perché l'angolare non può farlo internamente?
naikus,

12
Per seguire da vicino il comportamento predefinito di $ .ajax, è necessario specificare anche il set di caratteri nell'intestazione del tipo di contenuto -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre

25
Invece di usare la funzione param di jQuery, basta impostare la proprietà params sulla richiesta $ http e farà quello che fa il metodo jQuery.param fintanto che l'intestazione Content-Type è 'application / x-www-form-urlencoded' - stackoverflow .com / questions / 18967307 /…
spig

13
@spig Sì, farà ciò che fa jQuery.param, ma se usi la proprietà params le tue proprietà saranno codificate come parte dell'URL della richiesta anziché nel corpo - anche se hai specificato l'applicazione / x-www- intestazione codificata in forma.
stian,

194

Se non si desidera utilizzare jQuery nella soluzione, è possibile provare questo. La soluzione è arrivata da qui 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
Questo metodo funziona per me in 1.2.x angolare e penso che sia la risposta migliore perché è elegante, funziona in core angolare e non dipende da nessuna libreria esterna come jQuery.
gregtczap,

2
Ho riscontrato un problema durante l'utilizzo di questo metodo all'interno di un'azione $ resource. I dati del modulo includevano anche funzioni per $ get, $ save, ecc. La soluzione era invece di modificare forun po ' l' istruzione da usare angular.forEach.
Anthony,

10
Si noti che a differenza di $ .param () questo metodo non funziona in modo ricorsivo su matrici / oggetti.
MazeChaZer

1
Verificherei che obj[p]non sia nullo o indefinito . Altrimenti finirai per inviare una stringa "null" o "non definita" come valore.
Tamir,

1
Non ho capito transformRequest: function(obj)Dato che obj non è definito, supponiamo di passare xsrf? Mi piacetransformRequest: function(xsrf)
Akshay Taru,

92

La continua confusione che circonda questo problema mi ha ispirato a scrivere un post sul blog al riguardo. La soluzione che propongo in questo post è migliore della tua attuale soluzione più votata perché non ti limita a parametrizzare il tuo oggetto dati per chiamate di servizio $ http; vale a dire con la mia soluzione puoi semplicemente continuare a passare oggetti dati reali a $ http.post (), ecc. e ottenere comunque il risultato desiderato.

Inoltre, la risposta più votata si basa sull'inclusione di jQuery completo nella pagina per la funzione $ .param (), mentre la mia soluzione è agnostica jQuery, pronta AngularJS pura.

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

Spero che sia di aiuto.


10
+1 per il blog dettagliato, ma il fatto che ce ne sia bisogno è orribile ...
iwein

4
Sì, forse orribile su due livelli: 1) che AngularJS ha deciso di ribaltare uno standard de facto (anche se certamente errato) e 2) che PHP (e chissà qualunque altra lingua lato server) in qualche modo non rilevi automaticamente application / json ingresso. : P
Ezechiele Vittorio,

Sarebbe possibile che angularjs si adatti automaticamente al tipo di contenuto e codifichi di conseguenza? È previsto?
unludo,

4
Io (come molti altri) mi sono imbattuto in questo che il mio backend ASP.NETnon lo supportava "nativamente". Se non vuoi cambiare il comportamento di AngularJS (cosa che non ho fatto, perché la mia API restituisce JSON, perché non farlo accettare anche JSON, è più flessibile dei dati del modulo) puoi leggere da Request.InputStreame quindi gestirlo in qualsiasi modo lo vuoi. (Ho scelto di deserializzarlo dynamicper facilità d'uso.)
Aidiakapi

2
Sottovalutato perché questa non è una risposta autonoma . Le buone risposte non si limitano a collegare altrove. Da Come rispondere : "Cita sempre la parte più rilevante di un link importante, nel caso in cui il sito di destinazione non sia raggiungibile o rimanga permanentemente offline."
James,

83

Ho preso alcune delle altre risposte e fatto qualcosa di un po 'più pulito, ho messo questa .config()chiamata alla fine del tuo angular.module nel tuo 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("&");
  });
}]);

1
Funziona come un incantesimo, anche se aggiunto a una definizione di risorsa.
Kai Mattern,

3
Si è anche preso cura di usarlo in unshift()modo che le altre trasformazioni rimangano indisturbate. Buon lavoro.
MP Aditya,

2
Perfetto! ha funzionato bene per me! triste angolare non lo supporta nativamente.
spierala,

2
Questa risposta dovrebbe essere quella giusta in alto, le altre sono sbagliate, grazie amico !!
Jose Ignacio Hita,

2
Che ne dici della codifica ricorsiva?
Petah,

58

A partire da AngularJS v1.4.0, esiste un servizio integrato $httpParamSerializerche converte qualsiasi oggetto in una parte di una richiesta HTTP in base alle regole elencate nella pagina dei documenti .

Può essere usato in questo modo:

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

Ricorda che per un post di modulo corretto, l' Content-Typeintestazione deve essere cambiata. Per fare ciò a livello globale per tutte le richieste POST, questo codice (preso dalla mezza risposta di Albireo) può essere usato:

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

Per fare ciò solo per il post corrente, la headersproprietà dell'oggetto richiesta deve essere modificata:

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

$http(req);

Come possiamo fare lo stesso su una factory di risorse $ personalizzata?
Stilllife

Nota: aggiorno un'app da Angular 1.3 a 1.5. Ha cambiato le intestazioni in transformRequest. Per qualche ragione, il metodo sopra non funziona per me, Angular aggiunge doppie virgolette attorno alla stringa codificata URL. Risolto con transformRequest: $httpParamSerializer, data: formDataObj. Grazie per la soluzione
PhiLho,

24

È possibile definire il comportamento a livello globale:

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

Quindi non devi ridefinirlo ogni volta:

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

46
Il tuo esempio è così sbagliato ... Tutto ciò che stai modificando è l'intestazione. I dati stessi saranno comunque codificati JSON e illeggibili da server meno recenti che non sono in grado di leggere JSON.
alexk,

victorblog.com/2012/12/20/… - ecco un buon esempio in cui si sovrascrive l'intestazione predefinita $ http e si converte l'oggetto in dati di modulo serializzati.
Federico,

20

Per ovviare al problema, puoi semplicemente fare in modo che il codice che riceve il POST risponda ai dati dell'applicazione / json. Per PHP ho aggiunto il codice seguente, permettendomi di postarlo in formato codificato 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

questo è uno dei buoni esempi di correzione lato server, perché il vero problema su questo problema è nell'API lato server .. bravo
Vignesh

16

Queste risposte sembrano eccessive, ma a volte semplice è semplicemente meglio:

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

1
Per me, ho ancora dovuto specificare l'intestazione Content-Typee impostarlo su application/x-www-form-urlencoded.
Victor Ramos,

9

Puoi provare con la soluzione seguente

$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

Creare un servizio adattatore per posta:

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

}) 

Usalo nei tuoi controller o qualsiasi altra cosa:

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 solo in jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer è equivalente Angularjs.
Dexter,

7

C'è un tutorial davvero carino che analizza questa e altre cose correlate - Invio di moduli AJAX: The AngularJS Way .

Fondamentalmente, è necessario impostare l'intestazione della richiesta POST per indicare che si stanno inviando i dati del modulo come una stringa codificata URL e impostare i dati da inviare nello stesso 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)
});

Si noti che la funzione helper param () di jQuery viene utilizzata qui per serializzare i dati in una stringa, ma è possibile farlo anche manualmente se non si utilizza jQuery.


1
I moderatori hanno semplicemente cancellato la mia risposta precedente perché non avevo fornito dettagli sull'attuazione effettivamente menzionata nel link. Sarebbe stato meglio se mi avessero invece chiesto prima di fornire ulteriori dettagli, invece di eliminarlo, poiché stavo già modificando la mia risposta per fornire i dettagli come mostrato in questa risposta!
robinmitra,

Il $.paramfare la magia. soluzione perfetta per chi ha un'app basata su jQuery + AngularJS.
dashtinejad,


4

Per gli utenti di Symfony2:

Se non vuoi cambiare nulla nel tuo javascript affinché funzioni, puoi fare queste modifiche nella tua app symfony:

Crea una classe che estende la 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;

}

}

Ora usa la tua classe in app_dev.php (o qualsiasi file indice che usi)

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

questo è stato davvero utile per me, il nuovo createFromGlobals ora funziona perfettamente. Non so perché hai ottenuto un downvote, ma l'ho rimosso.
Richard,

3

Basta impostare Content-Type non è sufficiente, url codifica i dati del modulo prima di inviarli. $http.post(url, jQuery.param(data))


3

Attualmente sto usando la seguente soluzione che ho trovato nel gruppo Google AngularJS.

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

Nota che se stai usando PHP, dovrai usare qualcosa come il componente HTTP di Symfony 2 Request::createFromGlobals()per leggere questo, poiché $ _POST non verrà caricato automaticamente con esso.


2

AngularJS lo sta facendo bene come sta facendo il seguente tipo di contenuto all'interno dell'intestazione della richiesta http:

Content-Type: application/json

Se vai con php come me o anche con Symfony2 puoi semplicemente estendere la compatibilità del tuo server per lo standard json come descritto qui: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

Il modo Symfony2 (ad es. All'interno di 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());

Il vantaggio sarebbe che non è necessario utilizzare jQuery param e si può usare AngularJS nel suo modo nativo di fare tali richieste.


2

Risposta completa (dall'angolo 1.4). Devi includere la dipendenza $ 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

Nella configurazione dell'app:

$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 la tua richiesta di risorse -

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

0

Questa non è una risposta diretta, ma piuttosto una direzione del design leggermente diversa:

Non pubblicare i dati come modulo, ma come oggetto JSON da mappare direttamente sull'oggetto lato server o utilizzare la variabile percorso REST

Ora so che nessuna delle due opzioni potrebbe essere adatta nel tuo caso poiché stai cercando di passare una chiave XSRF. Mapparlo in una variabile di percorso come questa è un progetto terribile:

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

Perché per natura vorresti passare la chiave xsrf anche su un altro percorso /login, /book-appointmentecc. E non vuoi rovinare il tuo bel URL

È interessante notare che non è appropriato aggiungerlo come campo oggetto, perché ora su ciascuno degli oggetti json che passi al server devi aggiungere il campo

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

Certamente non vuoi aggiungere un altro campo alla tua classe lato server che non abbia un'associazione semantica diretta con l'oggetto dominio.

A mio avviso, il modo migliore per passare la chiave xsrf è tramite un'intestazione HTTP. Molte librerie web framework lato server di protezione xsrf lo supportano. Ad esempio in Java Spring, puoi passarlo usando l' X-CSRF-TOKENintestazione .

L'eccellente capacità di Angular di associare l'oggetto JS all'oggetto dell'interfaccia utente ci consente di sbarazzarci della pratica di pubblicare tutti i moduli insieme e di pubblicare invece JSON. JSON può essere facilmente serializzato in oggetti lato server e supportare strutture dati complesse come mappe, array, oggetti nidificati, ecc.

Come si inserisce un array in un payload del modulo? Forse così:

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

o questo:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Entrambi sono design scadente ..


0

Questo è quello che sto facendo per le mie necessità, dove devo inviare i dati di accesso all'API come dati del modulo e l'oggetto Javascript (userData) viene convertito automaticamente in dati codificati 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);
        });

Ecco come sono i miei dati utente

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

-1

L'unico thin che devi cambiare è usare la proprietà "params" anziché "data" quando crei il tuo oggetto $ http:

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

Nell'esempio sopra i client [i] è solo un oggetto JSON (non serializzato in alcun modo). Se usi "params" anziché "dati" angular, serializzerai l'oggetto per te usando $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer


2
Usando params invece di dati, Angular inserisce i dati nei parametri URL anziché nel corpo della richiesta. Questo non è quello che ci si aspetta da un post del modulo.
cmenning

-3

Utilizzare il $httpservizio AngularJS e utilizzare il postmetodo o la $httpfunzione di configurazione .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.