Files

8 lines
24 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 24,
"slug": "editorial-2027-05-practice-http-tls-guide",
"title": "HTTP и TLS: как за один запрос найти границу отказа",
"excerpt": "Практический маршрут от DNS и TLS к статусу HTTP: как отличить отсутствие маршрута от ошибки сертификата, проверить гипотезу и не сделать опасный retry.",
"contentHtml": "<p>Браузер показывает «сайт недоступен», а инженер сразу меняет timeout, маршрут или сертификат. Через час выясняется, что запрос остановился на другом слое: DNS отдал не тот адрес, TLS отверг имя, reverse proxy вернул <code>404</code> или upstream не успел ответить. Исправление симптома в таком месте добавляет rollout и не приближает к причине.</p>\n<p>Разберём один вопрос: как по наблюдаемому запросу найти первый подтверждённый этап отказа. До HTTP находятся разрешение имени, TCP и TLS. После успешного TLS клиент отправляет метод, целевой URI и поля HTTP. Статус <code>404</code> уже доказывает, что некоторый участник обмена сформировал HTTP-ответ; ошибка проверки сертификата — нет.</p>\n<p>Результат диагностики — не формула «для <code>503</code> всегда повторяем». Нужна запись, в которой видны вход, первый ответивший слой, проверка и действие. Такой формат помогает отделить исправление маршрута от изменения сетевых лимитов и отдельно решить вопрос безопасности повтора.</p>\n<h2>Сначала фиксируем наблюдаемый симптом</h2>\n<p>Начните с одного конкретного запроса: hostname, порт, метод, путь, время, код клиента и безопасный request ID. Секреты из записи удалите. Значение имеет не фраза из браузера, а то, что реально можно сопоставить с логом или повторить командой.</p>\n<table><caption>Симптом связывает проверку с уровнем, но не доказывает конкретный компонент</caption><thead><tr><th scope=\"col\">Наблюдение</th><th scope=\"col\">Что уже подтверждено</th><th scope=\"col\">Следующая проверка</th><th scope=\"col\">Чего не следует заключать</th></tr></thead><tbody><tr><td>Не разрешается имя</td><td>До TCP-соединения дело не дошло</td><td>DNS-запись, resolver, адрес и TTL</td><td>Что приложение не работает</td></tr><tr><td>TCP открылся, TLS не завершился</td><td>Порт достижим, HTTP ещё не отправлен</td><td>hostname, SAN, цепочка и срок сертификата</td><td>Что URI или метод неверны</td></tr><tr><td><code>404 Not Found</code></td><td>Получен HTTP-ответ с кодом 404</td><td>метод, URI, Host, proxy- и application-логи</td><td>Что ответил именно origin</td></tr><tr><td><code>503 Service Unavailable</code></td><td>HTTP-участник сообщил о недоступности</td><td>upstream, <code>Retry-After</code>, deadline и request ID</td><td>Что повтор безопасен для записи</td></tr><tr><td>Нет статуса до timeout</td><td>Получатель не отдал HTTP-ответ вовремя</td><td>connect/read timeout и трасса по прокси</td><td>Что причина обязательно в приложении</td></tr></tbody></table>\n<p>Таблица задаёт область поиска, а не готовый диагноз. <code>404</code> может создать edge, балансировщик или приложение. Заголовок <code>Server</code> и внешний вид страницы не дают надёжного ответа о владельце. Поэтому к статусу добавляйте путь прохождения запроса: hostname, порт, request ID и доступные логи.</p>\n<h2>Где заканчивается TLS и начинается HTTP</h2>\n<p>Упрощённая последовательность выглядит так: клиент разрешает имя, устанавливает TCP, проводит TLS-рукопожатие и только затем передаёт HTTP-сообщение. TLS 1.3 описывает защищённое рукопожатие и параметры канала, а HTTP определяет сообщение, метод, URI, поля и статус. Это разные контракты с разными наблюдениями.</p>\n<figure><img src=\"/assets/editorial/2027/http-tls-guide-2027-handshake-header-map.svg\" alt=\"Схема диагностики: DNS и TCP ведут к TLS с проверкой hostname и SAN, затем к HTTP с методом и статусом\" loading=\"lazy\" /><figcaption>Статус HTTP появляется после успешного прохождения TLS. Фиксируйте первый слой, для которого есть наблюдение.</figcaption></figure>\n<p>Проверка сертификата сопоставляет имя назначения с идентичностью сервера. Если URL содержит старый alias, IP вместо DNS-имени или имя другого виртуального хоста, клиент может остановиться до отправки запроса. В этом случае у приложения нет достоверного статуса, тела и заголовков, которые можно было бы исправлять.</p>\n<p>Флаг <code>--insecure</code> годится только для изолированного эксперимента, когда нужно увидеть, отвечает ли endpoint при отключённой проверке. Он не исправляет цепочку доверия, hostname или конфигурацию сервера. Результат такого эксперимента нельзя принимать за доказательство безопасного рабочего соединения.</p>\n<h2>Как читать 404, 405, 503 и 504</h2>\n<p><code>404</code> означает, что отвечающий сервер не нашёл текущего представления целевого ресурса. Причиной может быть опечатка в пути, неправильный Host, отсутствие маршрута на proxy или удалённый endpoint. Сравните ошибочный запрос с известным <code>GET /health</code>, а затем проверьте метод и нормализацию URI.</p>\n<p><code>405 Method Not Allowed</code> — другой сигнал: ресурс распознан, но данный метод для него не разрешён. Поле <code>Allow</code> показывает поддерживаемые методы, если сервер его сформировал. Подмена <code>POST</code> на <code>GET</code> не является исправлением: она меняет семантику операции и может скрыть ошибку клиента.</p>\n<p><code>503</code> сообщает о временной неспособности обработать запрос. Это может быть перегруженный или недоступный upstream, окно обслуживания либо решение посредника. <code>Retry-After</code> задаёт время или дату, после которых клиенту предлагается повторить запрос, но сам по себе не обещает, что операция безопасна или завершилась без побочного эффекта.</p>\n<p><code>504</code> означает, что gateway или proxy не получил своевременный ответ от upstream. Увеличение timeout на браузере не доказывает, что upstream стал быстрее. Сопоставьте deadline клиента, proxy и обработчика, а также отметьте, дошёл ли запрос до приложения и мог ли обработчик завершить запись до обрыва ответа.</p>\n<h2>Воспроизводимый локальный пример</h2>\n<p>Ниже — учебный HTTP-сервер на localhost. Он намеренно не моделирует DNS, TLS, proxy и базу данных. Его задача — отделить корректный маршрут от неизвестного URI и показать, что метод является частью контракта. Сохраните код в <code>check-http.mjs</code> и запустите в Node.js с поддержкой глобального <code>fetch</code>.</p>\n<pre><code>import { createServer } from 'node:http';\n\nconst server = createServer((request, response) =&gt; {\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 () =&gt; {\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});</code></pre>\n<p>Ожидаемый вывод содержит <code>/health 200 ok</code>, <code>/missing 404 missing</code> и <code>/orders 503 10</code>. В первом случае совпали метод и путь. Во втором сервер сформировал HTTP-ответ, поэтому TLS здесь вообще не участвует. В третьем клиент получил указание <code>Retry-After: 10</code>, но код не запускает автоматический повтор.</p>\n<p>Измените в первом цикле <code>GET</code> на отдельный запрос <code>POST /health</code>. Ответ будет <code>404</code>, потому что простая модель сервера различает метод и URI. Это маленькая, но полезная проверка: одинаковый путь не означает одинаковый контракт.</p>\n<h2>Разделяем проверки командами</h2>\n<p>Для HTTPS удобнее идти от дешёвого наблюдения к дорогому. Команды ниже — шаблоны: подставьте разрешённый hostname и безопасный endpoint, не копируйте токены и cookies в историю shell.</p>\n<pre><code># Адреса, которые вернул локальный 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 &lt;/dev/null</code></pre>\n<p><code>dig</code> показывает ответ выбранного resolver-а, но не маршрут внутри сети. <code>curl --verbose</code> помогает увидеть этап соединения и HTTP-заголовки; его вывод всё равно нужно сопоставить с логами. <code>openssl s_client</code> показывает детали рукопожатия, но набор флагов и текст результата зависят от версии OpenSSL. Ни одна команда не доказывает состояние бизнес-операции.</p>\n<p>Если имя не разрешается, остановитесь на DNS и проверьте resolver. Если TCP соединён, но TLS не завершён, сравните hostname в URL с SAN и цепочкой доверия. Если TLS завершён и есть статус, переходите к HTTP-маршруту. Если статус <code>503</code> или <code>504</code>, добавьте в расследование upstream и границы времени, а не только клиентский timeout.</p>\n<h2>Почему retry требует отдельного решения</h2>\n<p>Повтор после сетевого обрыва оставляет неопределённый результат: сервер мог принять запрос, а клиент не успел получить ответ. Для чтения такой повтор часто допустим по смыслу метода, но лимит, deadline и нагрузка всё равно остаются проектными решениями. Для записи одного статуса <code>503</code> недостаточно.</p>\n<p>HTTP определяет идемпотентность метода как свойство повторного применения к серверу с тем же эффектом, что и однократное применение, если исходный запрос уже был выполнен. Это не означает, что каждый конкретный endpoint безопасен автоматически. <code>POST</code> по умолчанию не получает такой гарантии от протокола. Сервис может добавить ключ идемпотентности и дедупликацию, но это уже его прикладной контракт.</p>\n<table><caption>Решение о повторе принимается по эффекту операции</caption><thead><tr><th scope=\"col\">Ситуация</th><th scope=\"col\">Риск</th><th scope=\"col\">Защита</th></tr></thead><tbody><tr><td>GET вернул <code>503</code> с коротким deadline</td><td>Лишняя нагрузка и каскад повторов</td><td>ограничить число попыток, общий deadline и backoff</td></tr><tr><td>POST оборвался без ответа</td><td>Заказ мог быть создан</td><td>ключ операции, дедупликация и проверка результата</td></tr><tr><td>Ответ содержит <code>Retry-After</code></td><td>Задержка интерпретирована неверно</td><td>разобрать секунды или дату и не превышать общий deadline</td></tr><tr><td>504 от gateway</td><td>upstream мог завершить работу после обрыва</td><td>сопоставить логи gateway и upstream до повтора</td></tr></tbody></table>\n<p>Без контракта идемпотентности безопасное действие после неопределённого <code>POST</code> — не повторять вслепую. Сначала запросите состояние операции по отдельному идентификатору или передайте результат владельцу сервиса. Клиентская библиотека не может восстановить неизвестный побочный эффект по одному коду ответа.</p>\n<h2>Порядок расследования</h2>\n<ol><li>Сохраните точные входы: hostname, порт, метод, нормализованный путь, время, код клиента и безопасный request ID.</li><li>Проверьте DNS отдельно: адрес, resolver, ожидаемый TTL и совпадение окружения. Не переходите к маршруту приложения, пока имя ведёт не туда.</li><li>Проверьте TCP и TLS: доступность порта, hostname, SAN, цепочку, срок действия и SNI. Не отключайте проверку сертификата в рабочем запросе.</li><li>Если появился HTTP-статус, сравните метод, URI, Host, <code>Allow</code>, <code>Retry-After</code>, тип тела и логи proxy с логами origin.</li><li>Для <code>503</code> и <code>504</code> зафиксируйте upstream, таймаут каждого слоя и факт выполнения операции. Один клиентский замер не разделяет эти причины.</li><li>Перед retry классифицируйте эффект: чтение, идемпотентная запись или неопределённая операция. Для последней сначала найдите статус по ключу операции.</li><li>После изменения одного условия повторите тот же запрос и сравните новое наблюдение с исходным. Если изменились одновременно маршрут, timeout и код клиента, причинность не доказана.</li></ol>\n<h2>Ограничения применимости</h2>\n<p>Эта схема описывает обычный HTTPS-путь с доступным наблюдением клиента. Она не заменяет диагностику mTLS, QUIC/HTTP/3, service mesh, корпоративного proxy, CDN, нестандартного DNS, балансировки по региону или логики авторизации. В таких системах добавьте соответствующие границы и владельцев, сохранив порядок «первый подтверждённый слой → следующая проверка».</p>\n<p>Статус, полученный от proxy, не доказывает, что origin получил запрос. Запись в access log не доказывает завершение транзакции в базе. DNS-ответ не доказывает наличие маршрута. Локальный сервер не моделирует сертификаты, распределённые часы, реальную очередь или повторную доставку. Эти выводы требуют собственных трасс, логов и тестового стенда.</p>\n<p>Команды могут раскрыть имена хостов и заголовки, поэтому запускайте их только в разрешённом окружении. Не используйте чужие адреса, не отправляйте production-записи в учебный endpoint и не добавляйте <code>Authorization</code>, Cookie или персональные параметры в публичный отчёт.</p>\n<h2>Критерий завершения проверки</h2>\n<p>Расследование можно закрыть, когда для одного запроса записаны входы, первый подтверждённый слой, доказательство и действие. Для ошибки сертификата это детали имени и цепочки; для <code>404</code> — метод, URI и владелец ответа; для <code>503</code>/<code>504</code> — upstream, временные границы и решение по retry. После изменения воспроизведите только этот сценарий и проверьте, что наблюдение изменилось ожидаемым образом.</p>\n<p>Если остаётся только фраза «ошибка исчезла», проверка не закончена. Нужен повторяемый запрос, сопоставленный с логами и контрактом операции. Тогда следующий инженер сможет отличить исправленный маршрут от временно здорового upstream и не вернётся к случайному изменению timeout.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 9110 — HTTP Semantics</a> — определения HTTP-сообщения, маршрутизации, методов, полей <code>Allow</code> и <code>Retry-After</code>, статусов <code>404</code>, <code>405</code>, <code>503</code> и <code>504</code>, а также идемпотентности. Стандарт не определяет конфигурацию конкретного proxy или прикладного endpoint.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8446.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3</a> — описание TLS 1.3 и защищённого рукопожатия. Стандарт не подтверждает состояние сертификата конкретного hostname.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc6125.html\" target=\"_blank\" rel=\"noopener noreferrer\">IETF RFC 6125 — Representation and Verification of Domain-Based Application Service Identity</a> — правила сопоставления DNS-имени сервиса и его идентичности при проверке сертификата. Применение зависит от протокола и конкретного клиента.</li><li><a href=\"https://nodejs.org/api/http.html\" target=\"_blank\" rel=\"noopener noreferrer\">Node.js HTTP API</a> — официальный API для учебного локального HTTP-сервера, использованного в примере. Документация не делает пример моделью production-инфраструктуры.</li></ul>"
}