Javascript метод xmlhttprequest.send()
Содержание:
- JS Учебник
- Запросы от имени пользователя
- Свойства объекта XMLHttpRequest
- Заголовки ответа
- Основы
- Зачем нужен CORS? Экскурс в историю
- Introduction
- Пример 2. Обработка синхронного AJAX запроса на сервере с помощью PHP
- FAQ
- Методы объекта XMLHttpRequest
- The basics
- Практические примеры
- Устранение неполадок XHR-файлов
- §Brief History of XHR
- Определение и применение
- Простые запросы
- §XHR Use Cases and Performance
- Кодировка multipart/form-data
- POST с multipart/form-data
- Как это устроено
- Итоги
- Итоги
- Итого
JS Учебник
JS ГлавнаяJS ВведениеJS Что? Где? Куда?JS ВыводJS ЗаявленияJS СинтаксисJS КомментарииJS ПеременныеJS ОператорыJS АрифметикаJS ПрисваиванияJS Типы данныхJS ФункцииJS ОбъектыJS СобытияJS СтрокиJS Методы строкJS ЧислаJS Методы чиселJS МассивыJS Методы массиваJS Сортировка массиваJS Итерация массиваJS Объекты датJS Формат датJS Метод получения датJS Метод набора датJS Математические…JS Случайные числаJS БулевыJS Сравнение…JS Заявления if…elseJS Заявление switchJS Цикл forJS Цикл whileJS Заявление break…JS Преобразование…JS Битовые…JS Регулярные выраженияJS ОшибкиJS ОбластьJS ПодъемныйJS СтрогийJS Ключевое слово thisJS Ключевое слово letJS КонстантыJS Функция стрелкиJS КлассыJS ОтладчикJS Руководство стиляJS ПрактикаJS Распространенные ошибкиJS ЭффективностьJS Зарезервированные словаJS ВерсииJS Версия ES5JS Версия ES6JS Версия 2016JS Версия 2017JS JSON
Запросы от имени пользователя
По умолчанию браузер не передаёт с запросом куки и авторизующие заголовки.
Чтобы браузер передал вместе с запросом куки и HTTP-авторизацию, нужно поставить запросу :
Далее – всё как обычно, дополнительных действий со стороны клиента не требуется.
Такой с куками, естественно, требует от сервера больше разрешений, чем «анонимный».
Поэтому для запросов с предусмотрено дополнительное подтверждение со стороны сервера.
При запросе с сервер должен вернуть уже не один, а два заголовка:
Пример заголовков:
Использование звёздочки в при этом запрещено.
Если этих заголовков не будет, то браузер не даст JavaScript’у доступ к ответу сервера.
Свойства объекта XMLHttpRequest
Ссылается на функцию-обработчик состояний запроса. В некоторых браузерах функция имеет аргумент — событие. Не используйте его, он совершенно лишний.
responseText
Текст ответа сервера. Полный текст есть только при readyState=4, ряд браузеров дают доступ к полученной части ответа сервера при readyState=3.
responseXML
Ответ сервера в виде XML, при readyState=4.
Это свойство хранит объект типа XML document, с которым можно обращаться так же, как с обычным document. Например,
var authorElem = xmlhttp.responseXML.getElementById('author');
Чтобы браузер распарсил ответ сервера в свойство responseXML, в ответе должен быть заголовок Content-Type: text/xml.
Иначе свойство responseXML будет равно null.
status
Для HTTP-запросов — статусный код ответа сервера: 200 — OK, 404 — Not Found, и т.п. Браузер Internet Explorer может также присвоить status код ошибки WinInet,
например 12029 для ошибки «cannot connect».
Запросы по протоколам FTP, FILE:// не возвращают статуса, поэтому нормальным для них является status=0.
Заголовки ответа
Чтобы JavaScript мог прочитать HTTP-заголовок ответа, сервер должен указать его имя в Access-Control-Expose-Headers.
Например:
HTTP/1.1 200 OK Content-Type:text/html; charset=utf-8 Access-Control-Allow-Origin: http://site.ru X-Uid: 123 X-Authorization: 2c9de507f2c54aa1 Access-Control-Expose-Headers: X-Uid, X-Authentication
По умолчанию скрипт может прочитать из ответа только «простые» заголовки:
Cache-Control Content-Language Content-Type Expires Last-Modified Pragma
То есть, Content-Type получить всегда можно, а доступ к специфическим заголовкам нужно открывать явно.
Основы
XMLHttpRequest имеет два режима работы: синхронный и асинхронный.
Сначала рассмотрим асинхронный, так как в большинстве случаев используется именно он.
Чтобы сделать запрос, нам нужно выполнить три шага:
-
Создать .
-
Инициализировать его.
Этот метод обычно вызывается сразу после . В него передаются основные параметры запроса:
- – HTTP-метод. Обычно это или .
- – URL, куда отправляется запрос: строка, может быть и объект URL.
- – если указать , тогда запрос будет выполнен синхронно, это мы рассмотрим чуть позже.
- , – логин и пароль для базовой HTTP-авторизации (если требуется).
Заметим, что вызов , вопреки своему названию, не открывает соединение. Он лишь конфигурирует запрос, но непосредственно отсылается запрос только лишь после вызова .
-
Послать запрос.
Этот метод устанавливает соединение и отсылает запрос к серверу. Необязательный параметр содержит тело запроса.
Некоторые типы запросов, такие как , не имеют тела. А некоторые, как, например, , используют , чтобы отправлять данные на сервер. Мы позже увидим примеры.
-
Слушать события на , чтобы получить ответ.
Три наиболее используемых события:
- – происходит, когда получен какой-либо ответ, включая ответы с HTTP-ошибкой, например 404.
- – когда запрос не может быть выполнен, например, нет соединения или невалидный URL.
- – происходит периодически во время загрузки ответа, сообщает о прогрессе.
Вот полный пример. Код ниже загружает с сервера и сообщает о прогрессе:
После ответа сервера мы можем получить результат запроса в следующих свойствах :
- Код состояния HTTP (число): , , и так далее, может быть в случае, если ошибка не связана с HTTP.
- Сообщение о состоянии ответа HTTP (строка): обычно для , для , для , и так далее.
- (в старом коде может встречаться как )
- Тело ответа сервера.
Мы можем также указать таймаут – промежуток времени, который мы готовы ждать ответ:
Если запрос не успевает выполниться в установленное время, то он прерывается, и происходит событие .
URL с параметрами
Чтобы добавить к URL параметры, вида , и корректно закодировать их, можно использовать объект URL:
Зачем нужен CORS? Экскурс в историю
CORS существует для защиты интернета от злых хакеров.
Серьёзно. Давайте сделаем краткое историческое отступление.
Многие годы скрипт с одного сайта не мог получить доступ к содержимому другого сайта.
Это простое, но могучее правило было основой интернет-безопасности. Например, хакерский скрипт с сайта не мог получить доступ к почтовому ящику пользователя на сайте . И люди чувствовали себя спокойно.
В то время в JavaScript не было методов для сетевых запросов. Это был «игрушечный» язык для украшения веб-страниц.
Но веб-разработчики жаждали большей власти. Чтобы обойти этот запрет и всё же получать данные с других сайтов, были придуманы разные хитрости.
Одним из способов общения с другим сервером была отправка туда формы . Люди отправляли её в , чтобы оставаться на текущей странице, вот так:
Таким способом было возможно сделать GET/POST запрос к другому сайту даже без сетевых методов, так как формы можно отправлять куда угодно. Но так как запрещено получать доступ к содержимому с другого сайта, прочитать ответ было невозможно.
Если быть точным, были трюки и для этого, требующие специального кода на странице и в ифрейме, так что общение с ифреймом было технически возможно. Сейчас мы не будем вдаваться в подробности, пусть эти динозавры покоятся в мире.
Ещё один трюк заключался в использовании тега . У него может быть любой , с любым доменом, например . Это даёт возможность загрузить и выполнить скрипт откуда угодно.
Если сайт, например , хотел предоставить данные для такого доступа, он предоставлял так называемый «протокол JSONP» (JSON with Padding)».
Вот как он работал.
Например, нам на нашем сайте нужны данные с сайта , скажем, погода:
-
Сначала, заранее, объявляем глобальную функцию для обработки данных, например .
-
Затем создаём тег с , при этом имя нашей функции – в URL-параметре .
-
Удалённый сервер с должен в ответ сгенерировать скрипт, который вызывает с данными, которые хочет передать.
-
Когда этот скрипт загрузится и выполнится, наша функция получает данные.
Это работает и не нарушает безопасность, потому что обе стороны согласились передавать данные таким образом. А когда обе стороны согласны, то это определённо не хак. Всё ещё существуют сервисы, которые предоставляют такой доступ, так как это работает даже для очень старых браузеров.
Спустя некоторое время в браузерном JavaScript появились методы для сетевых запросов.
Вначале запросы на другой источник были запрещены. Но в результате долгих дискуссий было решено разрешить их делать, но для использования новых возможностей требовать разрешение сервера, выраженное в специальных заголовках.
Introduction
XMLHttpRequest (XHR) is a browser-level API that enables the client to
script data transfers via JavaScript. XHR made its first debut in
Internet Explorer 5, became one of the key technologies behind the
Asynchronous JavaScript and XML (AJAX) revolution, and is now a
fundamental building block of nearly every modern web application.
Prior to XHR, the web page had to be refreshed to send or fetch any
state updates between the client and server. With XHR, this workflow
could be done asynchronously and under full control of the application
JavaScript code. XHR is what enabled us to make the leap from building
pages to building interactive web applications in the browser.
However, the power of XHR is not only that it enabled asynchronous
communication within the browser, but also that it made it simple. XHR is
an application API provided by the browser, which is to say that the
browser automatically takes care of all the low-level connection
management, protocol negotiation, formatting of HTTP requests, and much
more:
-
The browser manages connection establishment, pooling, and
termination. -
The browser determines the best HTTP(S) transport (HTTP/1.0, 1.1,
2). -
The browser handles HTTP caching, redirects, and content-type
negotiation. -
The browser enforces security, authentication, and privacy
constraints. -
And more…
Free from worrying about all the low-level details, our applications
can focus on the business logic of initiating requests, managing their
progress, and processing returned data from the server. The combination
of a simple API and its ubiquitous availability across all the browsers
makes XHR the «Swiss Army knife» of networking in the browser.
As a result, nearly every networking use case (scripted downloads,
uploads, streaming, and even real-time notifications) can and have been
built on top of XHR. Of course, this doesn’t mean that XHR is the most
efficient transport in each case—in fact, as we will see, far from it—but
it is nonetheless often used as a fallback transport for older clients,
which may not have access to newer browser networking APIs. With that in
mind, let’s take a closer look at the latest capabilities of XHR, its use
cases, and performance do’s and don’ts.
Пример 2. Обработка синхронного AJAX запроса на сервере с помощью PHP
Пример, который будет по технологии AJAX передавать серверу запрос, содержащий параметр и отображать ответ на странице.
В данном примере страница будет состоять из 3 кнопок. Первая кнопка будет иметь текст 1, вторая кнопка текст 2 и третья кнопка текст 3. При нажатии на любую из кнопок будет выполняться синхронный запрос на сервер. В качестве метода передачи запроса будем использовать GET. А адрес, по которому будем посылать запрос и параметры . Получать данные отправленные клиентом на сервере будем с помощью GET-переменной HTTP ($_GET). После этого полученные данные будем обрабатывать на сервере, и возвращать клиенту ответ (строку).
<html lang="ru"> <head> <meta charset="utf-8"> <title>JavaScript AJAX</title> <style> span { font-weight: bold; color: red; } </style> </head> <body> <p>Нажмите на одну из кнопок и получите ответ с сервера посредством технологии AJAX.</p> <div style="text-center;"> <button>1</button> <button>2</button> <button>3</button> </div> <p>Ответ (AJAX): <span id="answer"></span></p> <script src="script.js"></script> </body> </html>
// получить все элементы button на странице var buttons = document.getElementsByTagName("button"); // подпишемся на событие click все элементов button for (var i=0; i<buttons.length; i++) { buttons.addEventListener("click",function(){ // создадим объект XMLHttpRequest var request = new XMLHttpRequest(); //настраиваем запрос: GET - метод, ajax.php - URL-адрес по которому будет посылаться запрос, false - синхронный запрос //передавать параметр будем в составе URL request.open('GET','ajax.php?button='+this.textContent+'',false); // отправляем данные на сервер с помощью метода send request.send(); // если статус ответа 200 (OK) то if (request.status==200) { // выведем в элемент, имеющий id="answer", ответ сервера document.getElementById("answer").innerHTML = request.responseText; } } })
<?php // массив fructs $fructs = array("Яблоки","Виноград","Апельсин"); // если в ассоциативном массиве $_GET существует ключ button if (isset($_GET)) { // присвоим переменной $button значение из ассоциативного массива GET соответсвующее ключу button $button = $_GET; // выведем строку (ответ сервера) echo $fructs; } ?>
FAQ
- Why is my server’s JSON response not parsed? I returned the right content-type.
- See — you can set it to on a GET request to tell to parse the response body.
- Without body is returned as-is (a string or when is set and the browser supports it — a result of parsing JSON or XML)
- How do I send an object or array as POST body?
- should be a string. You need to serialize your object before passing to for sending.
- To serialize to JSON you can use
with for convenience — then will do the serialization and set content-type accordingly.
- Where’s stream API? etc.
- Why can’t I send as body by passing it as anymore?
- How do I add an listener?
xhr({...beforeSendfunction(xhrObject){xhrObject.onprogress=function(){}}})
Методы объекта XMLHttpRequest
open()
Варианты вызова:
- open( method, URL )
- open( method, URL, async )
- open( method, URL, async, userName )
- open( method, URL, async, userName, password )
Первый параметр method — HTTP-метод. Как правило, используется GET либо POST, хотя доступны и более экзотические, вроде TRACE/DELETE/PUT и т.п.
URL — адрес запроса. Можно использовать не только HTTP/HTTPS, но и другие протоколы, например FTP и FILE://. При этом есть ограничения безопасности, так называемая
«same origin policy»: запрос со страницы можно отправлять только на тот домен и порт, с которого она пришла.
Ниже это ограничение и способы обхода будут рассмотрены подробнее.
async = true задает асинхронные запросы, эта тема была поднята выше.
userName, password — данные для HTTP-авторизации.
send()
Отсылает запрос. Аргумент — тело запроса. Например, GET-запроса тела нет, поэтому используется , а для POST-запросов тело содержит параметры запроса.
abort()
Вызов этого метода xmlhttp.abort() обрывает текущий запрос.
Здесь есть одно НО для браузера Internet Explorer. Успешный вызов abort() на самом деле может не обрывать соединение,
а оставлять его в подвешенном состоянии на некоторый таймаут (20-30 секунд). Отловить такие повисшие соединения можно через прокси для отладки, например, Fiddler.
У браузера есть лимит: не более 2 одновременных соединений с одним доменом-портом. Т.е, если два соединения уже висят (и отвиснут по таймауту), то третье открыто не
будет, пока одно из них не умрет. Надеюсь, Вы с такой проблемой не столкнетесь. Ее можно обойти использованием кросс-доменных XmlHttpRequest.
setRequestHeader(name, value)
Устанавливает заголовок name запроса со значением value. Если заголовок с таким name уже есть — он заменяется.
Например,
xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
The basics
XMLHttpRequest has two modes of operation: synchronous and asynchronous.
Let’s see the asynchronous first, as it’s used in the majority of cases.
To do the request, we need 3 steps:
-
Create :
The constructor has no arguments.
-
Initialize it, usually right after :
This method specifies the main parameters of the request:
- – HTTP-method. Usually or .
- – the URL to request, a string, can be URL object.
- – if explicitly set to , then the request is synchronous, we’ll cover that a bit later.
- , – login and password for basic HTTP auth (if required).
Please note that call, contrary to its name, does not open the connection. It only configures the request, but the network activity only starts with the call of .
-
Send it out.
This method opens the connection and sends the request to server. The optional parameter contains the request body.
Some request methods like do not have a body. And some of them like use to send the data to the server. We’ll see examples of that later.
-
Listen to events for response.
These three events are the most widely used:
- – when the request is complete (even if HTTP status is like 400 or 500), and the response is fully downloaded.
- – when the request couldn’t be made, e.g. network down or invalid URL.
- – triggers periodically while the response is being downloaded, reports how much has been downloaded.
Here’s a full example. The code below loads the URL at from the server and prints the progress:
Once the server has responded, we can receive the result in the following properties:
- HTTP status code (a number): , , and so on, can be in case of a non-HTTP failure.
- HTTP status message (a string): usually for , for , for and so on.
- (old scripts may use )
- The server response body.
We can also specify a timeout using the corresponding property:
If the request does not succeed within the given time, it gets canceled and event triggers.
URL search parameters
To add parameters to URL, like , and ensure the proper encoding, we can use URL object:
Практические примеры
Загрузка и сохранение файлов в файловой системе HTML5
Предположим, у вас есть галерея изображений и вы хотите сохранить несколько картинок у себя с помощью файловой системы HTML5. Вы можете запросить эти картинки как объекты , создать на основе этих данных объект и записать его с помощью .
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; function onError(e) { console.log('Error', e); } var xhr = new XMLHttpRequest(); xhr.open('GET', '/path/to/image.png', true); xhr.responseType = 'arraybuffer'; xhr.onload = function(e) { window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) { fs.root.getFile('image.png', {create: true}, function(fileEntry) { fileEntry.createWriter(function(writer) { writer.onwrite = function(e) { ... }; writer.onerror = function(e) { ... }; var bb = new BlobBuilder(); bb.append(xhr.response); writer.write(bb.getBlob('image/png')); }, onError); }, onError); }, onError); }; xhr.send();
Обратите внимание: для использования этого кода нужно ознакомиться с условиями в руководстве Знакомство с API файловой системы
Отправка файла по частям
API файлов существенно облегчает отправку больших файлов. Методика такова: крупный файл разбивается на несколько мелких, которые затем отправляются с помощью XHR и собираются обратно на сервере. Примерно так же Gmail быстро отправляет большие прикрепленные файлы. Эта технология также позволяет обойти ограничение Google App Engine: 32 МБ на один HTTP-запрос.
window.BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder; function upload(blobOrFile) { var xhr = new XMLHttpRequest(); xhr.open('POST', '/server', true); xhr.onload = function(e) { ... }; xhr.send(blobOrFile); } document.querySelector('input').addEventListener('change', function(e) { var blob = this.files; const BYTES_PER_CHUNK = 1024 * 1024; // 1MB chunk sizes. const SIZE = blob.size; var start = 0; var end = BYTES_PER_CHUNK; while(start < SIZE) { // Note: blob.slice has changed semantics and been prefixed. See http://goo.gl/U9mE5. if ('mozSlice' in blob) { var chunk = blob.mozSlice(start, end); } else { var chunk = blob.webkitSlice(start, end); } upload(chunk); start = end; end = start + BYTES_PER_CHUNK; } }, false); })();
Ниже приведен код для сборки файла на сервере.
Проверьте, как он работает.
#bytes/chunk:
Устранение неполадок XHR-файлов
Распространенные проблемы открытия XHR
Google Chrome не установлен
При двойном щелчке XHR-файла появится сообщение «%%os%% не удается открыть XHR-файл». Как правило, это происходит в %%os%%, поскольку Google Chrome не установлен на вашем компьютере. Операционная система не может связать документ XHR с Google Chrome, поэтому двойной щелчок по файлу не будет работать.
Совет: Если у вас есть другое программное обеспечение, которое вы знаете, открывает файлы XHR, вы можете выбрать его, нажав кнопку «Показать приложения».
Устаревшая версия Google Chrome
Иногда установленная версия Google Chrome не поддерживает файл XMLHttpRequest. Если у вас установлена неправильная версия Google Chrome, вам потребуется установить правильную версию. Эта проблема в основном возникает, когда файл XMLHttpRequest был создан более новой версией Google Chrome, чем на компьютере.
Совет . Иногда вы можете получить подсказки о правильной версии программы, щелкнув правой кнопкой мыши XHR-файл, выбрав «Свойства» и посмотрев информацию о версии.
В первую очередь проблемы, возникающие при открытии XHR-файлов, связаны с тем, что на вашем компьютере установлена неправильная версия Google Chrome.
В большинстве случаев установка правильной версии Google Chrome решит вашу проблему. В %%os%% могут возникать внешние проблемы, которые вызывают эти ошибки при открытии XHR-файлов. Эти другие проблемы включают (перечислены в порядке от наиболее до наименее распространенных):
§Brief History of XHR
Despite its name, XHR was never intended to be tied to XML
specifically. The XML prefix is a vestige of a decision to ship the first
version of what became known as XHR as part of the MSXML library in
Internet Explorer 5:
Mozilla modeled its own implementation of XHR against Microsoft’s and
exposed it via the XMLHttpRequest interface. Safari, Opera, and other
browsers followed, and XHR became a de facto standard in all major
browsers—hence the name and why it stuck. In fact, the official W3C
Working Draft specification for XHR was only published in 2006, well
after XHR came into widespread use!
However, despite its popularity and key role in the AJAX revolution,
the early versions of XHR provided limited capabilities: text-based-only
data transfers, restricted support for handling uploads, and inability to
handle cross-domain requests. To address these shortcomings, the
«XMLHttpRequest Level 2» draft was published in 2008, which added a
number of new features:
-
Support for request timeouts
-
Support for binary and text-based data transfers
-
Support for application override of media type and encoding of
responses -
Support for monitoring progress events of each request
-
Support for efficient file uploads
-
Support for safe cross-origin requests
In 2011, «XMLHttpRequest Level 2» specification was merged with the
original XMLHttpRequest working draft. Hence, while you will often find
references to XHR version or level 1 and 2, these distinctions are no
longer relevant; today, there is only one, unified XHR specification. In
fact, all the new XHR2 features and capabilities are offered via the same
XMLHttpRequest API: same interface, more features.
Определение и применение
JavaScript свойство responseType объекта XMLHttpRequest возвращает перечисляемое строковое значение, указывающее тип данных, содержащихся в ответе. Допускается устанавливать responseType в определенное значение, при это вы должны убедиться, что сервер действительно отправляет ответ, совместимый с этим форматом. Если сервер возвращает данные, несовместимые с установленным типом ответа, то значение ответа будет равно null.
Если в качестве значения responseType задана пустая строка, то используется значение по умолчанию «text».
Типы возможных ответов представлены ниже:
Значение | Тип данных свойства response |
---|---|
«» | Пустая строка обрабатывается так же как значение «text», которое является значением по умолчанию. |
«arraybuffer» | Тип данных свойства response представляет собой JavaScript объект ArrayBuffer, содержащий двоичные данные. |
«blob» | Тип данных свойства response представляет собой JavaScript объект Blob, содержащий двоичные данные. |
«document» | Тип данных свойства response представляет собой документ HTML или XML, в зависимости от MIME типа полученных данных. |
«json» | Тип данных свойства response представляет собой объект JavaScript, созданный путем анализа содержимого полученных данных как JSON. |
«text» | Тип данных свойства response представляет собой текст в объекте DOMString. Это значение по умолчанию. |
Изменить значение responseType в синхронном XMLHttpRequest можно только в том случае, если запрос принадлежит Worker. Это ограничение частично предназначено для обеспечения того, чтобы синхронные операции не использовались для больших операций, которые блокируют основной поток браузера, и деструктивны для пользовательского интерфейса.
XMLHttpRequest асинхронны по умолчанию, для перехода в синхронный режим, необходимо передать логическое значение false как значение необязательного параметра async при вызове метода open(), который позволяет инициализировать запрос. Большинство современных браузеров полностью упразднили синхронную поддержку XHR в основном потоке. Попытки установить значение responseType в «document» игнорируются в Worker.
Простые запросы
- Простые.
- Все остальные.
Простые запросы будут попроще, поэтому давайте начнём с них.
– это запрос, удовлетворяющий следующим условиям:
- : GET, POST или HEAD
-
– разрешены только:
- ,
- ,
- ,
- со значением , или .
Любой другой запрос считается «непростым». Например, запрос с методом или с HTTP-заголовком не соответствует условиям.
Принципиальное отличие между ними состоит в том, что «простой запрос» может быть сделан через или , без каких-то специальных методов.
Таким образом, даже очень старый сервер должен быть способен принять простой запрос.
В противоположность этому, запросы с нестандартными заголовками или, например, методом нельзя создать таким способом. Долгое время JavaScript не мог делать такие запросы. Поэтому старый сервер может предположить, что такие запросы поступают от привилегированного источника, «просто потому, что веб-страница неспособна их посылать».
Когда мы пытаемся сделать непростой запрос, браузер посылает специальный предварительный запрос («предзапрос», по англ. «preflight»), который спрашивает у сервера – согласен ли он принять такой непростой запрос или нет?
И, если сервер явно не даёт согласие в заголовках, непростой запрос не посылается.
Далее мы разберём конкретные детали.
§XHR Use Cases and Performance
XMLHttpRequest is what enabled us to make the leap from building pages
to building interactive web applications in the browser. First, it
enabled asynchronous communication within the browser, but just as
importantly, it also made the process simple. Dispatching and controlling
a scripted HTTP request takes just a few lines of JavaScript code, and
the browser handles all the rest:
-
Browser formats the HTTP request and parses the response.
-
Browser enforces relevant security (same-origin) policies.
-
Browser handles content negotiation (e.g., gzip).
-
Browser handles request and response caching.
-
Browser handles authentication, redirects, and more…
As such, XHR is a versatile and a high-performance transport for any
transfers that follow the HTTP request-response cycle. Need to fetch a
resource that requires authentication, should be compressed while in
transfer, and should be cached for future lookups? The browser takes care
of all of this and more, allowing us to focus on the application logic!
However, XHR also has its limitations. As we saw, streaming has never
been an official use case in the XHR standard, and the support is
limited: streaming with XHR is neither efficient nor convenient.
Different browsers have different behaviors, and efficient binary
streaming is impossible. In short, XHR is not a good fit for streaming.
Similarly, there is no one best strategy for delivering real-time
updates with XHR. Periodic polling incurs high overhead and message
latency delays. Long-polling delivers low latency but still has the same
per-message overhead; each message is its own HTTP request. To have both
low latency and low overhead, we need XHR streaming!
As a result, while XHR is a popular mechanism for «real-time»
delivery, it may not be the best-performing transport for the job. Modern
browsers support both simpler and more efficient options, such as
Server-Sent Events and WebSocket. Hence, unless you have a specific
reason why XHR polling is required, use them.
Кодировка multipart/form-data
Кодировка urlencoded за счёт замены символов на %код может сильно «раздуть» общий объём пересылаемых данных. Поэтому для пересылки файлов используется другая кодировка: multipart/form-data.
В этой кодировке поля пересылаются одно за другим, через строку-разделитель.
Чтобы использовать этот способ, нужно указать его в атрибуте enctype и метод должен быть POST:
<form action="/submit" method="POST" enctype="multipart/form-data"> <input name="name" value="Виктор"> <input name="surname" value="Цой"> </form>
То есть, поля передаются одно за другим, значения не кодируются, а чтобы было чётко понятно, какое значение где – поля разделены случайно сгенерированной строкой, которую называют «boundary» (англ. граница), в примере выше это RaNdOmDeLiMiTeR:
Сервер видит заголовок Content-Type: multipart/form-data, читает из него границу и раскодирует соответсвенно поля формы.
Такой способ можно использовать в первую очередь при пересылке файлов, так перекодировка мегабайтов через urlencoded существенно загрузила бы браузер. Да и объём данных после неё сильно вырос бы.
Однако, никто не мешает вам использовать эту кодировку всегда для POST запросов. Для GET доступна только urlencoded.
POST с multipart/form-data
Сделать POST-запрос в кодировке multipart/form-data можно и через XMLHttpRequest.
Достаточно указать в заголовке Content-Type кодировку и границу, и далее сформировать тело запроса, удовлетворяющее требованиям кодировки.
Пример кода для того же запроса, что и раньше, теперь в кодировке multipart/form-data:
var data = { name: 'Виктор', surname: 'Цой' }; var boundary = String(Math.random()).slice(2); var boundaryMiddle = '--' + boundary + '\r\n'; var boundaryLast = '--' + boundary + '--\r\n' var body = ; for (var key in data) { // добавление поля body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data + '\r\n'); } body = body.join(boundaryMiddle) + boundaryLast; // Тело запроса готово, отправляем var xhr = new XMLHttpRequest(); xhr.open('POST', '/submit', true); xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); xhr.onreadystatechange = function() { if (this.readyState != 4) return; alert( this.responseText ); } xhr.send(body);
Тело запроса будет иметь вид, описанный выше, то есть поля через разделитель.
Отправка файла
Можно создать запрос, который сервер воспримет как загрузку файла.
Для добавления файла нужно использовать тот же код, что выше, модифицировав заголовки перед полем, которое является файлом, так:
Content-Disposition: form-data; name="myfile"; filename="pic.jpg" Content-Type: image/jpeg (пустая строка) содержимое файла
Как это устроено
Если мы хотим хранить данные на сервере и отправлять их туда в любой момент, нам нужно действовать так:
- Собрать данные в JSON-формат.
- Упаковать их в специальный запрос.
- Встроенными средствами JavaScript отправить этот запрос на сервер по нужному адресу.
- Чтобы наш запрос был принят, по этому адресу на сервере должен находиться скрипт, который умеет работать с такими запросами.
- А чтобы сервер в принципе отвечал на какие-то запросы, нам нужно его этому обучить.
Первые три пункта сделаем на клиенте — нашей HTML-странице, а скрипт и настройки — на сервере. Скрипт будем писать на PHP, поэтому, если не знаете, что это и как с этим работать, — почитайте.
Чтобы было проще, мы отправим и обработаем на сервере совсем маленький JSON — в нём всего две пары «имя: значение», но даже со сложным запросом всё будет работать так же.
Итоги
- Все браузеры умеют делать кросс-доменные XMLHttpRequest.
- Кросс-доменный запрос всегда содержит заголовок Origin с доменом запроса.
Порядок выполнения:
- Для запросов с «непростым» методом или особыми заголовками браузер делает предзапрос OPTIONS, указывая их в Access-Control-Request-Method и Access-Control-Request-Headers. Браузер ожидает ответ со статусом 200, без тела, со списком разрешённых методов и заголовков в Access-Control-Allow-Method и Access-Control-Allow-Headers. Дополнительно можно указать Access-Control-Max-Age для того чтобы предзапрос кешировался.
- Браузер делает запрос и проверяет, есть ли в ответе Access-Control-Allow-Origin, равный * или Origin. Для запросов с withCredentials может быть только Origin и дополнительно Access-Control-Allow-Credentials: true.
- Если все проверки пройдены, то вызывается xhr.onload, иначе xhr.onerror, без деталей ответа.
- Дополнительно: названия нестандартных заголовков ответа сервер должен указать в Access-Control-Expose-Headers, если хочет, чтобы клиент мог их прочитать.
Поделиться
Твитнуть
Поделиться
Итоги
- У форм есть 2 основные кодировки: application/x-www-form-urlencoded и multipart/form-data – для POST запросов, если она явно указана в enctype. Вторая кодировка обычно используется для передачи больших данных и только для тела запроса.
- Для составления запроса в application/x-www-form-urlencoded есть функция encodeURIComponent.
- Для отправки запроса в multipart/form-data – есть объект FormData.
- Для обмена данными клиент сервер можно использовать и просто JSON, желательно с указанием кодировки в заголовке Content-Type.
В XMLHttpRequest вы можете использовать и другие HTTP-методы, как PUT, DELETE, TRACE.
Поделиться
Твитнуть
Поделиться
Итого
- Все современные браузеры умеют делать кросс-доменные XMLHttpRequest.
- В IE8,9 для этого используется объект , ограниченный по возможностям.
- Кросс-доменный запрос всегда содержит заголовок с доменом запроса.
Порядок выполнения:
-
Для запросов с «непростым» методом или особыми заголовками браузер делает предзапрос , указывая их в и .
Браузер ожидает ответ со статусом , без тела, со списком разрешённых методов и заголовков в и . Дополнительно можно указать для кеширования предзапроса.
-
Браузер делает запрос и проверяет, есть ли в ответе , равный или .
Для запросов с может быть только и дополнительно .
-
Если проверки пройдены, то вызывается , иначе , без деталей ответа.
-
Дополнительно: названия нестандартных заголовков ответа сервер должен указать в , если хочет, чтобы клиент мог их прочитать.
Детали и примеры мы разобрали выше.