Comment puis-je publier des données en tant que données de formulaire au lieu d'une charge utile de demande?

523

Dans le code ci-dessous, la $httpméthode AngularJS appelle l'URL et soumet l'objet xsrf en tant que "Request Payload" (comme décrit dans l'onglet réseau du débogueur Chrome). La $.ajaxméthode jQuery effectue le même appel, mais soumet xsrf en tant que "Données de formulaire".

Comment puis-je demander à AngularJS de soumettre xsrf en tant que données de formulaire au lieu d'une charge utile de demande?

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
la source
1
C'était une question très utile. Cela me permet d'envoyer une charge utile sous forme de chaîne (en changeant le type de contenu), ce qui m'empêche d'avoir à gérer les OPTIONS avant POST / GET.
earthmeLon
J'ai une même question, c'est après que j'ai demandé l'url, mais je ne peux pas obtenir le paramètre que je soumets
黄伟杰

Réponses:

614

La ligne suivante doit être ajoutée à l'objet $ http transmis:

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

Et les données transmises doivent être converties en une chaîne codée URL:

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

Vous avez donc quelque chose comme:

$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

MISE À JOUR

Pour utiliser les nouveaux services ajoutés avec AngularJS V1.4, consultez

mjibson
la source
3
Existe-t-il un moyen pour l'encodage json> url des données de se produire automatiquement ou de spécifier que cela se produise pour chaque méthode POST ou PUT?
Dogoku
51
+1 @mjibson, Pour moi, même passer les en-têtes ne fonctionnait pas, jusqu'à ce que je voie votre réponse contenant ceci: var xsrf = $.param({fkey: "key"});C'est stupide, pourquoi angulaire ne peut-il pas le faire en interne?
naikus
12
Pour suivre de plus près le comportement par défaut de $ .ajax, charset doit également être spécifié dans l'en-tête du type de contenu -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre
25
Au lieu d'utiliser la fonction param de jQuery, définissez simplement la propriété params sur la demande $ http et elle fera ce que fait la méthode jQuery.param tant que l'en-tête Content-Type est 'application / x-www-form-urlencoded' - stackoverflow .com / questions / 18967307 /…
spig
13
@spig Oui, il fera ce que jQuery.param fait, mais, si vous utilisez la propriété params, vos propriétés seront encodées dans le cadre de l'URL de la demande plutôt que dans le corps - même si vous avez spécifié l'application / x-www- en-tête form-urlencoded.
stian
194

Si vous ne souhaitez pas utiliser jQuery dans la solution, vous pouvez essayer ceci. Solution récupérée d'ici 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
la source
7
Cette méthode fonctionne pour moi dans angular 1.2.x, et je pense que c'est la meilleure réponse car elle est élégante, elle fonctionne en core angular et ne dépend d'aucune bibliothèque externe comme jQuery.
gregtczap
2
J'ai rencontré un problème lors de l'utilisation de cette méthode dans une action $ resource. Les données du formulaire incluaient également des fonctions pour $ get, $ save, etc. La solution était de modifier forun peu l' instruction à utiliser à la angular.forEachplace.
Anthony
10
Notez que contrairement à $ .param (), cette méthode ne fonctionne pas récursivement sur les tableaux / objets.
MazeChaZer
1
Je vérifierais que ce obj[p]n'est pas nul ou indéfini . Sinon, vous finirez par envoyer une chaîne "nulle" ou "non définie" comme valeur.
tamir
1
Je n'ai pas compris transformRequest: function(obj)Comme obj n'est pas défini, supposons-nous passer le xsrf? CommetransformRequest: function(xsrf)
Akshay Taru
92

La confusion continue entourant ce problème m'a inspiré à écrire un blog à ce sujet. La solution que je propose dans ce post est meilleure que votre solution actuelle la mieux notée car elle ne vous limite pas à paramétrer votre objet de données pour les appels de service $ http; c'est-à-dire qu'avec ma solution, vous pouvez simplement continuer à transmettre des objets de données réels à $ http.post (), etc. et toujours obtenir le résultat souhaité.

En outre, la réponse la mieux notée repose sur l'inclusion de jQuery complet dans la page pour la fonction $ .param (), alors que ma solution est agnostique jQuery, prête pour AngularJS pure.

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

J'espère que cela t'aides.

Ezekiel Victor
la source
10
+1 pour le blog détaillé, mais le fait qu'il soit nécessaire de le faire est horrible ...
iwein
4
Oui, peut-être horrible à deux niveaux: 1) qu'AngularJS a décidé de bouleverser une norme de facto (bien que certes erronée), et 2) que PHP (et qui connaît les autres langages côté serveur) ne détecte pas automatiquement application / json contribution. : P
Ezekiel Victor
Serait-il possible que angularjs s'adapte automatiquement au type de contenu et encode en conséquence? Est-ce prévu?
unludo
4
Je (comme beaucoup d'autres) suis tombé sur ce que mon backend ASP.NETne supportait pas «nativement». Si vous ne voulez pas changer le comportement d'AngularJS (ce que je n'ai pas fait, parce que mon API retourne JSON, pourquoi ne pas l'accepter aussi, c'est plus flexible que les données de formulaire), vous pouvez lire dans le Request.InputStreampuis le gérer de toute façon tu le veux. (J'ai choisi de le désérialiser dynamicpour plus de facilité d'utilisation.)
Aidiakapi
2
Voté parce que ce n'est pas une réponse autonome . Les bonnes réponses ne font pas que créer un lien ailleurs. From How to Answer : "Citez toujours la partie la plus pertinente d'un lien important, au cas où le site cible serait inaccessible ou se déconnecterait définitivement."
James
83

J'ai pris quelques autres réponses et fait quelque chose d'un peu plus propre, mettez cet .config()appel à la fin de votre module angulaire dans votre 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
la source
1
Fonctionne comme un charme - même s'il est ajouté à une définition de ressource.
Kai Mattern,
3
A également pris soin d'utiliser unshift()afin que les autres transformations ne soient pas perturbées. Bon travail.
Aditya MP
2
parfait! a bien fonctionné pour moi! triste angulaire ne soutient pas nativement cela.
spierala
2
Cette réponse devrait être la bonne en haut, les autres ont tort, merci mec !!
Jose Ignacio Hita
2
Et l'encodage récursif?
Petah
58

Depuis AngularJS v1.4.0, il existe un service intégré $httpParamSerializerqui convertit tout objet en une partie d'une demande HTTP selon les règles répertoriées sur la page des documents .

Il peut être utilisé comme ceci:

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

N'oubliez pas que pour un message de formulaire correct, l'en- Content-Typetête doit être modifié. Pour le faire globalement pour toutes les requêtes POST, ce code (extrait de la demi-réponse d'Albireo) peut être utilisé:

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

Pour ce faire uniquement pour le message actuel, la headerspropriété de l'objet de demande doit être modifiée:

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

$http(req);
Mitja
la source
Comment pouvons-nous faire de même sur une fabrique de ressources $ personnalisée?
nature morte
Remarque: je mets à niveau une application d'Angular 1.3 à 1.5. Il a changé les en-têtes dans transformRequest. Pour une raison quelconque, la méthode ci-dessus ne fonctionne pas pour moi, Angular ajoute des guillemets autour de la chaîne encodée URL. Résolu avec transformRequest: $httpParamSerializer, data: formDataObj. Merci pour la solution.
PhiLho
24

Vous pouvez définir le comportement globalement:

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

Vous n'avez donc pas à le redéfinir à chaque fois:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
la source
46
Votre exemple est tellement faux ... Tout ce que vous modifiez est l'en-tête. Les données elles-mêmes seront toujours codées en JSON et illisibles par les serveurs plus anciens qui ne peuvent pas lire le JSON.
alexk
victorblog.com/2012/12/20/… - voici un bon exemple où vous remplacez l'en-tête par défaut $ http, ainsi que convertissez l'objet en données de formulaire sérialisées.
Federico
20

Comme solution de contournement, vous pouvez simplement faire en sorte que le code recevant le POST réponde aux données d'application / json. Pour PHP, j'ai ajouté le code ci-dessous, ce qui me permet de le poster en format codé 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
la source
c'est l'un des bons exemples de correctifs côté serveur, car le vrai problème sur ce problème est sur l'API côté serveur. bravo
Vignesh
16

Ces réponses ressemblent à une folie excessive, parfois, simple est juste mieux:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
la source
1
Pour moi, je devais encore spécifier l'en-tête Content-Typeet le définir sur application/x-www-form-urlencoded.
Victor Ramos
9

Vous pouvez essayer avec la solution ci-dessous

$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
la source
8

Créez un service d'adaptateur pour la publication:

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

}) 

Utilisez-le dans vos contrôleurs ou autre:

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
la source
$ .param uniquement dans jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer est l'équivalent d'Angularjs.
Dexter
7

Il y a un tutoriel vraiment sympa qui passe en revue cela et d'autres choses connexes - Soumettre des formulaires AJAX: La manière AngularJS .

Fondamentalement, vous devez définir l'en-tête de la demande POST pour indiquer que vous envoyez des données de formulaire sous forme de chaîne codée URL et définir les données à envoyer au même format

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

Notez que la fonction d'assistance param () de jQuery est utilisée ici pour sérialiser les données dans une chaîne, mais vous pouvez également le faire manuellement si vous n'utilisez pas jQuery.

Robinmitra
la source
1
Les modérateurs ont simplement supprimé ma réponse précédente car je n'ai pas fourni de détails sur la mise en œuvre réelle mentionnée dans le lien. Cela aurait été mieux s'ils m'avaient plutôt demandé d'abord de fournir plus de détails, au lieu de le supprimer, car je modifiais déjà ma réponse pour fournir les détails comme on le voit dans cette réponse!
robinmitra
Le $.paramfaire la magie. solution parfaite pour ceux qui ont une application basée sur jQuery + AngularJS.
dashtinejad
4

Pour les utilisateurs de Symfony2:

Si vous ne voulez rien changer dans votre javascript pour que cela fonctionne, vous pouvez effectuer ces modifications dans votre application symfony:

Créez une classe qui étend 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;

}

}

Utilisez maintenant votre classe dans app_dev.php (ou tout fichier d'index que vous utilisez)

// 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
la source
c'était vraiment utile pour moi, le nouveau createFromGlobals fonctionne maintenant parfaitement. Je ne sais pas pourquoi vous avez obtenu un downvote, mais je l'ai supprimé.
Richard
3

Définir simplement Content-Type ne suffit pas, encoder les données du formulaire avant d'envoyer $http.post(url, jQuery.param(data))

Merlin Ran
la source
3

J'utilise actuellement la solution suivante que j'ai trouvée dans le groupe Google AngularJS.

$ http
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (data)), {
    en-têtes: {
        'Content-Type': 'application / x-www-form-urlencoded; charset = UTF-8 '
    }
}). success (fonction (données) {
    $ scope.data = data;
});

Notez que si vous utilisez PHP, vous devrez utiliser quelque chose comme le composant HTTP Symfony 2 Request::createFromGlobals()pour lire ceci, car $ _POST ne sera pas automatiquement chargé avec.

Aditya MP
la source
2

AngularJS le fait correctement car il fait le type de contenu suivant dans l'en-tête de requête http:

Content-Type: application/json

Si vous utilisez php comme moi, ou même avec Symfony2, vous pouvez simplement étendre la compatibilité de votre serveur pour la norme json comme décrit ici: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

La manière Symfony2 (par exemple à l'intérieur de votre 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());

L'avantage serait que vous n'avez pas besoin d'utiliser param jQuery et que vous pouvez utiliser AngularJS sa façon native de faire de telles requêtes.

Michael
la source
2

Réponse complète (depuis angulaire 1.4). Vous devez inclure la dépendance $ 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
la source
1

Dans la configuration de votre application -

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

Avec votre demande de ressources -

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

Ce n'est pas une réponse directe, mais plutôt une direction de conception légèrement différente:

Ne publiez pas les données sous forme de formulaire, mais en tant qu'objet JSON à mapper directement sur un objet côté serveur, ou utilisez la variable de chemin de style REST

Maintenant, je sais qu'aucune des options ne pourrait convenir dans votre cas puisque vous essayez de passer une clé XSRF. Le mapper dans une variable de chemin comme celle-ci est une conception terrible:

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

Parce que par nature, vous voudriez également passer la clé xsrf à un autre chemin /login, /book-appointmentetc., et vous ne voulez pas salir votre jolie URL

Il est intéressant de l'ajouter en tant que champ d'objet n'est pas approprié non plus, car maintenant sur chaque objet json que vous passez au serveur, vous devez ajouter le champ

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

Vous ne voulez certainement pas ajouter un autre champ sur votre classe côté serveur qui n'a pas d'association sémantique directe avec l'objet domaine.

À mon avis, la meilleure façon de transmettre votre clé xsrf est via un en-tête HTTP. De nombreuses bibliothèques de frameworks Web côté serveur de protection xsrf le prennent en charge. Par exemple, dans Java Spring, vous pouvez le passer en utilisant l'en- X-CSRF-TOKENtête .

L'excellente capacité d'Angular de lier un objet JS à un objet d'interface utilisateur signifie que nous pouvons nous débarrasser de la pratique de publier le formulaire tous ensemble et de publier JSON à la place. JSON peut être facilement désérialisé en objet côté serveur et prend en charge des structures de données complexes telles que la carte, les tableaux, les objets imbriqués, etc.

Comment publier un tableau dans une charge utile de formulaire? Peut-être comme ça:

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

ou ca:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Les deux sont de mauvaise conception ..

gerrytan
la source
0

C'est ce que je fais pour mon besoin, où je dois envoyer les données de connexion à l'API en tant que données de formulaire et l'objet Javascript (userData) est converti automatiquement en données encodées 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);
        });

Voici comment mes données utilisateur sont

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

La seule mince que vous devez changer est d'utiliser la propriété "params" plutôt que "data" lorsque vous créez votre objet $ http:

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

Dans l'exemple ci-dessus, les clients [i] ne sont que des objets JSON (en aucun cas sérialisés). Si vous utilisez "params" plutôt que "data" angular va sérialiser l'objet pour vous en utilisant $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Rafal Zajac
la source
2
En utilisant des paramètres au lieu de données, Angular place les données dans les paramètres d'URL au lieu du corps de la demande. Ce n'est pas ce que l'on attend d'un post de formulaire.
cmenning du
-3

Utilisez le $httpservice AngularJS et utilisez sa postméthode ou sa $httpfonction de configuration .

Shivang Gupta
la source