8 lines
14 KiB
JSON
8 lines
14 KiB
JSON
{
|
||
"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) => { if (req.method === 'GET' && 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 () => { 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>"
|
||
}
|