Files
progcode/editorial/agent-rewrites/024.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
14 KiB
JSON
Raw 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": "404, 503 и ошибка сертификата возникают на разных этапах запроса. Разбираем их по наблюдаемым признакам, проверяем локальным примером и не превращаем retry или --insecure в случайное исправление.",
"contentHtml": "<p>Браузер показывает ошибку, а команда сразу меняет timeout, маршрут или сертификат. Через час выясняется, что запрос вообще не дошёл до приложения. В другом случае приложение вернуло <code>404</code>, но инженер ищет проблему в TLS. Цена такой ошибки — лишний rollout, потерянное время и риск сломать рабочий путь, пытаясь исправить не тот слой.</p>\n<p><strong>Тезис:</strong> сначала нужно определить первый подтверждённый этап отказа. До HTTP находятся DNS, TCP и TLS. После успешного TLS появляются метод, URI, заголовки и статус HTTP. Если перепутать границу, проверка не отвечает на вопрос и создаёт ложное ощущение прогресса.</p>\n<h2>Один запрос, несколько разных отказов</h2>\n<p>У HTTPS-запроса есть последовательность. Клиент разрешает имя, открывает TCP-соединение, проводит TLS-рукопожатие, отправляет HTTP-сообщение и читает ответ. Посредник может завершить запрос на любом шаге. Поэтому текст ошибки важнее цвета страницы: <code>ERR_TLS_CERT_ALTNAME_INVALID</code> ещё не является HTTP-ответом, а <code>404</code> означает, что HTTP-обмен уже состоялся.</p>\n<p>Уровень ошибки задаёт набор допустимых проверок. Заголовок <code>Host</code> не исправит сертификат, если TLS-клиент не доверяет имени. Увеличение timeout не создаст отсутствующий маршрут. Повтор <code>POST</code> не становится безопасным только потому, что сервер вернул <code>503</code>.</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><code>ERR_TLS_CERT_ALTNAME_INVALID</code></td><td>Имя в URL не совпадает с SAN</td><td>Сверить hostname, SAN и адрес</td><td>Исправить имя, сертификат или vhost</td></tr><tr><td><code>404 Not Found</code></td><td>URI или метод не попал в маршрут</td><td>Проверить известный endpoint и лог маршрута</td><td>Исправить путь, метод или route config</td></tr><tr><td><code>503 Service Unavailable</code></td><td>Обработчик или зависимость недоступны</td><td>Проверить <code>Retry-After</code> и upstream-логи</td><td>Устранить недоступность; retry ограничить контрактом</td></tr><tr><td>Timeout без статуса</td><td>Неизвестен этап задержки</td><td>Разделить connect и read timeout</td><td>Найти этап, затем менять лимит</td></tr></tbody></table>\n<figure><img src=\"/assets/editorial/2027/http-tls-guide-2027-handshake-header-map.svg\" alt=\"Последовательность DNS, TCP, TLS и HTTP с точкой остановки диагностики\" loading=\"lazy\" /><figcaption>Диагностика идёт слева направо. Первый наблюдаемый сбой ограничивает область поиска.</figcaption></figure>\n<h2>Что означает HTTP-статус</h2>\n<p><code>404</code> — это статус HTTP-ответа. Он не доказывает, что ответ сформировало origin-приложение: его мог вернуть reverse proxy или другой посредник. Сохраняйте метод, нормализованный путь, статус, request ID и безопасный набор заголовков. Полные Cookie, Authorization и чувствительные query-параметры в запись не нужны.</p>\n<p><code>503</code> сообщает о временной невозможности обработать запрос. Заголовок <code>Retry-After</code> может дать ориентир, но не гарантирует безопасность повтора. Для чтения задайте ограниченный retry с общим deadline. Для записи сначала проверьте идемпотентность и правило дедупликации. Иначе потерянный ответ после успешной записи превратится в дубль.</p>\n<p>Отрицательный путь обязателен. Если метод меняет деньги, заказ, подписку или другой ресурс, а сервер не принимает ключ операции и не описывает повтор, клиент должен остановиться. Автоматический retry в таком месте скрывает неопределённый результат.</p>\n<h2>Почему ошибка сертификата возникает раньше HTTP</h2>\n<p>TLS защищает канал и связывает его с именем узла. Клиент сравнивает имя назначения с именами в Subject Alternative Name сертификата. Если URL содержит старый alias, IP-адрес или имя другого виртуального хоста, проверка может завершиться до отправки HTTP-запроса. Тогда у приложения нет статуса и тела для анализа.</p>\n<p>Проверяйте три свойства: имя, цепочку доверия и срок действия. Общий текст <code>certificate error</code> скрывает различия между ними. Не подменяйте проверку флагом <code>--insecure</code>. Он может показать, что endpoint отвечает без валидации сертификата, но не исправляет доверие и не доказывает безопасность соединения.</p>\n<h2>Учебная проверка на локальном сервере</h2>\n<p>Пример ниже учебный. Он запускает только локальный HTTP-сервер, не обращается к внешней сети и не моделирует TLS. Его задача — показать разницу между известным маршрутом и отсутствующим URI. В production этот код не заменяет proxy, сертификат, health-check или журнал приложения.</p>\n<pre><code>import { createServer } from 'node:http'; const server = createServer((req, res) =&gt; { if (req.method === 'GET' &amp;&amp; req.url === '/health') { res.writeHead(200, { 'content-type': 'text/plain' }); res.end('ok'); return; } res.writeHead(404, { 'content-type': 'text/plain' }); res.end('missing'); }); server.listen({ port: 0, host: '127.0.0.1' }, async () =&gt; { const { port } = server.address(); const response = await fetch('http://127.0.0.1:' + port + '/missing'); console.log(response.status, await response.text()); server.close(); });</code></pre>\n<p>Запуск <code>node check-http.mjs</code> в этом учебном случае печатает <code>404 missing</code>. Если заменить путь на <code>/health</code>, получится <code>200 ok</code>. Мы проверяем и статус, и тело. Один только текст страницы не показывает, какой контракт нарушен.</p>\n<p>Отправьте <code>POST /health</code> и получите <code>404</code>: маршрут принимает только <code>GET</code>. Это не проблема TLS и не причина увеличивать timeout. Сначала решите, должен ли такой метод существовать в контракте.</p>\n<h2>Порядок диагностики</h2>\n<ol><li>Запишите URL, метод, время и request ID. Удалите Authorization, Cookie и персональные параметры.</li><li>Проверьте DNS и адрес назначения отдельно от приложения. Несколько адресов могут вести к разным конфигурациям.</li><li>Для HTTPS проверьте hostname, SAN, срок действия и цепочку сертификата обычным клиентом. Не отключайте проверку в рабочем запросе.</li><li>После успешного TLS проверьте статус, <code>Allow</code>, <code>Location</code>, <code>Retry-After</code>, тип тела и идентификатор ответа.</li><li>Сопоставьте метод с эффектом. Для изменения состояния определите идемпотентность или ключ операции до включения повторов.</li><li>Сравните ошибочный запрос с безопасным endpoint на том же hostname и зафиксируйте ожидаемый результат.</li></ol>\n<h2>Ограничения</h2>\n<p>HTTP-статус не раскрывает автоматически путь внутри прокси или состояние upstream. Заголовок <code>Server</code> не является доказательством источника ответа. DNS-ответ не доказывает наличие нужного маршрута. Для вывода о реальной инфраструктуре нужны согласованные логи, сетевые данные и разрешённый доступ.</p>\n<p>Локальный пример не проверяет CDN, балансировщик, корпоративный proxy, реальную цепочку сертификатов, рестарт процесса или запись в базе. Он показывает только границу между URI, методом и ответом локального HTTP-сервера. Не переносите его упрощённое поведение в production без явного контракта и тестов.</p>\n<p>Если TLS не проходит, не ищите заголовки приложения. Если TLS проходит, но статус равен <code>404</code>, ищите маршрут и метод. Если пришёл <code>503</code>, определяйте доступность обработчика и безопасность повтора. Если нет статуса, сначала найдите этап timeout.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Диагностика готова, если для одного hostname можно воспроизвести успешный HTTPS-запрос, ошибку проверки имени сертификата, известный <code>404</code> и временный <code>503</code>. Для каждого случая запись содержит этап, метод, путь, статус или TLS-ошибку, безопасный request ID и одно действие. Для записи с неопределённым результатом retry остановлен или защищён идемпотентным контрактом.</p>\n<p>Проверяемый результат — не «ошибка исчезла», а совпадение наблюдения с уровнем проверки. Повторите запрос после изменения одного условия. Если причина и новый результат не различаются по логам или команде, ремонт ещё не доказан.</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, маршрутизацию, поля, статусы и свойства методов. Он не объясняет конфигурацию конкретного proxy или сервиса.</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 и этапы защищённого соединения. Он не подтверждает корректность сертификата конкретного домена или состояние вашей инфраструктуры.</li></ul>"
}