Bagaimana cara mengirim data sebagai data formulir alih-alih payload permintaan?

523

Dalam kode di bawah ini, $httpmetode AngularJS memanggil URL, dan mengirimkan objek xsrf sebagai "Minta Payload" (seperti yang dijelaskan dalam tab jaringan debugger Chrome). $.ajaxMetode jQuery melakukan panggilan yang sama, tetapi mengirimkan xsrf sebagai "Form Data".

Bagaimana saya bisa membuat AngularJS mengirimkan xsrf sebagai data formulir alih-alih muatan permintaan?

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
sumber
1
Ini adalah pertanyaan yang sangat berguna. Itu memungkinkan saya untuk mengirim payload sebagai string (dengan mengubah Content-Type), yang mencegah saya dari harus berurusan dengan OPSI sebelum POST / GET.
earthmeLon
Saya punya pertanyaan yang sama, itu setelah saya meminta url, tapi aku tidak bisa mendapatkan parameter yang saya kirimkan
黄伟杰

Jawaban:

614

Baris berikut perlu ditambahkan ke objek $ http yang diteruskan:

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

Dan data yang dikirimkan harus dikonversi ke string yang disandikan URL:

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

Jadi, Anda memiliki sesuatu seperti:

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

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

MEMPERBARUI

Untuk menggunakan layanan baru yang ditambahkan dengan AngularJS V1.4, lihat

mjibson
sumber
3
Apakah ada cara agar penyandian json> url data terjadi secara otomatis atau untuk menentukan ini terjadi untuk setiap metode POST atau PUT?
Dogoku
51
+1 @mjibson, bagi saya, meskipun melewati header tidak berfungsi, sampai saya melihat jawaban Anda berisi ini: var xsrf = $.param({fkey: "key"});Itu bodoh, mengapa sudut tidak bisa melakukannya secara internal?
naikus
12
Untuk lebih dekat mengikuti perilaku default $ .ajax, charset juga harus ditentukan dalam header tipe konten -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Imre
25
Alih-alih menggunakan fungsi param jQuery, cukup atur properti params pada permintaan $ http dan itu akan melakukan apa yang dilakukan metode jQuery.param selama header Tipe-Konten adalah 'application / x-www-form-urlencoded' - stackoverflow .com / pertanyaan / 18967307 / ...
spig
13
@ spig Ya, itu akan melakukan apa yang jQuery.param lakukan, tetapi, jika Anda menggunakan properti params, properti Anda akan dikodekan sebagai bagian dari URL permintaan alih-alih di dalam tubuh - bahkan jika Anda telah menentukan aplikasi / x-www- form-urlencoded header.
stian
194

Jika Anda tidak ingin menggunakan jQuery dalam solusi, Anda bisa mencoba ini. Solusi diperoleh dari sini 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
sumber
7
Metode ini bekerja untuk saya di 1.2.x sudut, dan saya pikir itu adalah jawaban terbaik karena elegan, ia bekerja di sudut inti dan tidak bergantung pada perpustakaan eksternal seperti jQuery.
gregtczap
2
Saya menemukan masalah ketika menggunakan metode ini di dalam tindakan $ resource. Formulir data juga termasuk fungsi untuk $ get, $ save, dll. Solusinya adalah sedikit mengubah forpernyataan untuk digunakan angular.forEach.
Anthony
10
Perhatikan bahwa berbeda dengan $ .param () metode ini tidak bekerja secara rekursif pada array / objek.
MazeChaZer
1
Saya akan memeriksa bahwa obj[p]itu tidak nol atau tidak terdefinisi . Kalau tidak, Anda akhirnya akan mengirim string "null" atau "undefined" sebagai nilai.
tamir
1
Saya tidak mengerti transformRequest: function(obj)Karena obj tidak terdefinisi, Apakah kita harus lulus xsrf? SepertitransformRequest: function(xsrf)
Akshay Taru
92

Kebingungan yang terus berlanjut seputar masalah ini mengilhami saya untuk menulis posting blog tentang hal itu. Solusi yang saya usulkan dalam posting ini lebih baik daripada solusi teratas Anda saat ini karena tidak membatasi Anda untuk parametrizing objek data Anda untuk panggilan layanan $ http; yaitu dengan solusi saya, Anda dapat terus meneruskan objek data aktual ke $ http.post (), dll. dan masih mencapai hasil yang diinginkan.

Juga, jawaban berperingkat teratas bergantung pada dimasukkannya jQuery penuh di halaman untuk fungsi $ .param (), sedangkan solusi saya adalah jQuery agnostic, AngularJS murni siap.

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

Semoga ini membantu.

Yehezkiel Victor
sumber
10
+1 untuk blog terperinci, tetapi kenyataan bahwa ada kebutuhan untuk ini mengerikan ...
iwein
4
Ya, mungkin mengerikan pada dua level: 1) bahwa AngularJS memutuskan untuk menjungkirbalikkan standar de facto (meskipun diakui salah), dan 2) bahwa PHP (dan siapa yang tahu bahasa server-side apa pun) entah bagaimana tidak secara otomatis mendeteksi aplikasi / json memasukkan. : P
Yehezkiel Victor
Apakah mungkin angularjs secara otomatis menyesuaikan dengan tipe konten dan menyandikannya? Apakah sudah diramalkan?
unludo
4
Saya (seperti banyak orang lain) menemukan ini bahwa backend saya ASP.NETtidak 'secara native' mendukung ini. Jika Anda tidak ingin mengubah perilaku AngularJS (yang tidak saya lakukan, karena API saya mengembalikan JSON, mengapa tidak menerima JSON juga, ini lebih fleksibel daripada data formulir) Anda dapat membaca dari Request.InputStreamdan kemudian menanganinya dengan cara apa pun Anda ingin. (Saya memilih untuk membatalkan deserialisasi dynamicagar mudah digunakan.)
Aidiakapi
2
Diturunkan karena ini bukan jawaban yang lengkap . Jawaban yang bagus jangan hanya menautkan di tempat lain. Dari Cara Menjawab : "Selalu kutip bagian yang paling relevan dari tautan penting, jika situs target tidak dapat dijangkau atau offline secara permanen."
James
83

Saya mengambil beberapa jawaban lain dan membuat sesuatu yang sedikit lebih bersih, letakkan .config()panggilan ini di ujung angular.module Anda di 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
sumber
1
Bekerja seperti jimat - bahkan jika ditambahkan ke definisi sumber daya.
Kai Mattern
3
Juga berhati-hati untuk menggunakan unshift()sehingga transformasi lainnya tetap tidak terganggu. Kerja bagus.
Aditya MP
2
sempurna! bekerja dengan baik untuk saya! Sudut sedih tidak secara asli mendukung ini.
spierala
2
Jawaban ini harus yang benar di atas, yang lain salah, terima kasih sobat !!
Jose Ignacio Hita
2
Bagaimana dengan pengkodean rekursif?
Petah
58

Pada AngularJS v1.4.0, ada layanan built-in $httpParamSerializeryang mengubah objek apa pun menjadi bagian dari permintaan HTTP sesuai dengan aturan yang tercantum pada halaman dokumen .

Dapat digunakan seperti ini:

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

Ingat bahwa untuk posting bentuk yang benar, Content-Typetajuk harus diubah. Untuk melakukan ini secara global untuk semua permintaan POST, kode ini (diambil dari setengah jawaban Albireo) dapat digunakan:

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

Untuk melakukan ini hanya untuk posting saat ini, headersproperti dari objek-permintaan perlu diubah:

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

$http(req);
Mitja
sumber
Bagaimana kita dapat melakukan hal yang sama pada pabrik sumber daya $ kustom?
stilllife
Catatan: Saya meningkatkan aplikasi dari Angular 1.3 ke 1.5. Itu mengubah header di transformRequest. Untuk beberapa alasan, metode di atas tidak berfungsi untuk saya, Angular menambahkan tanda kutip ganda di sekitar string yang disandikan URL. Dipecahkan dengan transformRequest: $httpParamSerializer, data: formDataObj. Terima kasih atas solusinya.
PhiLho
24

Anda dapat mendefinisikan perilaku secara global:

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

Jadi Anda tidak perlu mendefinisikannya kembali setiap waktu:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
sumber
46
Contoh Anda sangat salah ... Semua yang Anda modifikasi adalah tajuk. Data itu sendiri masih akan dikodekan JSON, dan tidak dapat dibaca oleh server lama yang tidak dapat membaca JSON.
alexk
victorblog.com/2012/12/20/… - di sini adalah contoh yang baik di mana Anda mengganti header default $ http, serta mengonversi objek ke data formulir berseri.
Federico
20

Sebagai solusinya, Anda cukup membuat kode yang menerima POST merespons data aplikasi / json. Untuk PHP saya menambahkan kode di bawah ini, memungkinkan saya untuk POST untuk itu baik dalam bentuk-disandikan atau 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
sumber
ini adalah salah satu contoh yang baik untuk perbaikan sisi server, Karena masalah sebenarnya pada masalah ini adalah pada API sisi server .. bravo
Vignesh
16

Jawaban-jawaban ini terlihat seperti gila berlebihan, kadang-kadang, sederhana hanya lebih baik:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Serj Sagan
sumber
1
Bagi saya, saya masih harus menentukan header Content-Typedan mengaturnya application/x-www-form-urlencoded.
Victor Ramos
9

Anda dapat mencoba dengan solusi di bawah ini

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

Buat layanan adaptor untuk posting:

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

}) 

Gunakan di pengontrol Anda atau apa pun:

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
sumber
$ .param hanya di jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer setara dengan Angularjs.
Dexter
7

Ada tutorial yang sangat bagus yang membahas hal ini dan hal-hal terkait lainnya - Menyerahkan Formulir AJAX: Cara AngularJS .

Pada dasarnya, Anda perlu mengatur tajuk permintaan POST untuk menunjukkan bahwa Anda mengirim data formulir sebagai string yang disandikan URL, dan mengatur data yang akan dikirim dengan format yang sama

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

Perhatikan bahwa fungsi pembantu param () jQuery digunakan di sini untuk membuat serialisasi data ke dalam sebuah string, tetapi Anda dapat melakukan ini secara manual juga jika tidak menggunakan jQuery.

robinmitra
sumber
1
Moderator hanya menghapus jawaban saya sebelumnya karena saya tidak memberikan detail implementasi sebenarnya yang disebutkan dalam tautan. Akan lebih baik jika mereka meminta saya terlebih dahulu untuk memberikan rincian lebih lanjut, daripada menghapusnya, karena saya sudah mengedit jawaban saya untuk memberikan detail seperti yang terlihat dalam jawaban ini!
robinmitra
The $.parammelakukan keajaiban. solusi sempurna untuk yang memiliki aplikasi berbasis jQuery + AngularJS.
dashtinejad
4

Untuk pengguna Symfony2:

Jika Anda tidak ingin mengubah apa pun di javascript Anda agar berfungsi, Anda dapat melakukan modifikasi ini di aplikasi symfony Anda:

Buat kelas yang memperluas kelas 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;

}

}

Sekarang gunakan kelas Anda di app_dev.php (atau file indeks apa pun yang Anda gunakan)

// 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
sumber
ini sangat berguna bagi saya, createFromGlobals baru berfungsi dengan sempurna. Saya tidak tahu mengapa Anda mendapat downvote, tapi saya menghapusnya.
Richard
3

Cukup atur Content-Type tidak cukup, url encode data formulir sebelum mengirim. $http.post(url, jQuery.param(data))

Merlin Ran
sumber
3

Saat ini saya menggunakan solusi berikut yang saya temukan di grup google AngularJS.

$ http
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (data)), {
    tajuk: {
        'Tipe-Konten': 'aplikasi / x-www-form-urlencoded; charset = UTF-8 '
    }
}). Sukses (fungsi (data) {
    $ scope.data = data;
});

Perhatikan bahwa jika Anda menggunakan PHP, Anda harus menggunakan sesuatu seperti komponen HTTP Symfony 2 Request::createFromGlobals()untuk membaca ini, karena $ _POST tidak akan secara otomatis dimuat dengannya.

Aditya MP
sumber
2

AngularJS melakukannya dengan benar saat melakukan tipe-konten berikut di dalam header http-request:

Content-Type: application/json

Jika Anda menggunakan php seperti saya, atau bahkan dengan Symfony2, Anda cukup memperluas kompatibilitas server Anda untuk standar json seperti dijelaskan di sini: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html

Cara Symfony2 (misalnya di dalam DefaultController Anda):

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

Keuntungannya adalah, bahwa Anda tidak perlu menggunakan param jQuery dan Anda bisa menggunakan AngularJS cara asalnya untuk melakukan permintaan seperti itu.

Michael
sumber
2

Jawaban lengkap (sejak sudut 1.4). Anda harus menyertakan de dependency $ 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
sumber
1

Di konfigurasi aplikasi Anda -

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

Dengan permintaan sumber daya Anda -

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

Ini bukan jawaban langsung, melainkan arah desain yang sedikit berbeda:

Jangan memposting data sebagai formulir, tetapi sebagai objek JSON yang akan langsung dipetakan ke objek sisi server, atau gunakan variabel jalur gaya REST

Sekarang saya tahu tidak ada pilihan yang cocok dalam kasus Anda karena Anda mencoba untuk melewati kunci XSRF. Memetakannya ke variabel path seperti ini adalah desain yang mengerikan:

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

Karena pada dasarnya Anda ingin meneruskan kunci xsrf ke jalur lain juga /login,, /book-appointmentdll. Dan Anda tidak ingin mengacaukan URL cantik Anda

Menariknya menambahkannya sebagai bidang objek juga tidak tepat, karena sekarang pada setiap objek json yang Anda lewati ke server Anda harus menambahkan bidang tersebut

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

Anda tentu tidak ingin menambahkan bidang lain di kelas sisi server Anda yang tidak memiliki asosiasi semantik langsung dengan objek domain.

Menurut pendapat saya cara terbaik untuk melewatkan kunci xsrf Anda adalah melalui header HTTP. Banyak pustaka kerangka kerja web server sisi perlindungan xsrf mendukung ini. Misalnya di Java Spring, Anda bisa meneruskannya menggunakan X-CSRF-TOKENheader .

Kemampuan Angular yang sangat baik untuk mengikat objek JS ke objek UI berarti kita dapat menyingkirkan praktik posting form bersama-sama, dan mengirim JSON sebagai gantinya. JSON dapat dengan mudah diderialisasi ke objek sisi server dan mendukung struktur data yang kompleks seperti peta, array, objek bersarang, dll.

Bagaimana cara Anda memposting array dalam bentuk muatan? Mungkin seperti ini:

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

atau ini:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Keduanya desain yang buruk ..

gerrytan
sumber
0

Ini adalah apa yang saya lakukan untuk kebutuhan saya, Di mana saya perlu mengirim data masuk ke API sebagai data formulir dan Objek Javascript (userData) semakin dikonversi secara otomatis ke URL data yang dikodekan

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

Ini bagaimana Userdata saya

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

Satu-satunya tipis yang harus Anda ubah adalah menggunakan properti "params" daripada "data" saat Anda membuat objek $ http Anda:

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

Dalam contoh di atas klien [i] hanyalah objek JSON (tidak diserialisasi dengan cara apa pun). Jika Anda menggunakan "params" daripada "data" angular akan membuat serial objek untuk Anda menggunakan $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Rafal Zajac
sumber
2
Dengan menggunakan params alih-alih data, Angular menempatkan data dalam parameter URL alih-alih badan permintaan. Ini bukan yang diharapkan dari form posting.
cmenning
-3

Gunakan $httplayanan AngularJS dan gunakan postmetode atau konfigurasikan $httpfungsinya.

Shivang Gupta
sumber