Jak mogę opublikować dane jako dane formularza zamiast ładunku żądania?

523

W poniższym kodzie $httpmetoda AngularJS wywołuje adres URL i przesyła obiekt xsrf jako „Żądanie ładunku” (zgodnie z opisem na karcie sieciowej debugera Chrome). Metoda jQuery $.ajaxwykonuje to samo wywołanie, ale przekazuje xsrf jako „Dane formularza”.

Jak zmusić AngularJS do przesłania xsrf jako danych formularza zamiast ładunku żądania?

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
źródło
1
To było bardzo przydatne pytanie. Pozwala mi wysyłać ładunek jako ciąg znaków (zmieniając typ zawartości), co uniemożliwia mi radzenie sobie z OPCJAMI przed POST / GET.
earthmeLon
Mam to samo pytanie, to po zażądaniu
adresu

Odpowiedzi:

614

Do przekazywanego obiektu $ http należy dodać następujący wiersz:

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

Przekazywane dane należy przekonwertować na ciąg zakodowany w adresie URL:

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

Masz więc coś takiego:

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

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

AKTUALIZACJA

Aby skorzystać z nowych usług dodanych do AngularJS 1.4, patrz

Mjibson
źródło
3
Czy istnieje sposób, aby kodowanie danych json> url odbywało się automatycznie, czy też można to określić dla każdej metody POST lub PUT?
Dogoku
51
+1 @mjibson, Dla mnie nawet przekazywanie nagłówków nie działało, dopóki nie zobaczyłem twojej odpowiedzi zawierającej to: var xsrf = $.param({fkey: "key"});To głupie, dlaczego kątowe nie może tego zrobić wewnętrznie?
naikus
12
Aby bliżej śledzić domyślne zachowanie $ .ajax, należy również określić zestaw znaków w nagłówku typu zawartości -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre
25
Zamiast używać funkcji param jQuery, po prostu ustaw właściwość params na żądanie $ http i zrobi to, co robi metoda jQuery.param, o ile nagłówek Content-Type to „application / x-www-form-urlencoded” - stackoverflow .pl / pytania / 18967307 /…
spig
13
@spig Tak, zrobi to, co robi jQuery.param, ale jeśli użyjesz właściwości params, twoje właściwości zostaną zakodowane jako część adresu URL żądania zamiast w treści - nawet jeśli podałeś aplikację / x-www- nagłówek zakodowany w formularzu.
stian
194

Jeśli nie chcesz używać jQuery w rozwiązaniu, możesz spróbować. Rozwiązanie pobrane stąd 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
źródło
7
Ta metoda działa dla mnie w wersji kątowej 1.2.x i myślę, że to najlepsza odpowiedź, ponieważ jest elegancka, działa w podstawie kątowej i nie zależy od żadnych zewnętrznych bibliotek, takich jak jQuery.
gregtczap
2
Podczas korzystania z tej metody natknąłem się na problem w akcji $ zasób. Dane formularza zawierały również funkcje dla $ get, $ save, itp. Rozwiązaniem było fortrochę zmienić instrukcję, aby użyć angular.forEachzamiast niej.
Anthony
10
Zauważ, że w przeciwieństwie do $ .param () ta metoda nie działa rekurencyjnie na tablicach / obiektach.
MazeChaZer
1
Sprawdziłbym, czy obj[p]to nie jest zerowe ani niezdefiniowane . W przeciwnym razie zostanie wysłany ciąg „null” lub „undefined” jako wartość.
tamir
1
Nie zrozumiałem transformRequest: function(obj)Ponieważ obiekt jest niezdefiniowany, czy przypuszczamy, że przekażemy xsrf? JaktransformRequest: function(xsrf)
Akshay Taru,
92

Ciągłe zamieszanie wokół tego problemu zainspirowało mnie do napisania postu na ten temat na blogu. Rozwiązanie, które proponuję w tym poście, jest lepsze niż twoje obecne najwyżej ocenione rozwiązanie, ponieważ nie ogranicza cię do parametryzacji obiektu danych dla wezwań serwisowych $ http; tzn. z moim rozwiązaniem możesz po prostu kontynuować przekazywanie rzeczywistych obiektów danych do $ http.post () itp. i nadal osiągać pożądany wynik.

Również najwyżej oceniana odpowiedź polega na uwzględnieniu na stronie pełnego jQuery dla funkcji $ .param (), podczas gdy moje rozwiązanie jest agnostyczne dla jQuery, czysty AngularJS.

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

Mam nadzieję że to pomoże.

Ezechiel Wiktor
źródło
10
+1 za szczegółowy blog, ale fakt, że jest to potrzebne, jest okropny ...
iwein
4
Tak, może okropne na dwóch poziomach: 1) że AngularJS zdecydował się podnieść de facto (choć wprawdzie źle wprowadzony) standard, oraz 2) że PHP (i kto zna inne języki po stronie serwera) jakoś nie wykrywa automatycznie aplikacji / json Wejście. : P
Ezechiel Wiktor
Czy to możliwe, że angularjs automatycznie dostosowuje się do typu zawartości i odpowiednio koduje? Czy jest to przewidziane
unludo
4
I (podobnie jak wiele innych) zetknąłem się z tym, że mój backend ASP.NET„natywnie” tego nie obsługuje. Jeśli nie chcesz zmieniać zachowania AngularJS (czego nie zrobiłem, ponieważ mój interfejs API zwraca JSON, dlaczego nie akceptuje też JSON, jest bardziej elastyczny niż dane formularza), możesz czytać z niego Request.InputStreami obsługiwać go w dowolny sposób chcesz tego. (Zdecydowałem się na deserializację w dynamiccelu ułatwienia użycia.)
Aidiakapi
2
Zagłosowano, ponieważ nie jest to samodzielna odpowiedź . Dobre odpowiedzi to nie tylko linki do innych stron. Od Jak odpowiedzieć : „Zawsze podawaj najistotniejszą część ważnego linku, w przypadku gdy strona docelowa jest nieosiągalna lub zostaje na stałe offline”.
James
83

Wziąłem kilka innych odpowiedzi i zrobiłem coś nieco czystszego, umieść to .config()wywołanie na końcu swojego angular.module w pliku 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
źródło
1
Działa jak urok - nawet jeśli jest dołączony do definicji zasobu.
Kai Mattern,
3
Zadbałem również o użycie, unshift()aby pozostałe transformacje pozostały niezakłócone. Dobra robota.
Aditya, poseł
2
doskonały! działało dobrze dla mnie! smutny kąt nie natywnie tego wspiera.
spierala
2
Ta odpowiedź powinna być właściwa na górze, inne są w błędzie, dzięki kolego !!
Jose Ignacio Hita
2
Co powiesz na kodowanie rekurencyjne?
Petah
58

Od wersji AngularJS 1.4.0 istnieje wbudowana $httpParamSerializerusługa, która konwertuje dowolny obiekt na część żądania HTTP zgodnie z regułami wymienionymi na stronie dokumentacji .

Można go użyć w następujący sposób:

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

Pamiętaj, że dla poprawnego formularza post Content-Typenagłówek musi zostać zmieniony. Aby to zrobić globalnie dla wszystkich żądań POST, można użyć tego kodu (wziętego z pół-odpowiedzi Albireo):

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

Aby to zrobić tylko dla bieżącego postu, headersnależy zmodyfikować właściwość obiektu żądania:

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

$http(req);
Mitja
źródło
Jak możemy zrobić to samo w niestandardowej fabryce zasobów $?
martwa natura
Uwaga: aktualizuję aplikację z Angular 1.3 do 1.5. Zmieniło nagłówki w transformRequest. Z jakiegoś powodu powyższa metoda nie działa dla mnie, Angular dodaje podwójne cudzysłowy wokół ciągu zakodowanego w adresie URL. Rozwiązany z transformRequest: $httpParamSerializer, data: formDataObj. Dzięki za rozwiązanie.
PhiLho
24

Możesz zdefiniować zachowanie globalnie:

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

Nie musisz za każdym razem zmieniać tego na nowo:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
źródło
46
Twój przykład jest tak błędny ... Zmieniasz tylko nagłówek. Same dane nadal będą kodowane w JSON i nieczytelne dla starszych serwerów, które nie mogą odczytać JSON.
alexk,
victorblog.com/2012/12/20/… - tutaj jest dobry przykład, w którym przesłonisz domyślny nagłówek $ http, a także przekonwertujesz obiekt na serializowane dane formularza.
Federico
20

Aby obejść ten problem, kod odbierający test POST powinien odpowiadać na dane aplikacji / json. Do PHP dodałem poniższy kod, pozwalając mi na POST w postaci zakodowanej w formacie 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
źródło
jest to jeden z dobrych przykładów poprawek po stronie serwera, ponieważ prawdziwym problemem w tym zakresie jest interfejs API po stronie serwera. bravo
Vignesh
16

Te odpowiedzi wyglądają jak szalone przesady, czasem proste jest po prostu lepsze:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
źródło
1
Dla mnie nadal musiałem określić nagłówek Content-Typei ustawić go na application/x-www-form-urlencoded.
Victor Ramos,
9

Możesz wypróbować poniższe rozwiązanie

$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
źródło
8

Utwórz usługę adaptera dla 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'}
        })
    }

}) 

Użyj go w swoich kontrolerach lub cokolwiek innego:

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
źródło
$ .param tylko w jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer jest odpowiednikiem Angularjs.
Dexter
7

Jest naprawdę fajny samouczek, który omawia tę i inne powiązane rzeczy - Przesyłanie formularzy AJAX: The AngularJS Way .

Zasadniczo musisz ustawić nagłówek żądania POST, aby wskazać, że wysyłasz dane formularza jako ciąg znaków zakodowany w adresie URL, i ustawić dane do wysłania w tym samym formacie

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

Zauważ, że funkcja pomocnicza param () jQuery służy tutaj do szeregowania danych w ciąg, ale możesz to zrobić ręcznie, jeśli nie używasz jQuery.

robinmitra
źródło
1
Moderatorzy po prostu usunęli moją poprzednią odpowiedź, ponieważ nie podałem szczegółów faktycznej implementacji wspomnianej w linku. Byłoby lepiej, gdyby zamiast tego poprosili mnie najpierw o podanie dalszych szczegółów, zamiast ich usuwania, ponieważ już edytowałem moją odpowiedź, aby podać szczegóły, jak pokazano w tej odpowiedzi!
robinmitra
$.paramZrobić magię. idealne rozwiązanie dla osób, które mają aplikację opartą na jQuery + AngularJS.
dashtinejad
4

Dla użytkowników Symfony2:

Jeśli nie chcesz nic zmieniać w swoim javascript, aby to działało, możesz wprowadzić te zmiany w aplikacji Symfony:

Utwórz klasę rozszerzającą klasę 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;

}

}

Teraz użyj swojej klasy w app_dev.php (lub dowolnym używanym pliku indeksu)

// 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
źródło
było to dla mnie bardzo przydatne, nowy createFromGlobals działa teraz idealnie. Nie wiem, dlaczego masz głos negatywny, ale go usunąłem.
Richard
3

Wystarczy ustawić Content-Type nie wystarczy, adres URL koduje dane formularza przed wysłaniem. $http.post(url, jQuery.param(data))

Merlin Ran
źródło
3

Obecnie używam następującego rozwiązania, które znalazłem w grupie google AngularJS.

$ http
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (data)), {
    nagłówki: {
        „Content-Type”: „application / x-www-form-urlencoded; charset = UTF-8 '
    }
}). sukces (funkcja (dane) {
    $ scope.data = dane;
});

Zauważ, że jeśli używasz PHP, musisz użyć czegoś takiego jak komponent HTTP Symfony 2, Request::createFromGlobals()aby to przeczytać, ponieważ $ _POST nie zostanie automatycznie załadowany z nim.

Aditya MP
źródło
2

AngularJS robi to dobrze, ponieważ wykonuje następujący typ zawartości w nagłówku żądania http:

Content-Type: application/json

Jeśli idziesz z php jak ja, a nawet z Symfony2, możesz po prostu rozszerzyć kompatybilność serwera dla standardu json, jak opisano tutaj: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

Sposób Symfony2 (np. W twoim 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());

Zaletą byłoby to, że nie musisz używać parametru jQuery i możesz używać AngularJS w jego natywnym sposobie wykonywania takich żądań.

Michał
źródło
2

Pełna odpowiedź (od kątowej 1.4). Musisz dołączyć zależność $ 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
źródło
1

W konfiguracji aplikacji -

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

Z prośbą o zasoby -

 headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
Vivex
źródło
0

To nie jest bezpośrednia odpowiedź, ale raczej nieco inny kierunek projektowania:

Nie publikuj danych jako formularza, ale jako obiekt JSON, który ma być bezpośrednio odwzorowany na obiekt po stronie serwera, lub użyj zmiennej ścieżki stylu REST

Teraz wiem, że żadna opcja może nie być odpowiednia w twoim przypadku, ponieważ próbujesz przekazać klucz XSRF. Odwzorowanie go na taką zmienną ścieżkową jest okropnym projektem:

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

Ponieważ z natury chcesz przekazać klucz xsrf również na inną ścieżkę, /login , /book-appointmentitd. I nie chcesz, aby Twój dość bałaganu URL

Co ciekawe, dodanie go jako pola obiektowego również nie jest właściwe, ponieważ teraz do każdego obiektu json przekazywanego do serwera należy dodać pole

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

Na pewno nie chcesz dodawać innego pola do klasy po stronie serwera, która nie ma bezpośredniego powiązania semantycznego z obiektem domeny.

Moim zdaniem najlepszym sposobem na przekazanie klucza xsrf jest nagłówek HTTP. Obsługuje to wiele bibliotek frameworku WWW po stronie serwera ochrony xsrf. Na przykład w Java Spring można przekazać go za pomocą X-CSRF-TOKENnagłówka .

Doskonała zdolność Angulara do wiązania obiektu JS z obiektem interfejsu użytkownika oznacza, że ​​możemy pozbyć się praktyki wysyłania formularza razem i zamiast tego publikować JSON. JSON można łatwo przekształcić do postaci szeregowej w obiekt po stronie serwera i obsługiwać złożone struktury danych, takie jak mapa, tablice, obiekty zagnieżdżone itp.

Jak zamieszczasz tablicę w formularzu? Może tak:

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

albo to:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Oba są kiepskie.

Gerrytan
źródło
0

To właśnie robię dla moich potrzeb, gdzie muszę wysłać dane logowania do interfejsu API jako dane formularza, a obiekt JavaScript (userData) jest automatycznie konwertowany na dane zakodowane w adresie 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);
        });

Tak wyglądają moje dane użytkownika

var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }
Shubham
źródło
-1

Jedyne, co musisz zmienić, to użyć właściwości „params” zamiast „data” podczas tworzenia obiektu $ http:

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

W powyższym przykładzie klienci [i] to po prostu obiekt JSON (w żaden sposób nie szeregowany). Jeśli użyjesz „params” zamiast „data”, kątowy serializuje obiekt za Ciebie za pomocą $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Rafał Zajac
źródło
2
Używając parametrów zamiast danych, Angular umieszcza dane w parametrach URL zamiast w treści żądania. Nie jest to oczekiwane od postu formularza.
powitanie
-3

Użyj $httpusługi AngularJS i użyj jej postmetody lub $httpfunkcji konfiguracji .

Shivang Gupta
źródło