Files
progcode/editorial/agent-rewrites/022.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
17 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": 22,
"slug": "editorial-2027-05-field-http-tls-guide",
"title": "HTTP и TLS без догадок: как найти границу сетевой ошибки",
"excerpt": "504, ошибка сертификата и 404 выглядят похожими в браузере, но рождаются на разных этапах. Разбираем безопасную диагностику: от имени узла и TLS до HTTP-статуса, логов и критерия готовности.",
"contentHtml": "<p>Пользователь видит в браузере «не удаётся подключиться», а мониторинг показывает 504. Инженер меняет таймаут в приложении, повторяет запрос и получает тот же результат. Иногда он добавляет <code>--insecure</code>, видит ответ и считает проблему решённой. Цена такой ошибки — потерянное время, ослабленная проверка сертификата и повтор запроса, который для <code>POST</code> может создать вторую операцию.</p>\n<p>У сетевого сбоя есть граница. Он возникает при разрешении имени, установке TCP-соединения, TLS-рукопожатии, передаче HTTP или обработке маршрута приложением. Код из браузера не называет границу. Поэтому проверяйте этапы по порядку и записывайте только подтверждённые факты.</p>\n<h2>Тезис: сначала установите, где остановился запрос</h2>\n<p>HTTP-статус появляется только после того, как клиент получил HTTP-ответ. Если TLS завершился ошибкой, приложение не могло вернуть <code>404</code> или <code>503</code>. Если запрос дошёл до доверенного входа и получил <code>404</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>DNS</td><td>Имя разрешилось в адрес</td><td>Порт принимает соединение</td></tr><tr><td>TCP</td><td>Соединение с адресом установлено</td><td>Сертификат подходит имени</td></tr><tr><td>TLS</td><td>Канал и проверка имени завершились</td><td>Маршрут приложения существует</td></tr><tr><td>HTTP</td><td>Получены статус и заголовки</td><td>Ответ сформировало origin-приложение</td></tr><tr><td>Приложение</td><td>Лог связывает запрос с handler</td><td>Проблем нет у посредника или клиента</td></tr></tbody></table>\n<p>Эта граница защищает расследование от скачка к удобной гипотезе. Статус <code>504</code> обычно означает, что компонент, который отвечает клиенту, не дождался другого компонента. Он не доказывает, что origin недоступен: причиной может быть маршрут, лимит соединений, балансировщик или промежуточный proxy. Проверяйте того, кто сформировал статус.</p>\n<figure><img src=\"/assets/editorial/2027/http-tls-guide-2027-evidence-handoff-loop.svg\" alt=\"Цикл диагностики HTTP и TLS: безопасный сбор фактов, определение этапа, проверка гипотезы и запись результата\" loading=\"lazy\" /><figcaption>Сначала остаётся безопасный факт, затем выбирается граница проверки. Гипотеза меняется только после нового наблюдения.</figcaption></figure>\n<h2>Механизм: что проверяет каждый слой</h2>\n<p>DNS отвечает на вопрос «какой адрес связан с именем». Запишите имя и выбранный адрес. Если имя разрешается в несколько адресов, один успешный ответ не объясняет поведение остальных. Зафиксируйте также тип записи и момент проверки. Не делайте из DNS-ответа вывод о доступности сервиса.</p>\n<p>TCP отвечает на вопрос «принимает ли адрес соединение на порту». Ошибка соединения и таймаут различают отказ узла и отсутствие ответа, но не объясняют причину сами по себе. Балансировщик может принять TCP и не передать запрос дальше.</p>\n<p>TLS добавляет проверку защищённого канала и имени. Клиент сравнивает hostname с именами в Subject Alternative Name сертификата и проверяет цепочку доверия и срок действия. Сертификат может быть действующим, но выпущенным для другого имени. Подмена URL на IP часто ломает именно эту проверку. Заголовок <code>Host</code> не исправит ошибку: до HTTP клиент ещё не дошёл.</p>\n<p>Флаг <code>--insecure</code> полезен только как ограниченный учебный эксперимент, который показывает, что сервер способен отправить байты. Он отключает проверку сертификата и не является исправлением. После него вернитесь к обычной валидации и не переносите результат в критерий доступности.</p>\n<p>HTTP сообщает метод, путь, статус, заголовки и тело. Смотрите на <code>Retry-After</code>, <code>Location</code>, <code>Allow</code>, <code>Cache-Control</code>, <code>Age</code> и <code>Via</code>, если они относятся к вопросу. Один заголовок не доказывает источник ответа: proxy может его добавить, удалить или переписать. Сопоставляйте ответ с логом доверенного входа по request id.</p>\n<h2>Учебный пример: отделяем запрос от его результата</h2>\n<p>Ниже — самостоятельный локальный пример без сети. Сервер возвращает безопасный идентификатор, метод и путь. Код демонстрирует форму HTTP-обмена; он не показывает работу CDN, TLS, балансировщика или production-сервиса.</p>\n<pre><code>import { createServer } from 'node:http';\n\nconst server = createServer((request, response) =&gt; {\n response.writeHead(request.url === '/health' ? 200 : 404, {\n 'content-type': 'application/json; charset=utf-8',\n 'x-request-id': 'local-001'\n });\n response.end(JSON.stringify({ method: request.method, path: request.url }));\n});\n\nserver.listen(0, '127.0.0.1', async () =&gt; {\n const { port } = server.address();\n for (const path of ['/health', '/missing']) {\n const response = await fetch(\\`http://127.0.0.1:\\${port}\\${path}\\`);\n console.log(response.status, response.headers.get('x-request-id'));\n }\n server.close();\n});</code></pre>\n<p>В учебном запуске <code>/health</code> возвращает <code>200</code>, а <code>/missing</code> — <code>404</code>. Это проверяет только локальный HTTP-контракт. Идентификатор <code>local-001</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>Ошибка до HTTP-статуса</td><td>DNS, TCP или TLS</td><td>Сравнить этап и текст ошибки клиента</td><td>Исправлять имя, порт или сертификат на подтверждённом этапе</td></tr><tr><td>504 от proxy</td><td>Таймаут ожидания upstream</td><td>Сопоставить request id, длительность и лог proxy</td><td>Проверить маршрут, лимит и upstream; не увеличивать таймаут вслепую</td></tr><tr><td>404 после успешного TLS</td><td>Путь, метод или версия API</td><td>Сверить метод, нормализованный путь и лог handler</td><td>Исправить контракт или маршрутизацию</td></tr><tr><td>401</td><td>Аутентификация не принята</td><td>Посмотреть challenge и безопасный класс credentials</td><td>Проверить выдачу и область токена; секрет не копировать</td></tr><tr><td>403</td><td>Доступ запрещён правилом</td><td>Проверить policy и origin запроса</td><td>Исправить право или объяснить отказ; не подменять его повтором</td></tr><tr><td>503 с Retry-After</td><td>Временная недоступность сервера</td><td>Сверить зависимость, лимит и семантику метода</td><td>Повторять только идемпотентную операцию с лимитом</td></tr></tbody></table>\n<h2>Безопасная запись результата</h2>\n<p>Полный вывод <code>curl -v</code> удобен для диагностики, но может содержать <code>Authorization</code>, cookie, токены в query и непубличные имена. Очищайте вывод до копирования в issue или чат. Сохраняйте hostname, порт, метод, путь без секретных параметров, этап, статус, длительность, размер ответа и безопасный request id. Время пишите вместе с часовым поясом, длительность — с единицей измерения.</p>\n<pre><code>function redactNetworkOutput(text) {\n return text\n .replace(/(Authorization:\\s*Bearer\\s+)[^\\s]+/gi, '$1[masked]')\n .replace(/(Cookie:\\s*)[^\\n]+/gi, '$1[masked]')\n .replace(/([?&amp;](?:token|secret|signature)=)[^&amp;\\s]+/gi, '$1[masked]');\n}\n\nconst sample = 'GET /health?token=abc HTTP/1.1\\nAuthorization: Bearer abc\\nCookie: sid=xyz';\nconsole.log(redactNetworkOutput(sample));</code></pre>\n<p>Это учебный санитайзер текстовой строки. Он показывает три известных формата и не обнаруживает неизвестные секреты, JSON-поля, бинарные данные или нестандартные заголовки. Перед передачей всё равно просмотрите результат. Для постоянной диагностики надёжнее allowlist структурированных полей, чем маскирование произвольного текста.</p>\n<h2>Порядок действий</h2>\n<ol><li>Зафиксируйте URL, метод, время, режим proxy и безопасный идентификатор. Уберите <code>Authorization</code>, cookie и персональные query-параметры.</li><li>Проверьте DNS и адрес назначения отдельно от приложения. Сохраните выбранный адрес, код ошибки и длительность.</li><li>Проверьте TCP-порт. Не называйте сервис доступным только потому, что имя разрешилось.</li><li>Для HTTPS проверьте hostname, SAN, цепочку доверия и срок действия сертификата обычным клиентом.</li><li>После успешного TLS снимите HTTP-статус и нужные заголовки. Сравните ответ с origin и кэшем, если между ними есть посредник.</li><li>Сопоставьте request id с логом доверенного входа и handler. Причину формулируйте только на уровне, подтверждённом наблюдением.</li><li>Выберите один следующий тест с ожидаемым результатом. Для <code>POST</code> отдельно проверьте идемпотентность и ключ операции до любого повтора.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Один локальный запрос не показывает потерю пакетов, DNS-балансировку, корпоративный proxy, особенности браузерного хранилища, региональные маршруты и политику реального центра сертификации. Код <code>504</code> не называет зависимость, а <code>404</code> не доказывает одинаковую настройку всех регионов. Для этих выводов нужны согласованные логи и доступные сетевые наблюдения.</p>\n<p>Если TLS не завершился, остановите HTTP-проверку. Не подставляйте <code>Host</code>, не включайте <code>--insecure</code> как постоянный режим и не меняйте таймауты приложения. Если TLS успешен, но серверный лог не знает request id, не объявляйте origin источником ответа: сначала установите доверенную границу сопоставления. Если очиститель оставил неизвестное поле, не публикуйте запись.</p>\n<h2>Критерий готовности</h2>\n<p>Диагностика готова, когда запись содержит проверенный этап остановки, безопасные входные данные, наблюдаемый результат и один повторяемый тест. Для TLS это hostname, SAN, цепочка и срок действия; для HTTP — метод, путь, статус, выбранные заголовки и связь с логом. Исправление готово, когда тот же тест с обычной проверкой сертификата и тем же контрактом даёт ожидаемый результат, а отрицательный путь остаётся объяснимым: неизвестный путь возвращает согласованный <code>404</code>, а повтор небезопасного метода не запускается автоматически.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://www.rfc-editor.org/rfc/rfc9110.html\" target=\"_blank\" rel=\"noopener\">IETF RFC 9110: HTTP Semantics</a> — статусы, методы, заголовки и посредники.</li><li><a href=\"https://www.rfc-editor.org/rfc/rfc8446.html\" target=\"_blank\" rel=\"noopener\">IETF RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3</a> — этапы TLS 1.3 и защищённый канал.</li></ul>"
}