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:

  1. Połączenie zaakceptowane — rozpoczyna się limit czasu nagłówków. OxPHP czeka, aż klient wyśle kompletny zestaw nagłówków HTTP.
  2. Nagłówki odebrane — limit czasu nagłówków się kończy. Żądanie jest przekazywane do workera PHP.
  3. PHP przetwarza żądanie — kod aplikacji działa pod kontrolą własnego max_execution_time PHP (sterowanego przez SIGALRM). Po osiągnięciu limitu żądanie zostaje anulowane i wyzwalany jest ujednolicony błąd krytyczny Request cancelled (timeout).
  4. 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.

Note

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 komunikat Request 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
; php.ini max_execution_time = 30

Albo dla poszczególnego skryptu w czasie działania:

php
set_time_limit(60); // 60 seconds from now set_time_limit(0); // disable for this request

Zalecane 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:

php
// 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:

php
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:

bash
HEADER_TIMEOUT_SECONDS=15
OPcache 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

compose.yaml
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:ro

Z plikiem php.ini zawierającym:

php.ini
max_execution_time = 30

Dobre praktyki

  • Nigdy nie ustawiaj max_execution_time = 0 globalnie w środowisku produkcyjnym, chyba że masz endpointy SSE lub long-polling, które wymagają nieskończenie otwartych połączeń. Preferuj set_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_time szybko 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ż