Как устроен заголовок HTTP/1.1 в текстовом формате
Что проверяют
Знаете ли вы формат сообщения на уровне байтов: где кончаются заголовки и откуда парсер берёт длину тела. Для Go-разработчика следом идёт вопрос, как это видит net/http и почему r.Header["content-type"] возвращает пустоту.
Как отвечать
Сообщение HTTP/1.1 это текст, строки которого разделены парой \r\n (CRLF). Актуальная спецификация: RFC 9112.
- стартовая строка: у запроса
GET /index.html HTTP/1.1, то есть метод, цель и версия через пробел. У ответаHTTP/1.1 200 OK: версия, код и поясняющая фраза; - поля: по одному на строку в виде
Name: value. Имена регистронезависимы, пробелы вокруг значения отбрасываются, перенос значения на новую строку устарел, генерировать его нельзя; - пустая строка: одна строка из CRLF отделяет заголовки от тела;
- тело: длину задаёт
Content-LengthилиTransfer-Encoding: chunked. Если пришли оба, это признак попытки request smuggling; - обязательное: в HTTP/1.1 обязателен только
Host, без него сервер отвечает 400.
Как это выглядит в Go:
http.Header: этоmap[string][]stringс ключами в канонической форме (Content-Type) черезtextproto.CanonicalMIMEHeaderKey.GetиSetприводят имя сами, прямое обращение к карте нет;r.Host: на сервереHostпереезжает из заголовков в это поле;- лимиты: размер заголовков ограничивает
Server.MaxHeaderBytes, по умолчанию 1 МБ, аReadHeaderTimeoutзащищает от медленной отправки заголовков; - отладка: сырой запрос удобно посмотреть через
httputil.DumpRequest.
Где спотыкаются
Говорят, что заголовки разделены пустыми строками. Пустая строка одна, и она означает конец заголовков.
Называют User-Agent обязательным. Клиенты шлют его по привычке, но стандарт требует только Host.
Путают текстовый формат с HTTP/2. Там бинарные фреймы, имена полей в нижнем регистре, сжатие HPACK, а стартовую строку заменяют псевдозаголовки :method и :path.