Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете
Протокол HTTP

Тело HTTP-запроса

HTTP request и response могут содержать так называемое тело (body).

Структура и тело запроса

Мы уже знаем, что сам HTTP запрос состоит из заголовков и опционального тела запроса. Для отделения заголовков от тела существуют определенные правила. Давайте посмотрим на примере, как работать с body и каким образом посылать какие-то данные кроме заголовков. Сделаем HTTP запрос к хосту hexlet.io:

$ telnet hexlet.io 80
GET / HTTP/1.1
Host: hexlet.io

HTTP/1.1 301 Moved Permanently
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Referrer-Policy: no-referrer
Location: https://34.102.241.4/
Content-Length: 218
Date: Tue, 07 Jul 2020 03:50:16 GMT

<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://34.102.241.4/">here</A>.
</BODY></HTML>

В ответ мы получаем какие-то заголовки и далее идёт тело, которое нас как раз и интересует. В данном случае это не какая-то страница нашего сайта, а просто страница которую отдаёт сервер. Она связана с перенаправлением.

Если с заголовками всё понятно, они отделяются друг от друга переводом строки и для отправки мы добавляем еще один перевод, который выглядит как пустая строка. То как быть с телом запроса? Оно может содержать внутри всё что угодно. Мы не можем кодировать перевод строки как специальный символ. Ведь те самые два перевода строки могут находиться внутри тела запроса. Но существуют и другие причины по которым в текстовом протоколе нельзя просто так определить когда заканчивается тело. Если бы мы приняли ответ при отсутствии каких-то специальных механизмов, то после того как сервер отправил первые два перевода строки мы сразу увидели бы ответ и всё что посылалось дальше вообще не считалось бы частью ответа HTTP response. Для решения этой проблемы был придуман другой, более универсальный механизм. Он основан на передаче специального заголовка.

Во время отправки ответа сервер формирует специальный заголовок, который называется Content-Length. Это и есть ключ к тому как работать с body. Причём не важно получаем ли мы или посылаем данные. Перед тем как отправить тело запроса, происходит вычисление его длины и записывается количество байт.

# число — количество байт
Content-Length: 218

После того, как передан такой заголовок другая сторона будет ожидать ровно столько байт, сколько в нём указано. Как мы помним, для response и request это работает абсолютно одинаково. После того как был передан последний символ, соединение закрывается. Стоит уточнить, что закрывается именно HTTP-сессия. На сервере может быть активен keep-alive, но ключевой момент в том, что запрос считается завершённым и отображается.

Но не смотря на наличие стандарта, практика показывает, что не все серверы правильно работают при наличии только заголовка Content-Length. Им не хватает еще одного. Тип содержимого запроса или ответа, которое содержит body должно быть как-то идентифицировано. По умолчанию в стандарте сказано, что сервер может сам попытаться определить содержимое контента на основе различных способов. Например, мы в query string делаем запрос image.png.

POST /image.png HTTP/1.1

Совсем не обязательно, но сервер может понять, что эта картинка в формате png и как-то это использовать. Во всех остальных случаях, когда он не может определить тип контента он должен использовать заголовок Content-Type: application/octet-stream. Это означает, что в теле запроса передаётся просто поток байт. Хотя серверы должны работать именно так, но часто всё происходит по другому. Указан только Content-Length и сервер отказывается принимать данные. Просто закрывает соединение после двух переводов строки, еще до body. Этот нюанс выяснен экспериментальным путём.

Давайте сейчас попробуем не указывать Content-Type и посмотрим, что будет. Сделаем запрос к нашему локальному серверу, который находится в контейнере. Внутри хоста, который я создал, работает сервер Node.js и фреймворк Express. Делаем запрос без указания Content-Type:

$ telnet localhost 8080
POST / HTTP/1.1
Host: hexlet.local
Content-Length: 28
Connection: close

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 2
ETag: W/"2-vyGp6PvFo4RvsFtPoIWeCReyIC8"
Date: Thu, 09 Jul 2020 03:14:02 GMT
Connection: close

Connection closed by foreign host.

Как видим, сервер сразу закрывает соединение, хотя не должен, так как мы указали заголовок Content-Length.

Теперь попробуем сделать запрос еще раз, только теперь добавим Content-Type. Укажем тип данных text/plain, что в общем-то не совсем правда, так как мы передаём другой тип контента, но давайте считать что это просто какая-то непрерывная строка, без разделителей.

POST / HTTP/1.1
Host: hexlet.local
Content-Type: text/plain
Content-Length: 28
Connection: close

login=user&password=12345678 # отправляем данные
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 28
ETag: W/"1c-nN+JIy/AUI6NXluIMlVAfghl9f8"
Date: Thu, 09 Jul 2020 03:15:54 GMT
Connection: close

login=user&password=12345678 # сервер отправляет обратно переданное тело
Connection closed by foreign host.

Теперь видно, что сервер ожидает от нас данные. Мы решили проблему преждевременного закрытия соединения добавлением заголовка Content-Type. Если мы отправим данные, то в ответ получим status line HTTP/1.1 200 OK. В данном случае сервер настроен на возврат того, что ему отправили и мы получили обратно наш body. Content-Type в ответе изменился на text/html, в данном случае это не важно, так как мы передавали text/plain. Content-Length остался неизменным, что очевидно. Вывод — всегда добавляйте заголовок Content-Type во избежание непредсказуемого поведения сервера. Существует очень много различных типов данных на все случаи жизни. Подробнее о них можно почитать в документации.

Еще одно замечание по поводу body. С точки зрения стандарта HTTP тело может присутствовать в любом запросе и никак не связано с методом. Посылать body можно в HEAD, POST, PUT и других запросах. Если мы посылаем body с GET, хотя это не описано в стандарте, сервер никак не будет на это реагировать, более того он и не должен, так как с практической точки зрения это не имеет смысла. Также есть типы запросов при которых он не будет посылать в ответ body ни в коем случае. Например, ответ на HEAD, когда мы запрашиваем только заголовки, так как такова семантика этого метода. Еще тело не отправляется, когда мы получаем в ответ такие статусы как 204 — нет контента и некоторые другие.


Дополнительные материалы

  1. Википедия / Тело сообщения
  2. Формат HTTP-запросов
  3. Формат HTTP-ответов

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».