# теория · шаг 1 из 7
Из чего состоит запрос и ответ
Коротко
HTTP - это текст, который клиент и сервер шлют друг другу по установленному соединению. Правил в нём мало, и nginx настраивают ровно поверх этих правил.
- Запрос и ответ устроены одинаково: строка, заголовки, пустая строка, тело. Пустая строка - не украшение, а признак «заголовки кончились».
- Заголовок - это пара «имя: значение», по одной на строку. Регистр имени не важен, порядок не важен.
- nginx дописывает в ответ то, чего ты не просил:
Server,Date,Content-Length,ETagи ещё несколько. - Одно и то же имя может встретиться дважды, и в переменной nginx такие значения склеиваются через запятую.
- Имя с подчёркиванием nginx по умолчанию выбрасывает молча.
Если четыре части сообщения и правило про подчёркивание знакомы - листай до «Теперь сам».
Сначала ответь сам
Клиент открыл соединение и отправил на сервер вот это, до последнего символа:
GET /f.txt HTTP/1.1
Host: lab.local
Строка запроса на месте, Host на месте, всё написано верно. nginx молчит:
ответа нет ни через секунду, ни через десять.
Чего не хватает?
Пустой строки. Пока она не пришла, сервер считает, что заголовки ещё идут, и
честно ждёт продолжения. Добавь \r\n в конце - и ответ появится немедленно.
Проверено: тот же запрос с пустой строкой отдаёт 200 OK за миллисекунды.
На что это похоже
Это бумажный бланк, который кладут в конверт.
Сверху одна главная строка: что вообще просят сделать. Ниже - поля: от кого, куда, чем оплачено, до какого числа действителен. Полей может быть три, а может быть тридцать, и порядок их не важен - важно, чтобы каждое было на своей строке.
Потом идёт отчёркивание, и оно значит «поля кончились, дальше приложение». Без него принимающий не знает, где заканчивается бланк и начинается вложенный документ, поэтому будет ждать.
А ниже отчёркивания - собственно груз: текст письма, картинка, файл. Он может
быть, а может и не быть: у обычного GET его нет вовсе.
Механизм: четыре части, одинаковые у обеих сторон
Правило
Заголовки кончаются пустой строкой, а не концом данных. Всё, что nginx решает о запросе, он решает по первой строке и заголовкам - то есть до того, как увидит хоть байт тела.
Разбор: живой обмен целиком
Отправляем минимальный корректный запрос и смотрим, что приходит:
printf 'GET /f.txt HTTP/1.1\r\nHost: lab.local\r\nConnection: close\r\n\r\n' | nc lab.local 80
HTTP/1.1 200 OK
Server: nginx/1.31.5
Date: Thu, 03 Sep 2026 16:30:16 GMT
Content-Type: text/plain
Content-Length: 300
Last-Modified: Thu, 03 Sep 2026 15:36:38 GMT
Connection: close
ETag: "6a999406-12c"
Accept-Ranges: bytes
Клиент попросил один файл и не просил ничего больше. nginx дописал восемь
заголовков: назвал себя, поставил время, сказал тип и размер, дал два способа
проверить свежесть (Last-Modified и ETag), сообщил, что умеет отдавать куски
файла, и предупредил, что закроет соединение.
Каждый из них дальше в курсе окажется чьей-нибудь настройкой: Content-Type
несёт MIME-типMIME-типСтрока вида text/html или image/png в заголовке Content-Type. По ней браузер решает, показать страницу, нарисовать картинку или предложить скачать файл. nginx берёт её из таблицы по расширению файла. и берётся из таблицы по расширению файла, ETag собирается из времени и размера файла, Server
убирается директивой server_tokens off (проверено: остаётся просто nginx, без
версии), а Connection зависит от того, кто попросил закрыть соединение.
Разбор: где наивное правило подводит
Регистр и лишние пробелы не значат ничего. hOsT: lab.local работает
так же, как Host: lab.local. И перевод строки одним символом \n вместо
пары \r\n nginx тоже принимает, хотя стандарт требует пару: серверы
снисходительны к кривым клиентам, и на этом строится половина несовместимостей
между реализациями.
Одно имя может встретиться дважды. Клиент вправе прислать X-Dup: a и
X-Dup: b двумя строками. Бэкенду nginx передаст обе, а вот в своей переменной
склеит через запятую:
dup=[a, b]
Значит проверка вида «если заголовок равен a» на таком запросе не сработает, и
это классический способ обойти наивную проверку в конфиге.
Имя с подчёркиванием исчезает молча. Отправим X-My_Header: v1:
x_my_header=[]
Заголовка нет ни в переменной, ни в том, что уедет бэкенду. Так задумано:
подчёркивание в имени легко путается с дефисом (в переменных nginx они
неразличимы - $http_x_my_header читает и X-My-Header, и X-My_Header), а
через сломанные обёртки такие имена служили способом протащить чужой заголовок.
Включается директивой underscores_in_headers on на уровне server:
underscores_in_headers on;
Проверено: с ней тот же запрос даёт x_my_header=[v1].
Что ломается без этого
Первый способ потерять день - искать причину в приложении, когда её отрезал
nginx. Заголовок X-Api_Key, который «точно отправляется», до бэкенда не
доезжает, и в его логах пусто. Строки в error_log тоже нет: это не ошибка, это
поведение по умолчанию.
Второй - писать в конфиге проверки по заголовку, не помня, что клиент управляет и его регистром, и его количеством.
Зачем это в работе
Смотреть обмен целиком удобнее всего самим curl:
curl -v https://shop.local/api/cart
Строки со > - то, что ушло; со < - то, что пришло. Это первое, что просят
показать при разборе любого «у нас не работает», и по этим двум блокам видно
сразу: дошёл ли Host, какой код вернулся, кто поставил Content-Type.
Отдельно полезен запрос только за заголовками:
curl -sI https://shop.local/app.js
Метод HEAD просит сервер ответить так же, как на GET, но без тела. Проверено:
Content-Length: 300 в ответе есть, а байтов приходит ноль. То есть узнать
размер и тип файла можно, не качая его.
Теперь сам
Клиент отправляет заголовок X-Request_Source: mobile, приложение его не видит.
В error_log пусто, в access_log код 200. Где искать?
Ни в приложении, ни в логах - искать нечего, строки об этом не будет. Имя
содержит подчёркивание, и nginx выбросил заголовок ещё на разборе запроса. Два
выхода: попросить клиента переименовать его в X-Request-Source (это правильный
путь - дефис привычнее и не зависит от настроек сервера) либо поставить
underscores_in_headers on в нужном server.
Главное
Запрос и ответ - это текст из четырёх частей: строка, заголовки, пустая строка,
тело. Пустая строка означает «заголовки кончились», и без неё сервер ждёт.
Заголовок - пара «имя: значение»; регистр имени и порядок не важны, одно имя
может повториться, и в переменной nginx такие значения склеиваются запятой. В
ответ nginx кладёт восемь заголовков от себя, включая Server, Content-Length
и ETag. Имя с подчёркиванием отбрасывается молча - лечится
underscores_in_headers on.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий