Здравствуйте! В этом уроке рассмотрим, что происходит при отправке данных формы методом POST.
Во время отправки формы браузер собирает значения всех полей и делает из них строку, а после составляет тело GET/POST-запроса для отсылки на сервер.
А при отправке данных через XMLHttpRequest, это нужно делать самостоятельно, в JS-коде. Большинство проблем и вопросов возникает как раз с кодировкой. Вот именно об этих вопросах мы и поговорим в этой статье.
Основной способ кодирования запросов – это urlencoded, то есть – стандартное кодирование URL.
Вот пример имеется форма:
<form action="/submit" method="GET"> <input name="name" value="Alex"> <input name="surname" value="Alexeev"> </form>
Здесь имеются 2 поля: name=Ivan и surname=Ivanov.
Браузер перечисляет такие пары «имя=значение» через символ амперсанда & и, так как используется метод GET, итоговый запрос выглядит как /submit?name=Alex&surname=Alexeev.
Все символы, кроме английских букв, цифр и — _ . ! ~ * ‘ ( ) заменяются на их цифровой код в UTF-8 со знаком %.
Например, пробел заменяется на %20, символ / на %2F, русские буквы кодируются двумя байтами в UTF-8, поэтому, к примеру, буква Ц заменится на %D0%A6.
Например:
<form action="/submit" method="GET"> <input name="name" value="Виктор"> <input name="surname" value="Цой"> </form>
Будет отправлена так: /submit?name=%D0%92%D0%B8%D0%BA%D1%82%D0%BE%D1%80&surname=%D0%A6%D0%BE%D0%B9.
в JavaScript имеется функция encodeURIComponent для получения такой кодировки «вручную»:
alert( encodeURIComponent(' ') ); // %20 alert( encodeURIComponent('/') ); // %2F alert( encodeURIComponent('В') ); // %D0%92 alert( encodeURIComponent('Виктор') ); // %D0%92%D0%B8%D0%BA%D1%82%D0%BE%D1%80
Эта кодировка используется в основном для метода GET, то есть для передачи параметра в строке запроса. По стандарту строка запроса не может содержать произвольные Unicode-символы.
Формируя XMLHttpRequest, надо формировать запрос «руками», кодируя поля функцией encodeURIComponent.
Например, для посылки GET-запроса с параметрами name и surname, аналогично форме выше, их необходимо закодировать так:
// Передаём name и surname в параметрах запроса var xhr = new XMLHttpRequest(); var params = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname); xhr.open("GET", '/submit?' + params, true); xhr.onreadystatechange = ...; xhr.send();
Браузер автоматически добавит к запросу нужные HTTP-заголовки, такие как Content-Length и Connection.
По спецификации браузер запрещает их явную установку, как и некоторых других низкоуровневых HTTP-заголовков, которые могли бы ввести в заблуждение сервер относительно того, кто и сколько данных ему прислал, например Referer. Это делается в целях контроля правильности запроса и для безопасности.
Запрос, отправленный кодом выше через XMLHttpRequest, никак не отличается от обычной отправки формы. Сервер не в состоянии их отличить.
Поэтому в некоторых фреймворках, чтобы сказать серверу, что это AJAX, добавляют специальный заголовок:
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
В методе POST параметры передаются не в URL, а в теле запроса. Оно указывается в вызове метода send(body).
В стандартных HTTP-формах для метода POST доступны 3 кодировки, которые задаются через атрибут enctype:
В зависимости от типа enctype браузер кодирует данные соответствующим способом перед отправкой их на сервер.
В случае с XMLHttpRequest мы, вообще говоря, и не обязаны использовать ни один из этих способов. Главное, чтобы сервер наш запрос понял. Но обычно проще всего выбрать какой-то из стандартных.
В частности, при POST обязателен заголовок Content-Type, содержащий кодировку. Это указание для сервера – как обрабатывать (раскодировать) пришедший запрос.
Для примера отправим давайте запрос в кодировке application/x-www-form-urlencoded:
var xhr = new XMLHttpRequest(); var body = 'name=' + encodeURIComponent(name) + '&surname=' + encodeURIComponent(surname); xhr.open("POST", '/submit', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.onreadystatechange = ...; xhr.send(body);
Всегда используется только кодировка UTF-8, независимо от языка и кодировки страницы.
Если сервер вдруг ожидает данные в другой кодировке, к примеру windows-1251, то их нужно будет перекодировать.
Кодировка 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 можно и через 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 = ['\r\n']; for (var key in data) { // добавление поля body.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n' + data[key] + '\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 (пустая строка) содержимое файла
Современные браузеры, исключая IE9- (впрочем, есть полифилл), поддерживают встроенный объект FormData, который может кодировать формы для отправки на сервер.
Это бывает очень удобно. Например:
<form name="person"> <input name="name" value="Виктор"> <input name="surname" value="Цой"> </form> <script> // создать объект для формы var formData = new FormData(document.forms.person); // добавить к пересылке ещё пару ключ - значение formData.append("patronym", "Робертович"); // отослать var xhr = new XMLHttpRequest(); xhr.open("POST", "/url"); xhr.send(formData); </script>
Этот код отправит на сервер форму с полями name, surname и patronym.
Интерфейс:
Объект formData можно сразу отсылать, интеграция FormData с XMLHttpRequest встроена в браузер. Кодировка при этом будет multipart/form-data.
XMLHttpRequest сам по себе не ограничивает кодировку и формат пересылаемых данных.
Поэтому для обмена данными часто используется формат JSON:
var xhr = new XMLHttpRequest(); var json = JSON.stringify({ name: "Виктор", surname: "Цой" }); xhr.open("POST", '/submit', true) xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); xhr.onreadystatechange = ...; // Отсылаем объект в формате JSON и с Content-Type application/json // Сервер должен уметь такой Content-Type принимать и раскодировать xhr.send(json);
В XMLHttpRequest вы можете использовать и другие HTTP-методы, как PUT, DELETE, TRACE.
Об авторе