Limity czasu
OxPHP wymusza dwa niezależne limity czasu, które chronią przed powolnymi klientami i wymykającymi się spod kontroli żądaniami. Limit czasu nagłówków zabezpiecza fazę połączenia na poziomie serwera. Czas wykonywania PHP jest ograniczony przez własną dyrektywę ini max_execution_time PHP (oraz funkcję środowiska uruchomieniowego set_time_limit()), dokładnie tak samo jak w każdym innym SAPI.
Jak to działa
Każde żądanie przechodzi przez następujące fazy:
- Połączenie zaakceptowane — rozpoczyna się limit czasu nagłówków. OxPHP czeka, aż klient wyśle kompletny zestaw nagłówków HTTP.
- Nagłówki odebrane — limit czasu nagłówków się kończy. Żądanie jest przekazywane do workera PHP.
- PHP przetwarza żądanie — kod aplikacji działa pod kontrolą własnego
max_execution_timePHP (sterowanego przez SIGALRM). Po osiągnięciu limitu żądanie zostaje anulowane i wyzwalany jest ujednolicony błąd krytycznyRequest cancelled (timeout). - Odpowiedź wysłana — w połączeniach keep-alive cykl powtarza się od kroku 1.
graph TD A["TCP connect<br/>(+ TLS handshake if enabled)"] -->|HEADER_TIMEOUT_SECONDS| B["Headers received"] B -->|max_execution_time| C["Response sent"] C -->|next request, keep-alive| A
W połączeniach keep-alive oba limity czasu obowiązują niezależnie dla każdego żądania w ramach połączenia.
Gdy TLS jest włączony, limit czasu nagłówków rozpoczyna się po zakończeniu uzgadniania TLS, a nie w momencie zaakceptowania połączenia TCP.
Limit czasu nagłówków chroni przed atakami typu slowloris, w których klient wysyła nagłówki po jednym bajcie, aby utrzymywać połączenia otwarte w nieskończoność.
Odmierzanie czasu wykonywania PHP jest w całości delegowane do PHP. Gdy max_execution_time zostanie przekroczony, ujednolicona ścieżka anulowania OxPHP:
- Ustawia
connection_status() & PHP_CONNECTION_TIMEOUT, aby kod użytkownika mógł wykryć przyczynę. - Uruchamia wszystkie funkcje zwrotne
register_shutdown_function(), dokładnie tak jak robi to PHP-FPM. - Zwraca HTTP
504 Gateway Timeout, a komunikatRequest cancelled (timeout)zapisuje w dzienniku błędów.
Kody statusu anulowania
OxPHP anuluje żądanie z kilku różnych powodów. Każdy z nich jest mapowany na status przesyłany po sieci, który odzwierciedla rzeczywisty stan, zamiast ogólnego 500:
| Przyczyna | Status | Uwagi |
|---|---|---|
Przekroczono max_execution_time / set_time_limit() |
504 Gateway Timeout |
Wyczerpanie czasu wykonywania po stronie serwera. |
| Łagodne zamknięcie serwera przerwało obsługę żądania | 503 Service Unavailable |
Dodaje Retry-After: 5, aby klienci ponawiali próbę wobec przywróconej lub zastępczej instancji. |
| Klient zamknął połączenie w trakcie żądania | 499 |
„Client Closed Request” w stylu nginx. Połączenie już nie istnieje, więc ten status pojawia się wyłącznie w dziennikach dostępu i metrykach — nigdy nie jest przesyłany po sieci. Ujawnia przerwania inicjowane przez klienta jako status inny niż 5xx, dzięki czemu nie zaśmiecają one alertów o błędach serwera. |
| Worker uznany za zablokowany przez nadzorcę | 500 Internal Server Error |
Ogólny błąd serwera — przyczyna (zakleszczenie, zablokowane wywołanie systemowe, …) jest nieznana. |
| Anulowanie zainicjowane przez kod użytkownika | 500 Internal Server Error |
Kod użytkownika może ustawić własny status za pomocą http_response_code() przed wyzwoleniem anulowania; ten jawnie ustawiony status jest zachowywany. |
Jeśli twój ERROR_PAGES_DIR zawiera tylko 500.html, dodaj 504.html, 503.html oraz (opcjonalnie) 499.html, aby ostylowane strony były spójne dla wszystkich przyczyn anulowania.
Konfiguracja
| Zmienna | Domyślnie | Opis |
|---|---|---|
HEADER_TIMEOUT_SECONDS |
5 |
Maksymalna liczba sekund na odebranie nagłówków żądania po zaakceptowaniu połączenia. Chroni przed atakami slowloris. 0 nie jest traktowane w szczególny sposób — jest przekazywane do hyper jako zerowy limit czasu, który wyzwala się natychmiast. Aby wyłączyć limit czasu, usuń zmienną, zamiast ustawiać ją na 0 |
Czas wykonywania PHP konfiguruje się poprzez php.ini, a nie zmienne środowiskowe OxPHP:
; php.ini
max_execution_time = 30Albo dla poszczególnego skryptu w czasie działania:
set_time_limit(60); // 60 seconds from now
set_time_limit(0); // disable for this requestZalecane wartości
| Scenariusz | Limit czasu nagłówków | max_execution_time |
|---|---|---|
| Serwer API | 5s | 30s |
| Ogólne serwowanie stron WWW | 5s | 60s |
| Przesyłanie plików | 10s | 300s |
| SSE / long-polling | 5s | 0 (wyłączone, ustawiane dla poszczególnego skryptu) |
Dostosuj te wartości do charakterystyki swojej aplikacji. W przypadku endpointów SSE wywołaj set_time_limit(0) na początku skryptu strumieniującego, zamiast globalnie wyłączać max_execution_time.
Rozwiązywanie problemów
Klienci nieoczekiwanie otrzymują 504 z komunikatem „Request cancelled (timeout)”
Limit czasu wykonywania PHP zadziałał, zanim skrypt zdążył się zakończyć.
Rozwiązanie: Zwiększ max_execution_time dla danego skryptu albo wywołaj set_time_limit($seconds), aby wydłużyć go w czasie działania:
// at the top of a slow script
set_time_limit(300);W przypadku endpointów SSE lub strumieniujących, gdzie połączenia muszą pozostawać otwarte w nieskończoność, wyłącz licznik dla tego skryptu:
set_time_limit(0);Połączenia są zrywane, zanim dotrą nagłówki
Limit czasu nagłówków jest zbyt krótki dla klientów na łączach o wysokim opóźnieniu lub za powolnymi load balancerami.
Rozwiązanie: Zwiększ limit czasu nagłówków:
HEADER_TIMEOUT_SECONDS=15OPcache powoduje przekroczenie limitu czasu pierwszego żądania po zmianie skryptu
Rekompilacja OPcache zwiększa opóźnienie pierwszego żądania po zmianie pliku. Zdarza się to częściej w środowiskach deweloperskich z wieloma plikami. Zwiększ max_execution_time lub wyłącz go na czas prac deweloperskich.
Przykład dla Dockera
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:8080"
environment:
HEADER_TIMEOUT_SECONDS: "5"
volumes:
- ./app:/var/www/html:ro
- ./php.ini:/usr/local/etc/php/conf.d/zz-app.ini:roZ plikiem php.ini zawierającym:
max_execution_time = 30Dobre praktyki
- Nigdy nie ustawiaj
max_execution_time = 0globalnie w środowisku produkcyjnym, chyba że masz endpointy SSE lub long-polling, które wymagają nieskończenie otwartych połączeń. Preferujset_time_limit(0)dla poszczególnych skryptów. - Stosuj krótsze limity dla serwerów API. API mają przewidywalne czasy odpowiedzi. 30-sekundowy
max_execution_timeszybko wychwytuje zablokowane żądania, nie wpływając na normalny ruch. - Łącz z ograniczaniem liczby żądań. Limity czasu chronią pojedyncze żądania; ograniczanie liczby żądań chroni przed dużym wolumenem żądań. Razem obejmują zarówno powolne, jak i szybkie wzorce ataków.
Zobacz też
- Ograniczanie liczby żądań — ograniczanie liczby żądań na adres IP
- Server-Sent Events (SSE) — wskazówki dotyczące wyłączania limitów czasu dla endpointów strumieniujących
- Dokumentacja konfiguracji — pełna dokumentacja zmiennych środowiskowych