HTTP request и response могут содержать так называемое тело (body).
Мы уже знаем, что сам HTTP-запрос состоит из заголовков и опционального тела запроса. Для отделения заголовков от тела существуют определенные правила. Давайте посмотрим на примере, как работать с body и каким образом посылать какие-то данные кроме заголовков. Сделаем HTTP-запрос к хосту http.hexlet.app:
telnet http.hexlet.app 80
Trying 188.114.97.0...
Connected to http.hexlet.app.
Escape character is '^]'.
GET /http-protocol/tasks HTTP/1.1
HOST: http.hexlet.app
HTTP/1.1 200 OK
Date: Wed, 02 Oct 2024 10:46:30 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 1105
Connection: keep-alive
CF-Cache-Status: DYNAMIC
Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=u3sdx2IpksLSErg9j3TWyTFIPfJq9mDJxdKklS7WmFjmjUkVEeH4GCbdKWf1qrbej7RGXz%2BxTuiGFE39qHGMFvtFTU0EOvlh1In2M77JhbDHRtuxbwWkx8NiAQIp%2F8hMykE%3D"}],"group":"cf-nel","max_age":604800}
NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800}
Server: cloudflare
CF-RAY: 8cc41be28d911108-HKG
{"tasks":[{"id":"1","title":"Опубликовать курс по основам JavaScript","description":"Автор подготовил курс по JavaScript. Нужно его опубликовать","status":"Backlog"},{"id":"2","title":"Опубликовать курс по основам Ruby","description":"Автор подготовил курс по Ruby. Нужно его опубликовать","status":"Ready"},{"id":"3","title":"Опубликовать курс по основам PHP","description":"Автор подготовил курс по PHP. Нужно его опубликовать","status":"In Progress"},{"id":"4","title":"Опубликовать курс по основам Java","description":"Автор подготовил курс по Java. Нужно его опубликовать","status":"Done"},{"id":"5","title":"Опубликовать курс по основам Python","description":"Автор подготовил курс по Python. Нужно его опубликовать","status":"Archived"}],"total":5,"skip":0,"limit":30}
В ответ мы получаем какие-то заголовки и далее идет тело, которое нас как раз и интересует. В данном случае это список задач.
Если с заголовками все понятно, они отделяются друг от друга переводом строки и для отправки мы добавляем еще один перевод, который выглядит как пустая строка, то как быть с телом запроса? Оно может содержать внутри все что угодно. Мы не можем кодировать перевод строки как специальный символ. Ведь те самые два перевода строки могут находиться внутри тела запроса. Но существуют и другие причины, по которым в текстовом протоколе нельзя просто так определить когда заканчивается тело. Если бы мы приняли ответ при отсутствии каких-то специальных механизмов, то после того как сервер отправил первые два перевода строки мы сразу увидели бы ответ и все что посылалось дальше вообще не считалось бы частью ответа HTTP response. Для решения этой проблемы был придуман другой, более универсальный механизм. Он основан на передаче специального заголовка.
Во время отправки ответа сервер формирует специальный заголовок, который называется Content-Length. Это и есть ключ к тому как работать с body. Перед тем как отправить тело ответа, происходит вычисление его длины и записывается количество байт.
# число — количество байт
Content-Length: 1105
После того как передан такой заголовок, другая сторона будет ожидать ровно столько байт, сколько в нем указано. Как мы помним, для response и request это работает абсолютно одинаково. После того как был передан последний символ, соединение закрывается. Стоит уточнить, что закрывается именно HTTP-сессия. На сервере может быть активен keep-alive, но ключевой момент в том, что запрос считается завершенным и отображается.
Указание размера тела нужно не только для отправки ответа, но и при запросах, когда на сервер посылаются, например, данные формы.
Практика показывает, что не все серверы правильно работают при наличии только заголовка Content-Length. Им не хватает еще одного. Тип содержимого запроса или ответа, которое содержит body, должен быть как-то идентифицирован. По умолчанию в стандарте сказано, что сервер может сам попытаться определить содержимое контента на основе различных способов. Например, мы в query string делаем запрос tasks.
POST /http-protocol/tasks HTTP/1.1
Совсем не обязательно, но сервер может понять, что это JSON, и как-то это использовать. Во всех остальных случаях, когда сервер не может определить тип контента, он должен использовать заголовок Content-Type: application/octet-stream. Это означает, что в теле запроса передается просто поток байтов. Хотя серверы должны работать именно так, но часто все происходит по-другому. Если указан только Content-Length, то сервер отказывается принимать данные. Он просто закрывает соединение после двух переводов строки, еще до body. Этот нюанс выяснен экспериментальным путем.
Еще одно замечание по поводу body. С точки зрения стандарта HTTP тело может присутствовать в любом запросе и никак не связано с глаголом. Посылать body можно в HEAD, POST, PUT и других запросах. Если мы посылаем body с GET, хотя это не описано в стандарте, сервер никак не будет на это реагировать, более того, он и не должен, так как с практической точки зрения это не имеет смысла. Также есть типы запросов, при которых он не будет посылать в ответ body ни в коем случае. Например, ответ на HEAD, когда мы запрашиваем только заголовки, так как такова семантика этого глагола. Еще тело не отправляется, когда мы получаем в ответ такие статусы как 204 — нет контента и некоторые другие.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.