{ "index": 24, "slug": "editorial-2027-05-practice-http-tls-guide", "title": "HTTP и TLS: как за один запрос найти границу отказа", "excerpt": "Практический маршрут от DNS и TLS к статусу HTTP: как отличить отсутствие маршрута от ошибки сертификата, проверить гипотезу и не сделать опасный retry.", "contentHtml": "
Браузер показывает «сайт недоступен», а инженер сразу меняет timeout, маршрут или сертификат. Через час выясняется, что запрос остановился на другом слое: DNS отдал не тот адрес, TLS отверг имя, reverse proxy вернул 404 или upstream не успел ответить. Исправление симптома в таком месте добавляет rollout и не приближает к причине.
Разберём один вопрос: как по наблюдаемому запросу найти первый подтверждённый этап отказа. До HTTP находятся разрешение имени, TCP и TLS. После успешного TLS клиент отправляет метод, целевой URI и поля HTTP. Статус 404 уже доказывает, что некоторый участник обмена сформировал HTTP-ответ; ошибка проверки сертификата — нет.
Результат диагностики — не формула «для 503 всегда повторяем». Нужна запись, в которой видны вход, первый ответивший слой, проверка и действие. Такой формат помогает отделить исправление маршрута от изменения сетевых лимитов и отдельно решить вопрос безопасности повтора.
Начните с одного конкретного запроса: hostname, порт, метод, путь, время, код клиента и безопасный request ID. Секреты из записи удалите. Значение имеет не фраза из браузера, а то, что реально можно сопоставить с логом или повторить командой.
\n| Наблюдение | Что уже подтверждено | Следующая проверка | Чего не следует заключать |
|---|---|---|---|
| Не разрешается имя | До TCP-соединения дело не дошло | DNS-запись, resolver, адрес и TTL | Что приложение не работает |
| TCP открылся, TLS не завершился | Порт достижим, HTTP ещё не отправлен | hostname, SAN, цепочка и срок сертификата | Что URI или метод неверны |
404 Not Found | Получен HTTP-ответ с кодом 404 | метод, URI, Host, proxy- и application-логи | Что ответил именно origin |
503 Service Unavailable | HTTP-участник сообщил о недоступности | upstream, Retry-After, deadline и request ID | Что повтор безопасен для записи |
| Нет статуса до timeout | Получатель не отдал HTTP-ответ вовремя | connect/read timeout и трасса по прокси | Что причина обязательно в приложении |
Таблица задаёт область поиска, а не готовый диагноз. 404 может создать edge, балансировщик или приложение. Заголовок Server и внешний вид страницы не дают надёжного ответа о владельце. Поэтому к статусу добавляйте путь прохождения запроса: hostname, порт, request ID и доступные логи.
Упрощённая последовательность выглядит так: клиент разрешает имя, устанавливает TCP, проводит TLS-рукопожатие и только затем передаёт HTTP-сообщение. TLS 1.3 описывает защищённое рукопожатие и параметры канала, а HTTP определяет сообщение, метод, URI, поля и статус. Это разные контракты с разными наблюдениями.
\nПроверка сертификата сопоставляет имя назначения с идентичностью сервера. Если URL содержит старый alias, IP вместо DNS-имени или имя другого виртуального хоста, клиент может остановиться до отправки запроса. В этом случае у приложения нет достоверного статуса, тела и заголовков, которые можно было бы исправлять.
\nФлаг --insecure годится только для изолированного эксперимента, когда нужно увидеть, отвечает ли endpoint при отключённой проверке. Он не исправляет цепочку доверия, hostname или конфигурацию сервера. Результат такого эксперимента нельзя принимать за доказательство безопасного рабочего соединения.
404 означает, что отвечающий сервер не нашёл текущего представления целевого ресурса. Причиной может быть опечатка в пути, неправильный Host, отсутствие маршрута на proxy или удалённый endpoint. Сравните ошибочный запрос с известным GET /health, а затем проверьте метод и нормализацию URI.
405 Method Not Allowed — другой сигнал: ресурс распознан, но данный метод для него не разрешён. Поле Allow показывает поддерживаемые методы, если сервер его сформировал. Подмена POST на GET не является исправлением: она меняет семантику операции и может скрыть ошибку клиента.
503 сообщает о временной неспособности обработать запрос. Это может быть перегруженный или недоступный upstream, окно обслуживания либо решение посредника. Retry-After задаёт время или дату, после которых клиенту предлагается повторить запрос, но сам по себе не обещает, что операция безопасна или завершилась без побочного эффекта.
504 означает, что gateway или proxy не получил своевременный ответ от upstream. Увеличение timeout на браузере не доказывает, что upstream стал быстрее. Сопоставьте deadline клиента, proxy и обработчика, а также отметьте, дошёл ли запрос до приложения и мог ли обработчик завершить запись до обрыва ответа.
Ниже — учебный HTTP-сервер на localhost. Он намеренно не моделирует DNS, TLS, proxy и базу данных. Его задача — отделить корректный маршрут от неизвестного URI и показать, что метод является частью контракта. Сохраните код в check-http.mjs и запустите в Node.js с поддержкой глобального fetch.
import { createServer } from 'node:http';\n\nconst server = createServer((request, response) => {\n const route = request.method + ' ' + request.url;\n\n if (route === 'GET /health') {\n response.writeHead(200, { 'content-type': 'text/plain' });\n response.end('ok');\n return;\n }\n\n if (route === 'POST /orders') {\n response.writeHead(503, {\n 'content-type': 'application/json',\n 'retry-after': '10',\n });\n response.end(JSON.stringify({ error: 'upstream_busy' }));\n return;\n }\n\n response.writeHead(404, { 'content-type': 'text/plain' });\n response.end('missing');\n});\n\nserver.listen({ port: 0, host: '127.0.0.1' }, async () => {\n const { port } = server.address();\n\n for (const path of ['/health', '/missing']) {\n const result = await fetch('http://127.0.0.1:' + port + path);\n console.log(path, result.status, await result.text());\n }\n\n const unavailable = await fetch('http://127.0.0.1:' + port + '/orders', {\n method: 'POST',\n });\n console.log('/orders', unavailable.status, unavailable.headers.get('retry-after'));\n server.close();\n});\nОжидаемый вывод содержит /health 200 ok, /missing 404 missing и /orders 503 10. В первом случае совпали метод и путь. Во втором сервер сформировал HTTP-ответ, поэтому TLS здесь вообще не участвует. В третьем клиент получил указание Retry-After: 10, но код не запускает автоматический повтор.
Измените в первом цикле GET на отдельный запрос POST /health. Ответ будет 404, потому что простая модель сервера различает метод и URI. Это маленькая, но полезная проверка: одинаковый путь не означает одинаковый контракт.
Для HTTPS удобнее идти от дешёвого наблюдения к дорогому. Команды ниже — шаблоны: подставьте разрешённый hostname и безопасный endpoint, не копируйте токены и cookies в историю shell.
\n# Адреса, которые вернул локальный DNS-resolver\ndig +short api.example.test\n\n# Полезные детали соединения и заголовки ответа\ncurl --verbose --connect-timeout 3 --max-time 10 --dump-header - --output /dev/null https://api.example.test/health\n\n# Наблюдение TLS с явным SNI; сертификат проверяйте штатным клиентом\nopenssl s_client -connect api.example.test:443 -servername api.example.test -brief </dev/null\ndig показывает ответ выбранного resolver-а, но не маршрут внутри сети. curl --verbose помогает увидеть этап соединения и HTTP-заголовки; его вывод всё равно нужно сопоставить с логами. openssl s_client показывает детали рукопожатия, но набор флагов и текст результата зависят от версии OpenSSL. Ни одна команда не доказывает состояние бизнес-операции.
Если имя не разрешается, остановитесь на DNS и проверьте resolver. Если TCP соединён, но TLS не завершён, сравните hostname в URL с SAN и цепочкой доверия. Если TLS завершён и есть статус, переходите к HTTP-маршруту. Если статус 503 или 504, добавьте в расследование upstream и границы времени, а не только клиентский timeout.
Повтор после сетевого обрыва оставляет неопределённый результат: сервер мог принять запрос, а клиент не успел получить ответ. Для чтения такой повтор часто допустим по смыслу метода, но лимит, deadline и нагрузка всё равно остаются проектными решениями. Для записи одного статуса 503 недостаточно.
HTTP определяет идемпотентность метода как свойство повторного применения к серверу с тем же эффектом, что и однократное применение, если исходный запрос уже был выполнен. Это не означает, что каждый конкретный endpoint безопасен автоматически. POST по умолчанию не получает такой гарантии от протокола. Сервис может добавить ключ идемпотентности и дедупликацию, но это уже его прикладной контракт.
| Ситуация | Риск | Защита |
|---|---|---|
GET вернул 503 с коротким deadline | Лишняя нагрузка и каскад повторов | ограничить число попыток, общий deadline и backoff |
| POST оборвался без ответа | Заказ мог быть создан | ключ операции, дедупликация и проверка результата |
Ответ содержит Retry-After | Задержка интерпретирована неверно | разобрать секунды или дату и не превышать общий deadline |
| 504 от gateway | upstream мог завершить работу после обрыва | сопоставить логи gateway и upstream до повтора |
Без контракта идемпотентности безопасное действие после неопределённого POST — не повторять вслепую. Сначала запросите состояние операции по отдельному идентификатору или передайте результат владельцу сервиса. Клиентская библиотека не может восстановить неизвестный побочный эффект по одному коду ответа.
Allow, Retry-After, тип тела и логи proxy с логами origin.503 и 504 зафиксируйте upstream, таймаут каждого слоя и факт выполнения операции. Один клиентский замер не разделяет эти причины.Эта схема описывает обычный HTTPS-путь с доступным наблюдением клиента. Она не заменяет диагностику mTLS, QUIC/HTTP/3, service mesh, корпоративного proxy, CDN, нестандартного DNS, балансировки по региону или логики авторизации. В таких системах добавьте соответствующие границы и владельцев, сохранив порядок «первый подтверждённый слой → следующая проверка».
\nСтатус, полученный от proxy, не доказывает, что origin получил запрос. Запись в access log не доказывает завершение транзакции в базе. DNS-ответ не доказывает наличие маршрута. Локальный сервер не моделирует сертификаты, распределённые часы, реальную очередь или повторную доставку. Эти выводы требуют собственных трасс, логов и тестового стенда.
\nКоманды могут раскрыть имена хостов и заголовки, поэтому запускайте их только в разрешённом окружении. Не используйте чужие адреса, не отправляйте production-записи в учебный endpoint и не добавляйте Authorization, Cookie или персональные параметры в публичный отчёт.
Расследование можно закрыть, когда для одного запроса записаны входы, первый подтверждённый слой, доказательство и действие. Для ошибки сертификата это детали имени и цепочки; для 404 — метод, URI и владелец ответа; для 503/504 — upstream, временные границы и решение по retry. После изменения воспроизведите только этот сценарий и проверьте, что наблюдение изменилось ожидаемым образом.
Если остаётся только фраза «ошибка исчезла», проверка не закончена. Нужен повторяемый запрос, сопоставленный с логами и контрактом операции. Тогда следующий инженер сможет отличить исправленный маршрут от временно здорового upstream и не вернётся к случайному изменению timeout.
\nAllow и Retry-After, статусов 404, 405, 503 и 504, а также идемпотентности. Стандарт не определяет конфигурацию конкретного proxy или прикладного endpoint.