Как я могу опубликовать данные в виде данных формы вместо полезной нагрузки запроса?

523

В приведенном ниже коде $httpметод AngularJS вызывает URL-адрес и отправляет объект xsrf как «Запрос полезной нагрузки» (как описано на вкладке «Сеть отладчика Chrome»). Метод jQuery $.ajaxвыполняет тот же вызов, но отправляет xsrf как «Данные формы».

Как я могу заставить AngularJS отправлять xsrf как данные формы вместо полезной нагрузки запроса?

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
источник
1
Это был очень полезный вопрос. Это позволяет мне отправлять полезную нагрузку в виде строки (путем изменения Content-Type), что не позволяет мне иметь дело с OPTIONS до POST / GET.
earthmeLon
У меня тот же вопрос, это после того, как я запрашиваю URL, но я не могу получить параметр, который я отправляю
黄伟杰

Ответы:

614

Следующая строка должна быть добавлена ​​к переданному объекту $ http:

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

И переданные данные должны быть преобразованы в строку в кодировке URL:

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

Итак, у вас есть что-то вроде:

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

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

ОБНОВИТЬ

Чтобы использовать новые сервисы, добавленные с AngularJS V1.4, см.

mjibson
источник
3
Есть ли способ, чтобы json> url-кодирование данных происходило автоматически или указывало это для каждого метода POST или PUT?
Догоку
51
+1 @mjibson, для меня даже прохождение заголовков не работало, пока я не увидел твой ответ, содержащий это: « var xsrf = $.param({fkey: "key"});Это глупо, почему Angular не может сделать это внутренне?
naikus
12
Для того, чтобы ближе следовать $ .ajax поведения по умолчанию, кодировка также должна быть указана в заголовке типа содержимого -headers: {Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}
Имре
25
Вместо использования функции param в jQuery, просто установите свойство params в запросе $ http, и он будет делать то, что делает метод jQuery.param, пока заголовок Content-Type равен application / x-www-form-urlencoded - stackoverflow .com / questions / 18967307 /…
spig
13
@spig Да, он будет делать то же, что и jQuery.param, но если вы используете свойство params, ваши свойства будут закодированы как часть URL-адреса запроса, а не в теле - даже если вы указали application / x-www- заголовок в форме кода
Стиан
194

Если вы не хотите использовать jQuery в решении, вы можете попробовать это. Решение можно получить здесь 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
Этот метод работает для меня в angular 1.2.x, и я думаю, что это лучший ответ, потому что он элегантен, работает в основном angular и не зависит от каких-либо внешних библиотек, таких как jQuery.
gregtczap
2
Я столкнулся с проблемой при использовании этого метода внутри действия $ resource. Данные формы также включали функции для $ get, $ save и т. Д. Решением было forнемного изменить оператор для использования angular.forEachвместо него.
Энтони
10
Обратите внимание, что в отличие от $ .param () этот метод не работает рекурсивно для массивов / объектов.
MazeChaZer
1
Я бы проверил, что obj[p]не является нулевым или неопределенным . В противном случае вы в конечном итоге отправите в качестве значения строку «null» или «undefined».
Тамир
1
Я не понял, так transformRequest: function(obj)как obj не определен, мы предполагаем передать xsrf? НравитсяtransformRequest: function(xsrf)
Акшай Тару
92

Продолжающаяся путаница вокруг этой проблемы вдохновила меня написать сообщение в блоге об этом. Решение, которое я предлагаю в этом посте, лучше, чем ваше текущее решение с самым высоким рейтингом, поскольку оно не ограничивает вас параметризацией объекта данных для вызовов службы $ http; т.е. с моим решением вы можете просто продолжать передавать фактические объекты данных в $ http.post () и т. д. и при этом достигать желаемого результата.

Кроме того, ответ с самым высоким рейтингом опирается на включение полного jQuery на странице для функции $ .param (), тогда как мое решение не зависит от jQuery и полностью готово для AngularJS.

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

Надеюсь это поможет.

Иезекииль Виктор
источник
10
+1 за подробный блог, но тот факт, что в этом есть необходимость, ужасен ...
iwein
4
Да, может быть, это ужасно на двух уровнях: 1) что AngularJS решил перевернуть де-факто (хотя и ошибочно) стандарт, и 2) что PHP (и кто знает любые другие серверные языки) каким-то образом не обнаруживает application / json автоматически вход. : P
Иезекииль Виктор
Возможно ли, чтобы angularjs автоматически адаптировался к типу контента и соответствующим образом кодировал? Это предусмотрено?
unludo
4
Я (как и многие другие) сталкивался с тем, что мой бэкэнд ASP.NETне «изначально» поддерживал это. Если вы не хотите изменять поведение AngularJS (что я не сделал, потому что мой API возвращает JSON, почему бы ему не принять и JSON, он более гибок, чем данные форм), вы можете прочитать из Request.InputStreamи затем обработать его любым способом ты хочешь это. (Я решил десериализовать его dynamicдля удобства использования.)
Aidiakapi
2
Понижено, потому что это не самостоятельный ответ . Хорошие ответы не просто ссылки где-то еще. От Как отвечать : «Всегда указывайте наиболее соответствующую часть важной связи, в случае , если целевой сайт недоступен или идет постоянно в автономном режиме.»
Джеймс
83

Я взял несколько других ответов и сделал немного чище, поместил этот .config()вызов в конец вашего angular.module в вашем 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
источник
1
Работает как очарование - даже если добавлено к определению ресурса.
Кай Маттерн
3
Также позаботился об использовании, unshift()чтобы другие преобразования оставались нетронутыми. Хорошая работа.
депутат Адитья
2
идеально! работал нормально для меня! грустный угловой не поддерживает это изначально.
Спиерала
2
Этот ответ должен быть правильным наверху, остальные не правы, спасибо друг!
Хосе Игнасио Хита
2
Как насчет рекурсивного кодирования?
Петах
58

Начиная с AngularJS v1.4.0, существует встроенная $httpParamSerializerслужба, которая преобразует любой объект в часть HTTP-запроса в соответствии с правилами, приведенными на странице документации .

Это можно использовать так:

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

Помните, что для правильной формы сообщения, Content-Typeзаголовок должен быть изменен. Чтобы сделать это глобально для всех запросов POST, можно использовать этот код (взят из полуответа Albireo):

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

Чтобы сделать это только для текущей публикации, headersнеобходимо изменить свойство объекта запроса:

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

$http(req);
Митя
источник
Как мы можем сделать то же самое на собственной фабрике $ resource?
натюрморт
Примечание: я обновляю приложение с Angular 1.3 до 1.5. Это изменило заголовки в transformRequest. По какой-то причине описанный выше метод у меня не работает, Angular добавляет двойные кавычки вокруг строки в кодировке URL. Решено с transformRequest: $httpParamSerializer, data: formDataObj. Спасибо за решение.
Филу,
24

Вы можете определить поведение глобально:

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

Так что вам не нужно каждый раз переопределять его:

$http.post("/handle/post", {
    foo: "FOO",
    bar: "BAR"
}).success(function (data, status, headers, config) {
    // TODO
}).error(function (data, status, headers, config) {
    // TODO
});
Albireo
источник
46
Ваш пример настолько неправильный ... Все, что вы изменяете, это заголовок. Сами данные по-прежнему будут закодированы в JSON и не будут читаться старыми серверами, которые не могут прочитать JSON.
alexk
victorblog.com/2012/12/20/… - вот хороший пример, где вы переопределяете заголовок $ http по умолчанию, а также конвертируете объект в сериализованные данные формы.
Федерико
20

В качестве обходного пути вы можете просто заставить код, получающий POST, отвечать на данные application / json. Для PHP я добавил код, приведенный ниже, что позволяет мне POST к нему в форме в кодировке или 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
Джеймс Белл
источник
это один из хороших примеров исправления на стороне сервера, потому что реальная проблема в этом вопросе заключается в API на стороне сервера .. bravo
Vignesh
16

Эти ответы выглядят как безумное излишество, иногда просто лучше:

$http.post(loginUrl, "userName=" + encodeURIComponent(email) +
                     "&password=" + encodeURIComponent(password) +
                     "&grant_type=password"
).success(function (data) {
//...
Серж Саган
источник
1
Мне все еще пришлось указать заголовок Content-Typeи установить его application/x-www-form-urlencoded.
Виктор Рамос
9

Вы можете попробовать с приведенным ниже решением

$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
источник
8

Создайте сервис адаптера для почты:

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

}) 

Используйте его в своих контроллерах или что-то еще:

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
источник
$ .param только в jquery abi. jsfiddle.net/4n9fao9q/27 $ httpParamSerializer является эквивалентом Angularjs.
Декстер
7

Существует действительно хороший учебник, посвященный этому и другим связанным с этим материалам - Отправка форм AJAX: Путь AngularJS .

По сути, вам нужно установить заголовок запроса POST, чтобы указать, что вы отправляете данные формы в виде строки в кодировке URL, и настроить отправку данных в том же формате.

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

Обратите внимание, что вспомогательная функция jQuery param () используется здесь для сериализации данных в строку, но вы также можете сделать это вручную, если не используете jQuery.

robinmitra
источник
1
Модераторы просто удалили мой предыдущий ответ, потому что я не предоставил подробную информацию о фактической реализации, упомянутой в ссылке. Было бы лучше, если бы вместо этого они попросили меня сначала предоставить дополнительную информацию, а не удалять ее, поскольку я уже редактировал свой ответ, чтобы предоставить детали, как видно из этого ответа!
Робинмитра
$.paramСделать магию. Идеальное решение для тех, у кого есть приложение на базе jQuery + AngularJS.
Даштинежад
4

Для пользователей Symfony2:

Если вы не хотите ничего менять в своем javascript, чтобы это работало, вы можете внести следующие изменения в ваше приложение Symfony:

Создайте класс, который расширяет класс 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;

}

}

Теперь используйте ваш класс в app_dev.php (или любом индексном файле, который вы используете)

// 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);
Кармель
источник
это было очень полезно для меня, теперь новый createFromGlobals работает отлично. Я не знаю, почему у вас понизилось голосование, но я его убрал.
Ричард
3

Просто установить Content-Type недостаточно, url кодирует данные формы перед отправкой. $http.post(url, jQuery.param(data))

Мерлин Ран
источник
3

В настоящее время я использую следующее решение, которое я нашел в группе Google AngularJS.

$ HTTP
.post ('/ echo / json /', 'json =' + encodeURIComponent (angular.toJson (data)), {
    заголовки: {
        'Content-Type': 'application / x-www-form-urlencoded; кодировка = UTF-8'
    }
}). success (function (data) {
    $ scope.data = data;
});

Обратите внимание, что если вы используете PHP, вам нужно будет использовать что-то вроде HTTP-компонента Symfony 2, Request::createFromGlobals()чтобы прочитать это, так как $ _POST не будет автоматически загружаться с ним.

Адитья М.П.
источник
2

AngularJS делает это правильно, так как он делает следующий тип контента внутри заголовка http-запроса:

Content-Type: application/json

Если вы используете php, как я, или даже Symfony2, вы можете просто расширить совместимость своего сервера со стандартом json, как описано здесь: http://silex.sensiolabs.org/doc/cookbook/json_request_body.html.

Способ Symfony2 (например, внутри вашего 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());

Преимущество заключается в том, что вам не нужно использовать параметр jQuery, и вы можете использовать AngularJS для его родного способа выполнения таких запросов.

Майкл
источник
2

Полный ответ (начиная с углового 1.4). Вам необходимо включить зависимость de $ 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

В конфиге вашего приложения -

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

С вашим запросом ресурса -

 headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
Vivex
источник
0

Это не прямой ответ, а немного другое направление дизайна:

Не размещайте данные как форму, но как объект JSON для непосредственного сопоставления с объектом на стороне сервера или используйте переменную пути в стиле REST

Теперь я знаю, что ни один из вариантов не подходит в вашем случае, так как вы пытаетесь передать ключ XSRF. Отображение этого в переменную пути как это ужасный дизайн:

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

Поскольку по своей природе вы хотите передать XSRF ключа к другому пути тоже /login, и /book-appointmentт.д. , и вы не хотите испортить ваш довольно URL

Интересно, что добавление его в качестве поля объекта также не подходит, потому что теперь для каждого объекта json, передаваемого на сервер, необходимо добавить поле

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

Вы, конечно, не хотите добавлять другое поле в свой класс на стороне сервера, которое не имеет прямой семантической ассоциации с объектом домена.

На мой взгляд, лучший способ передать ваш ключ xsrf через HTTP-заголовок. Многие библиотеки веб-фреймворков на стороне сервера защиты xsrf поддерживают это. Например, в Java Spring вы можете передать его с помощью X-CSRF-TOKENзаголовка .

Отличная возможность Angular связывать JS-объект с UI-объектом означает, что мы можем избавиться от практики публиковать формы все вместе и вместо этого публиковать JSON. JSON можно легко десериализовать в объект на стороне сервера и поддерживать сложные структуры данных, такие как карта, массивы, вложенные объекты и т. Д.

Как вы размещаете массив в форме полезной нагрузки? Может быть так:

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

или это:

shopLocation=downtwon&daysOpen=Monday,Tuesday,Wednesday

Оба плохой дизайн ..

gerrytan
источник
0

Это то, что я делаю для своих нужд. Когда мне нужно отправить данные для входа в API в виде данных формы, а объект Javascript (userData) автоматически преобразуется в данные в формате 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);
        });

Вот как мои пользовательские данные

var userData = {
                grant_type: 'password',
                username: loginData.userName,
                password: loginData.password
            }
Shubham
источник
-1

Единственное, что вам нужно изменить - это использовать свойство «params» вместо «data» при создании объекта $ http:

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

В приведенном выше примере клиенты [i] - это просто объект JSON (никак не сериализованный). Если вы используете «params» вместо «data», angular будет сериализовать объект для вас, используя $ httpParamSerializer: https://docs.angularjs.org/api/ng/service/ $ httpParamSerializer

Рафал Заяц
источник
2
Используя параметры вместо данных, Angular помещает данные в параметры URL вместо тела запроса. Это не то, что ожидается от формы сообщения.
начало
-3

Используйте $httpсервис AngularJS и используйте его postметод или $httpфункцию настройки .

Шиванг Гупта
источник