Skip to content

Таймауты и медленные запросы ​

Сервер измеряет приём запроса, ожидание в очереди, работу обработчика и отправку ответа. Таймауты ограничивают сетевые этапы; slow_request_threshold_ms позволяет заметить медленный запрос, включая очередь и обработчик, до завершения ответа.

Проверки работают для HTTP/1.0, HTTP/1.1, HTTP/2 и HTTP/3. WebSocket поддерживает их через HTTP/1.1 Upgrade и существующий HTTP/2 extended CONNECT. WebSocket через HTTP/3 пока не поддерживается.

Настройка ​

Добавьте объект timeouts в существующую секцию main. Это фрагмент конфигурации: остальные обязательные настройки сохраняются.

json
{
    "main": {
        "timeouts": {
            "request_header_timeout_ms": 15000,
            "request_body_idle_timeout_ms": 30000,
            "request_body_total_timeout_ms": 300000,
            "slow_request_threshold_ms": 1000
        }
    }
}

Истечение любого таймаута прерывает то, что он ограничивает: запрос, stream или соединение. Режима «только наблюдать» нет. Значения по умолчанию действуют и без секции timeouts, поэтому любой сервер сразу ограничивает приём заголовков, паузы в теле, отправку и живость WebSocket. Порог медленного запроса только записывает событие и не отменяет обработчик.

HTTP/2 и HTTP/3 используют те же параметры. Прежний main.env.http2_request_timeout_sec удалён; конфигурация, в которой он задан, отклоняется, чтобы бюджеты HTTP/2 не менялись незаметно. Остальные транспортные ограничения HTTP/2 и QUIC имеют собственные настройки.

Все длительности — целые миллисекунды от 0 до 86400000. Ноль отключает проверку, кроме этих — они всегда положительные: header и body idle, slow_request_threshold_ms, ws_close_timeout_ms, а при включённом Ping — Pong timeout. Полный список и значения по умолчанию — в разделе Параметры.

Параметры ​

Объект main.timeouts задаёт общую политику, servers.<name>.timeouts переопределяет отдельные поля для виртуального хоста. Отсутствующие поля наследуются.

ПолеУмолчание, мсСмысл
request_header_timeout_ms60000Абсолютный бюджет получения HTTP-заголовков; HTTP/1.x также ограничивает ожидание первых байтов и h2c preface
request_body_idle_timeout_ms60000Пауза без прогресса тела конкретного запроса
request_body_total_timeout_ms0Общая длительность приёма тела
slow_request_threshold_ms1000Порог события о медленном запросе
tls_handshake_timeout_ms10000Установка TLS для TCP; QUIC handshake регулируется транспортной политикой HTTP/3
keepalive_timeout_ms75000Ожидание следующего HTTP/1.x запроса
response_send_idle_timeout_ms60000Отсутствие прогресса отправки HTTP-ответа
ws_handshake_timeout_ms10000Установка WebSocket после HTTP-заголовков
ws_ping_interval_ms30000Интервал проверки живости WebSocket
ws_pong_timeout_ms10000Ожидание соответствующего Pong после фактической отправки Ping
ws_message_idle_timeout_ms60000Пауза в начатом WebSocket-сообщении или незавершённом frame
ws_message_total_timeout_ms0Общий бюджет сборки сообщения, включая фрагменты
ws_send_idle_timeout_ms60000Зависшая отправка при наличии готового ответа или ожидающего отправки heartbeat
ws_close_timeout_ms5000Бюджет завершения закрытия WebSocket
ws_application_idle_timeout_ms0Отсутствие завершённых прикладных сообщений; Pong его не продлевает

Активная WebSocket-сессия не ограничивается HTTP body timeout; тихая сессия с корректным Pong остаётся открытой при выключенном application idle.

Заголовки, тело и отправка ​

request_header_timeout_ms — абсолютный бюджет получения заголовков. Поступление отдельных байтов его не продлевает. HTTP/1.x также ограничивает ожидание первых байтов и незавершённого h2c preface.

request_body_idle_timeout_ms — максимальная пауза без прогресса приёма тела. Например, при бюджете 30 секунд и новой порции тела каждые 20 секунд idle не истечёт. request_body_total_timeout_ms ограничивает всю загрузку от завершения заголовков и не сбрасывается поступлением данных. По умолчанию общий бюджет выключен (0).

response_send_idle_timeout_ms ограничивает отсутствие прогресса отправки готового ответа. Это отдельный таймаут: длительная работа обработчика до готовности ответа не считается медленной отправкой. При Expect: 100-continue зависшая отправка Continue проверяется как отправка; незавершённый Continue не считается паузой клиента при загрузке тела.

tls_handshake_timeout_ms ограничивает TCP TLS handshake, а keepalive_timeout_ms — ожидание следующего HTTP/1.x запроса на свободном соединении. TLS внутри QUIC и простой HTTP/2/QUIC соединения регулируются транспортными настройками, описанными на страницах HTTP/2 и HTTP/3.

СитуацияДействие
Незавершённый HTTP/1.x запрос408 и закрытие соединения, если порядок ответов позволяет
Соединение HTTP/1.x без единого байта запроса (новое или keep-alive)Закрытие без ответа, как в nginx: клиент с пулом соединений мог бы принять 408 за ответ на запрос, который как раз отправляет
HTTP/1.x с ответом в полёте или очередью pipelineЗакрытие соединения; 408 не вставляется перед прежними ответами
Тело HTTP/2RST_STREAM(CANCEL) для соответствующего потока
Незавершённое HTTP/2 framing или HEADERS/CONTINUATIONЗавершение соединения с GOAWAY: общее состояние парсера препятствует изоляции потока
Приём или отправка HTTP/3Отмена соответствующего потока через STOP_SENDING/RESET_STREAM с H3_REQUEST_CANCELLED
Отправка HTTP/1.x ответа или TCP TLS handshakeЗакрытие соединения

Активность соседних HTTP/2/3 потоков не продлевает body idle конкретного запроса. Таймауты используют монотонное время. Worker проверяет их периодически (TCP tick — 500 мс) и на путях приёма данных; фактическое закрытие зависит от расписания event loop и доступности блокировки. Значение в 100 мс не гарантирует закрытие ровно через 100 мс.

Наследование и маршруты ​

Приоритет: встроенные значения → main.timeouts → servers.<name>.timeouts → разрешённые настройки метода маршрута. Частичный объект переопределяет только указанные поля. Например, для большого upload:

json
{
    "servers": {
        "s1": {
            "timeouts": {
                "request_body_idle_timeout_ms": 60000
            },
            "http": {
                "routes": {
                    "/upload": {
                        "POST": {
                            "file": "handlers/libapp.so",
                            "function": "upload",
                            "timeouts": {
                                "request_body_total_timeout_ms": 600000
                            }
                        }
                    }
                }
            }
        }
    }
}

На HTTP-маршруте разрешены только request_body_idle_timeout_ms, request_body_total_timeout_ms и slow_request_threshold_ms. Header/TLS бюджеты должны совпадать у всех виртуальных хостов одного TCP listener: до разбора Host/SNI политика должна быть однозначной.

Неизвестный ключ, неверный тип, дробь, недопустимый ноль или настройка на запрещённом уровне прекращают загрузку с указанием пути настройки. Политика проверяется при запуске и до переключения поколения при reload. Новые соединения получают новую политику; старое поколение сохраняет свою до завершения по обычным правилам soft/hard reload. Невалидный кандидат оставляет рабочее поколение активным.

WebSocket ​

В main.timeouts или servers.<name>.timeouts можно включить heartbeat и ограничить сборку сообщений:

json
{
    "timeouts": {
        "ws_handshake_timeout_ms": 10000,
        "ws_ping_interval_ms": 30000,
        "ws_pong_timeout_ms": 10000,
        "ws_message_idle_timeout_ms": 60000,
        "ws_message_total_timeout_ms": 300000,
        "ws_send_idle_timeout_ms": 60000,
        "ws_close_timeout_ms": 5000,
        "ws_application_idle_timeout_ms": 0
    }
}

Одновременно ожидается не более одного серверного Ping; соответствующий Pong должен повторить его payload. Чужой Pong, входящий Ping и прикладные сообщения не подтверждают heartbeat. Pong deadline начинается после фактической отправки Ping, а ожидание Ping в очереди покрывает send idle. ws_ping_interval_ms: 0 отключает серверный heartbeat. HTTP/2 PING не заменяет WebSocket Ping/Pong.

Message idle действует для начатого сообщения или незавершённого кадра, включая его заголовок. Total ограничивает сборку одного сообщения с учётом фрагментации; межкадровые Ping/Pong его не продлевают. Между сообщениями message idle не действует. Тихая сессия с правильными Pong остаётся открытой при ws_application_idle_timeout_ms: 0. Положительное значение application idle требует завершённых прикладных сообщений; Pong его не сбрасывает.

При истечении таймаута сервер пытается отправить Close с кодом 1008 в HTTP/1.1 и HTTP/2 (раньше H2 использовал 1000 для policy timeout). Ожидание завершения ограничено ws_close_timeout_ms. При неполном входящем кадре или зависшей отправке транспорт может завершиться сразу. После Upgrade HTTP 408 не отправляется. В HTTP/2 завершение сессии отменяет её поток.

В websockets.routes.<path>.<method>.timeouts разрешены все ws_*, кроме ws_handshake_timeout_ms. Resource-маршрут выбирается по сообщению, поэтому его настройки начинают действовать после первого полностью принятого сообщения, выбравшего этот маршрут. При выборе следующего маршрута наследование начинается заново от виртуального хоста. Для websockets.default отдельного route override нет. Пример устройства маршрутов: WebSocket-запросы.

HTTP/2 использует один абсолютный header deadline от первого байта HEADERS до завершения всего блока CONTINUATION. Переход к следующему кадру не продлевает срок. Пока тип кадра ещё неизвестен, незавершённый preface/транспортный заголовок остаётся под защитой существующего receive budget соединения; SETTINGS ACK и idle/PING сохраняют свои проверки.

После распознавания DATA header timeout к его payload не применяется. Полезные части тела продлевают body idle до сборки полного кадра; padding его не продлевает. При body timeout отменяется соответствующий stream, остаток DATA дочитывается и отбрасывается до границы следующего кадра. TCP не позволяет обработать соседний кадр, пока этот остаток не пришёл. Body total не продлевается. На точной границе дедлайна операция уже истекла: поздние байты до следующего tick не обходят дедлайн.

Время приёма HTTP/2 HEADERS/CONTINUATION входит в headers_ms, total_ms и access log. slow_request phase=headers доступен ещё до END_HEADERS; при создании request уже зарегистрированное событие не повторяется. Trailers не меняют начало запроса и время исходных заголовков.

Полностью отправленный WebSocket Ping отключает признак ожидающей отправки: при пустой очереди действует только Pong deadline. Следующая проба разрешена после подтверждения предыдущей. Pong на границе срока или позднее не восстанавливает сессию. Timeout из read path и tick инициирует одинаковое закрытие.

Журнал ​

Для событий таймаутов включите журнал уровня info:

json
{
    "main": {
        "log": { "enabled": true, "level": "info" }
    }
}
text
slow_request protocol=h2 phase=body age_ms=1000
request_timeout protocol=h2 reason=body_idle fd=12 stream=1
websocket_timeout protocol=websocket reason=pong fd=12 stream=0
slow_request_complete headers_ms=2 body_ms=1500 queue_ms=1 handler_ms=5 send_ms=2 total_ms=1510

Это примеры записей: поля зависят от события и протокола. slow_request отмечает пересечение порога один раз для запроса, доступного worker, и указывает текущую фазу: headers, body, queue, handler или send. slow_request_complete содержит длительности при завершении ответа, если общий порог превышен. Отмена незавершённого запроса отражается в request_timeout; итоговая запись длительностей для каждой отмены не гарантируется.

События дедуплицируются по причине: slow event не подавляет timeout. Новый HTTP-запрос, heartbeat или эпизод отправки может зарегистрировать новое превышение. Поля timeout-записи едины: protocol, reason, fd, stream; для HTTP/1.x stream=0.

Последнее поле access log — секунды от первых байтов запроса, включая приём, очередь, обработчик и отправку. В HTTP/3 завершением измерения считается передача ответа в QUIC; отправку транспортом контролирует отдельный send idle. Срок жизни открытой WebSocket-сессии не входит в длительность HTTP-запроса установки соединения.

Границы реализации ​

Закрытие соединения или отмена потока не останавливают выполняющуюся C-функцию обработчика. Общего deadline обработчика, cancel token и кооперативной отмены вызовов БД здесь нет. Не реализована и проверка минимальной скорости загрузки: регулярные порции могут продлевать idle, поэтому для ограничения всей загрузки используйте total budget.

Таймауты не заменяют лимиты тела и настройки его хранения.

Выпущено под лицензией MIT.