Как отправить междоменный запрос POST через JavaScript?


568

Как отправить междоменный запрос POST через JavaScript?

Примечания - это не должно обновлять страницу, и мне нужно получить и проанализировать ответ впоследствии.


Я хотел бы немного узнать о сценарии использования, который позволяет вам попытаться это сделать. Не могли бы вы рассказать что-нибудь об этом?
mkoeller

В основном я работаю над сценарием, который должен отправить некоторый текст из файла HTML на другой сервер для обработки.
Идо Шахам

3
Можете ли вы настроить прокси, который делает это на стороне сервера и просто дает вашему сценарию результат? Или это должен быть 100% JavaScript?
Саша Чедыгов

Ответы:


382

Обновление: Прежде чем продолжить, каждый должен прочитать и понять учебник html5rocks по CORS. Это легко понять и очень ясно.

Если вы управляете сервером POST, просто используйте «Стандарт общего доступа к ресурсам», установив заголовки ответов на сервере. Этот ответ обсуждается в других ответах в этой теме, но не очень ясно, на мой взгляд.

Вкратце, вот как вы выполняете междоменный POST от from.com/1.html до to.com/postHere.php (используя PHP в качестве примера). Примечание: вам нужно только установить Access-Control-Allow-Originдля OPTIONSзапросов NON - этот пример всегда устанавливает все заголовки для меньшего фрагмента кода.

  1. В postHere.php настройка следующая:

    switch ($_SERVER['HTTP_ORIGIN']) {
        case 'http://from.com': case 'https://from.com':
        header('Access-Control-Allow-Origin: '.$_SERVER['HTTP_ORIGIN']);
        header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS');
        header('Access-Control-Max-Age: 1000');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
        break;
    }

    Это позволяет вашему скрипту создавать междоменные POST, GET и OPTIONS. Это станет ясно, когда вы продолжите читать ...

  2. Настройте свой междоменный POST из JS (пример jQuery):

    $.ajax({
        type: 'POST',
        url: 'https://to.com/postHere.php',
        crossDomain: true,
        data: '{"some":"json"}',
        dataType: 'json',
        success: function(responseData, textStatus, jqXHR) {
            var value = responseData.someKey;
        },
        error: function (responseData, textStatus, errorThrown) {
            alert('POST failed.');
        }
    });

Когда вы выполните POST на шаге 2, ваш браузер отправит серверу метод «OPTIONS». Это «фыркание» браузером, чтобы увидеть, крут ли сервер, когда вы размещаете на нем сообщение. Сервер отвечает «Access-Control-Allow-Origin», сообщая браузеру, что он в порядке, чтобы POST | GET | ORIGIN, если запрос возник из « http://from.com » или « https://from.com ». Поскольку с сервером все в порядке, браузер сделает второй запрос (на этот раз POST). Рекомендуется, чтобы ваш клиент устанавливал тип контента, который он отправляет, так что вам также нужно это разрешить.

У MDN есть отличная статья о контроле доступа HTTP , которая подробно описывает работу всего потока. Согласно их документам, он должен «работать в браузерах, которые поддерживают межсайтовый XMLHttpRequest». Это немного вводит в заблуждение, так как я думаю, что только современные браузеры допускают междоменный POST. Я только подтвердил, что это работает с сафари, хромом, FF 3.6.

Имейте в виду следующее, если вы делаете это:

  1. Ваш сервер должен будет обработать 2 запроса за операцию
  2. Вам придется подумать о последствиях для безопасности. Будьте осторожны, прежде чем делать что-то вроде «Access-Control-Allow-Origin: *»
  3. Это не будет работать на мобильных браузерах. По моему опыту они вообще не допускают междоменный POST. Я тестировал Android, iPad, iPhone
  4. Существует довольно большая ошибка в FF <3.6, где, если сервер возвращает код ответа, отличный от 400, и существует тело ответа (например, ошибки проверки), FF 3.6 не получит тело ответа. Это огромная боль в заднице, так как вы не можете использовать хорошие практики REST. Смотрите ошибку здесь (она хранится в jQuery, но я думаю, это ошибка FF - похоже, она исправлена ​​в FF4).
  5. Всегда возвращайте вышеуказанные заголовки, а не только по запросам OPTION. FF нуждается в этом в ответе от POST.

Может ли он вернуть HTML, например? Мне нужно вернуть HTML, и что-то не работает ...
denis_n

Да, вы должны быть в состоянии. Никогда не пробовал, хотя. Ваш сервер возвращает 200? Также ваш сервер возвращает заголовки в запросах OPTIONS AND POST? Я обновил свой ответ с более подробной информацией об этом. Убедитесь, что ваш сервер отвечает с правильным заголовком типа контента (например, text / html). Я рекомендую использовать Google Chrome, щелкните правой кнопкой мыши страницу> проверить элемент. Нажмите на вкладку сети, и посмотрите POST и ответ. Должен дать вам информацию о том, что идет не так.
Рыноп

Я пробовал это, но все равно получаю 400 Bad Requestпо OPTIONSзапросу. и во firefoxвтором запросе POSTникогда не делается. :(
Заин Шейх

Есть ли способ вызвать ваш локальный компьютер в вашем случае выше? Или вам просто нужно использовать * в этом случае для разрешения происхождения.
Тодд Вэнс

1
Последний раз редактировалось 4 года назад. Будет ли это работать в мобильных браузерах сейчас?
Франкпинто

121

Если вы управляете удаленным сервером, вам, вероятно, следует использовать CORS, как описано в этом ответе ; он поддерживается в IE8 и более поздних версиях, а также во всех последних версиях FF, GC и Safari. (Но в IE8 и 9 CORS не позволит вам отправлять куки в запросе.)

Так что, если вы не управляете удаленным сервером, или если вам нужно поддерживать IE7, или если вам нужны файлы cookie, и вы должны поддерживать IE8 / 9, вы, вероятно, захотите использовать технику iframe.

  1. Создайте фрейм с уникальным именем. (iframes используют глобальное пространство имен для всего браузера, поэтому выберите имя, которое не будет использовать ни один другой веб-сайт.)
  2. Создайте форму со скрытыми входными данными, ориентируясь на iframe.
  3. Отправьте форму.

Вот пример кода; Я проверил это на IE6, IE7, IE8, IE9, FF4, GC11, S5.

function crossDomainPost() {
  // Add the iframe with a unique name
  var iframe = document.createElement("iframe");
  var uniqueString = "CHANGE_THIS_TO_SOME_UNIQUE_STRING";
  document.body.appendChild(iframe);
  iframe.style.display = "none";
  iframe.contentWindow.name = uniqueString;

  // construct a form with hidden inputs, targeting the iframe
  var form = document.createElement("form");
  form.target = uniqueString;
  form.action = "http://INSERT_YOUR_URL_HERE";
  form.method = "POST";

  // repeat for each parameter
  var input = document.createElement("input");
  input.type = "hidden";
  input.name = "INSERT_YOUR_PARAMETER_NAME_HERE";
  input.value = "INSERT_YOUR_PARAMETER_VALUE_HERE";
  form.appendChild(input);

  document.body.appendChild(form);
  form.submit();
}

Осторожно! Вы не сможете напрямую прочитать ответ POST, так как iframe существует в отдельном домене. Кадры не могут общаться друг с другом из разных доменов; это политика того же происхождения .

Если вы управляете удаленным сервером, но не можете использовать CORS (например, потому что вы используете IE8 / IE9 и вам необходимо использовать куки), есть способы обойти политику одного и того же происхождения, например, с помощью window.postMessageи / или одна из нескольких библиотек, позволяющая отправлять междоменные кросс-фреймовые сообщения в старых браузерах:

Если вы не управляете удаленным сервером, вы не можете прочитать ответ POST, точка. В противном случае это вызовет проблемы с безопасностью.


2
Вам нужно будет указать для form.target что-нибудь, иначе браузер перейдет с вашего сайта на URL-адрес действия формы. Кроме того, строка должна быть уникальной; если есть другие фреймы или окна с таким же именем, форма может публиковаться в этом окне вместо вашего iframe. Но насколько уникальным оно должно быть? Наверное, не очень. Вероятность ударов довольно мала. пожимает плечами
Дан Фабулич

1
@Nawaz Как я уже сказал в своем ответе, вам придется выполнять кросс-доменное взаимодействие между доменами, чтобы получить результат на своей веб-странице. Требуется, чтобы вы управляли удаленным веб-сервером, чтобы вы могли изменить его ответ, чтобы разрешить связь с вашей веб-страницей. (Во-первых, серверу нужно будет ответить с помощью HTML; если сервер отвечает с помощью необработанного XML, он не может осуществлять межкадровую связь.)
Дэн Фабулич,

1
+1 - это лучшее решение, которое я нашел, если у вас нет доступа к серверу
Джеймс Лонг

1
@ VojtechB Нет, это будет дыра в безопасности.
Дан Фабулич

1
@Andrus Вы можете прочитать результат POST, но только если вы управляете сервером! Смотри в этом ответе: «сделай X на отправителе [клиенте], сделай Y на получателе [сервере]». Если вы не контролируете приемник / сервер, вы не можете сделать Y, и поэтому вы не можете прочитать результат POST.
Дан Фабулич

48
  1. Создать iFrame,
  2. положить форму в нем со скрытыми входами,
  3. установить действие формы для URL,
  4. Добавить iframe в документ
  5. отправить форму

ПСЕВДОКОД

 var ifr = document.createElement('iframe');
 var frm = document.createElement('form');
 frm.setAttribute("action", "yoururl");
 frm.setAttribute("method", "post");

 // create hidden inputs, add them
 // not shown, but similar (create, setAttribute, appendChild)

 ifr.appendChild(frm);
 document.body.appendChild(ifr);
 frm.submit();

Вы, вероятно, хотите, чтобы стиль iframe был скрыт и абсолютно позиционирован. Не уверен, что браузер разрешит публикацию на нескольких сайтах, но если это так, то как это сделать.


4
На самом деле, это немного неточно, так как ifr.appendChild (frm); не будет работать. iframe является ссылкой на объект окна, и для него не существует метода appendChild. Сначала вам нужно будет захватить узел документа в iframe. Это требует обнаружения функций для работы в разных браузерах.
Ракеш Пай

Спасибо. Нашли эти полезные ссылки по этому вопросу: bindzus.wordpress.com/2007/12/24/… developer.apple.com/internet/webcontent/iframe.html
Идо Шахам

19
Проблема! Полученный ответ в iframe находится в другом домене, поэтому главное окно не имеет к нему доступа, а iframe не имеет доступа к главному окну. Так что это решение только кажется хорошим для выполнения POST, но вы не можете разобрать ответ позже :(
Ido Schacham

2
Попробуйте установить onload в теге body ответа функции JavaScript, которая вызывает функцию в родительском элементе со строкой ответа.
Лу Франко

Этот ответ не работал для меня; Я разместил свой вариант ниже.
Дан Фабулич

24

Будь проще:

  1. междоменный пост:
    использованиеcrossDomain: true,

  2. Не следует обновлять страницу:
    Нет, она не обновляет страницу, посколькуобратный вызовsuccessилиerrorасинхронный вызов будет вызван, когда сервер отправит ответ.


Пример скрипта:

$.ajax({
        type: "POST",
        url: "http://www.yoururl.com/",
        crossDomain: true,
        data: 'param1=value1&param2=value2',
        success: function (data) {
            // do something with server response data
        },
        error: function (err) {
            // handle your error logic here
        }
    });

8
crossDomain: trueстранно, не имеет абсолютно никакого отношения к реальным междоменным запросам. Если запрос междоменный, jquery автоматически устанавливает значение true.
Кевин Б.

16

Если у вас есть доступ ко всем задействованным серверам, укажите в заголовке ответа следующую страницу для запрашиваемой страницы в другом домене:

PHP:

header('Access-Control-Allow-Origin: *');

Например, в коде Drupal xmlrpc.php вы должны сделать это:

function xmlrpc_server_output($xml) {
    $xml = '<?xml version="1.0"?>'."\n". $xml;
    header('Connection: close');
    header('Content-Length: '. strlen($xml));
    header('Access-Control-Allow-Origin: *');
    header('Content-Type: application/x-www-form-urlencoded');
    header('Date: '. date('r'));
    // $xml = str_replace("\n", " ", $xml); 

    echo $xml;
    exit;
}

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



6
  1. Создайте два скрытых фрейма (добавьте «display: none;» в стиль css). Сделайте так, чтобы ваш второй iframe указывал на что-то в вашем домене.

  2. Создайте скрытую форму, установите для ее метода «post» с target = вашим первым iframe и, при желании, установите для enctype «multipart / form-data» (я думаю, вы хотите сделать POST, потому что вы хотите отправлять многокомпонентные данные, такие как изображения ?)

  3. Когда все будет готово, сделайте форму submit () POST.

  4. Если вы можете заставить другой домен возвращать javascript, который будет выполнять междоменную связь с Iframes ( http://softwareas.com/cross-domain-communication-with-iframes ), то вам повезло, и вы можете получить ответ также.

Конечно, если вы хотите использовать свой сервер в качестве прокси, вы можете избежать всего этого. Просто отправьте форму на свой собственный сервер, который перенаправит запрос на другой сервер (при условии, что другой сервер не настроен на обнаружение несоответствия IP-адресов), получите ответ и верните все, что захотите.


6

Еще одна важная вещь, чтобы отметить !!! В приведенном выше примере описано, как использовать

$.ajax({
    type     : 'POST',
    dataType : 'json', 
    url      : 'another-remote-server',
    ...
});

JQuery 1.6 и ниже имеет ошибку с междоменным XHR. Согласно Firebug, никаких запросов, кроме ВАРИАНТОВ, не было отправлено. Нет пост. Совсем.

Потратил 5 часов на тестирование / настройку моего кода. Добавление большого количества заголовков на удаленный сервер (скрипт). Без какого-либо эффекта. Но позже я обновил JQuery lib до версии 1.6.4, и все работает как шарм.


Whoopps, а не в Опере 10.61. Мое окончательное решение сделать это было использовать прокси PHP на моем домене.
BasTaller

Как вы использовали PHP прокси? Можете ли вы направить меня на это?
Zoran777

см. ответы ниже, например, Иван Дерст
BasTaller

5

Если вы хотите сделать это в среде ASP.net MVC с JQuery AJAX, выполните следующие действия: (это краткое изложение решения, предлагаемого в этой теме)

Предположим, что «caller.com» (может быть любой веб-сайт) необходимо опубликовать на «server.com» (приложение ASP.net MVC)

  1. В файле «server.com» приложения Web.config добавьте следующий раздел:

      <httpProtocol>
          <customHeaders>
              <add name="Access-Control-Allow-Origin" value="*" />
              <add name="Access-Control-Allow-Headers" value="Content-Type" />
              <add name="Access-Control-Allow-Methods" value="POST, GET, OPTIONS" />
          </customHeaders>
      </httpProtocol>
  2. На «server.com» у нас будет следующее действие на контроллере (называемом «Home»), на котором мы будем публиковать сообщения:

    [HttpPost]
    public JsonResult Save()
    {
        //Handle the post data...
    
        return Json(
            new
            {
                IsSuccess = true
            });
    }
  3. Затем из «caller.com» отправьте данные из формы (с html id «formId») на «server.com» следующим образом:

    $.ajax({
            type: "POST",
            url: "http://www.server.com/home/save",
            dataType: 'json',
            crossDomain: true,
            data: $(formId).serialize(),
            success: function (jsonResult) {
               //do what ever with the reply
            },
            error: function (jqXHR, textStatus) {
                //handle error
            }
        });

4

Есть еще один способ (использование функции html5). Вы можете использовать прокси-сервер iframe, размещенный в этом другом домене, вы отправляете сообщение с помощью postMessage этому iframe, затем этот iframe может выполнить запрос POST (в том же домене) и postMessage обратно с reposnse в родительское окно.

родитель на sender.com

var win = $('iframe')[0].contentWindow

function get(event) {
    if (event.origin === "http://reciver.com") {
        // event.data is response from POST
    }
}

if (window.addEventListener){
    addEventListener("message", get, false)
} else {
    attachEvent("onmessage", get)
}
win.postMessage(JSON.stringify({url: "URL", data: {}}),"http://reciver.com");

iframe на reciver.com

function listener(event) {
    if (event.origin === "http://sender.com") {
        var data = JSON.parse(event.data);
        $.post(data.url, data.data, function(reponse) {
            window.parent.postMessage(reponse, "*");
        });
    }
}
// don't know if we can use jQuery here
if (window.addEventListener){
    addEventListener("message", listener, false)
} else {
    attachEvent("onmessage", listener)
}

Есть связанный вопрос в stackoverflow.com/questions/38940932/… . Можно ли создать какой-нибудь плагин или универсальную функцию на основе вашего образца?
Андрус

@Andrus может быть что-то вроде этого gist.github.com/jcubic/26f806800abae0db9a0dfccd88cf6f3c
jcubic

Этот код требует изменения страницы получателя. Как прочитать ответ, если страницы получателя не могут быть изменены?
Андрус

@Andrus вы не можете иметь доступ к iframe recever.com для отправки туда запросов ajax. Без iframe не будет запросов.
Июнь

3

Высокий уровень .... Вам нужно настроить cname на своем сервере, чтобы other-serve.your-server.com указывал на other-server.com.

Ваша страница динамически создает невидимый iframe, который служит вашим транспортом на other-server.com. Затем вам нужно связаться через JS со своей страницы на other-server.com и получить обратные вызовы, которые возвращают данные обратно на вашу страницу.

Возможно, но требует координации от your-server.com и other-server.com


Даже не думал об использовании CNAME для перенаправления. Хороший звонок! Я еще не попробовал это, но я предполагаю, что CNAME заставит браузер думать, что он взаимодействует с тем же сайтом? Я собираюсь использовать его для публикации в Amazon S3, поэтому надеюсь, что это сработает.
СпенсерЭллиотт

1
Я не вижу, как это решит что-нибудь. переход на другой поддомен имеет те же проблемы, что и переход на другой домен.
Осьминог


2

Это старый вопрос, но некоторые новые технологии могут помочь кому-то.

Если у вас есть административный доступ к другому серверу, вы можете использовать проект Forge с открытым исходным кодом для выполнения вашего междоменного POST. Forge предоставляет междоменную оболочку JavaScript XmlHttpRequest, которая использует API-интерфейсы необработанных сокетов Flash. POST может даже быть сделан по TLS.

Причина, по которой вам нужен административный доступ к серверу, на который вы размещаете POST, заключается в том, что вы должны предоставить междоменную политику, которая разрешает доступ из вашего домена.

http://github.com/digitalbazaar/forge


2

Я знаю, что это старый вопрос, но я хотел поделиться своим подходом. Я использую cURL в качестве прокси, очень легко и последовательно. Создайте страницу php под названием submit.php и добавьте следующий код:

<?

function post($url, $data) {
$header = array("User-Agent: " . $_SERVER["HTTP_USER_AGENT"], "Content-Type: application/x-www-form-urlencoded");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($curl);
curl_close($curl);
return $response;
}

$url = "your cross domain request here";
$data = $_SERVER["QUERY_STRING"];
echo(post($url, $data));

Затем в вашем JS (JQuery здесь):

$.ajax({
type: 'POST',
url: 'submit.php',
crossDomain: true,
data: '{"some":"json"}',
dataType: 'json',
success: function(responseData, textStatus, jqXHR) {
    var value = responseData.someKey;
},
error: function (responseData, textStatus, errorThrown) {
    alert('POST failed.');
}
});

1

Должно быть возможно с пользовательской таблицей YQL + JS XHR, взгляните на: http://developer.yahoo.com/yql/guide/index.html

Я использую его, чтобы выполнить некоторые операции очистки jtml на стороне клиента (js), работает отлично (у меня есть полноценный аудиоплеер, с поиском в интернете / плейлистов / текстов песен / последней информации fm, все клиенты js + YQL)


1

CORS для вас. CORS - это «Обмен ресурсами между источниками», это способ отправки междоменного запроса. Теперь XMLHttpRequest2 и Fetch API поддерживают CORS и могут отправлять как запросы POST, так и GET.

Но у него есть свои пределы. Серверу необходимо отдельно запросить Access-Control-Allow-Origin , и его нельзя установить на «*».

И если вы хотите, чтобы любой источник мог отправить вам запрос, вам нужен JSONP (также необходимо установить Access-Control-Allow-Origin , но может быть '*')

Для большого количества запросов, если вы не знаете, как сделать выбор, я думаю, вам нужен полнофункциональный компонент, чтобы сделать это. Позвольте мне представить простой компонент https://github.com/Joker-Jelly/catta.


Если вы используете современный браузер (> IE9, Chrome, FF, Edge и т. Д.), Очень рекомендую использовать простой, но красивый компонент https://github.com/Joker-Jelly/catta . У него нет зависимости, меньше чем 3 КБ, и он поддерживает Fetch, AJAX и JSONP с тем же синтаксисом и опциями смертельного образца.

catta('./data/simple.json').then(function (res) {
  console.log(res);
});

Он также поддерживает импорт в ваш проект, такой как модуль ES6, CommonJS и даже <script>в HTML.


1

Если у вас есть доступ к междоменному серверу и вы не хотите вносить какие-либо изменения в код на стороне сервера, вы можете использовать библиотеку под названием - 'xdomain'.

Как это работает:

Шаг 1: сервер 1: включите библиотеку xdomain и настройте междоменный домен в качестве ведомого:

<script src="js/xdomain.min.js" slave="https://crossdomain_server/proxy.html"></script>

Шаг 2: на междоменном сервере создайте файл proxy.html и включите сервер 1 в качестве главного:

proxy.html:
<!DOCTYPE HTML>
<script src="js/xdomain.min.js"></script>
<script>
  xdomain.masters({
    "https://server1" : '*'
  });
</script>

Шаг 3:

Теперь вы можете сделать AJAX-вызов proxy.html в качестве конечной точки с сервера server1. Это обход запроса CORS. Библиотека внутренне использует решение iframe, которое работает с учетными данными и всеми возможными методами: GET, POST и т. Д.

Запросите код AJAX:

$.ajax({
        url: 'https://crossdomain_server/proxy.html',
        type: "POST",
        data: JSON.stringify(_data),
        dataType: "json",
        contentType: "application/json; charset=utf-8"
    })
    .done(_success)
    .fail(_failed)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.