Délais d'expiration

OxPHP applique deux délais d'expiration indépendants qui protègent contre les clients lents et les requêtes incontrôlées. Le délai d'expiration des en-têtes protège la phase de connexion au niveau du serveur. Le temps d'exécution PHP est borné par la directive ini max_execution_time propre à PHP (et par la fonction d'exécution set_time_limit()), exactement comme sur n'importe quel autre SAPI.

Fonctionnement

Chaque requête passe par les phases suivantes :

  1. Connexion acceptée — le délai d'expiration des en-têtes démarre. OxPHP attend que le client envoie un ensemble complet d'en-têtes HTTP.
  2. En-têtes reçus — le délai d'expiration des en-têtes se termine. La requête est distribuée à un worker PHP.
  3. PHP traite la requête — le code applicatif s'exécute sous le max_execution_time propre à PHP (piloté par SIGALRM). Lorsque la limite est atteinte, la requête est annulée et l'erreur fatale unifiée Request cancelled (timeout) se déclenche.
  4. Réponse envoyée — sur les connexions keep-alive, le cycle recommence à l'étape 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

Sur les connexions keep-alive, les deux délais d'expiration s'appliquent indépendamment à chaque requête de la connexion.

Note

Lorsque TLS est activé, le délai d'expiration des en-têtes démarre une fois la poignée de main TLS terminée, et non au moment où la connexion TCP est acceptée.

Le délai d'expiration des en-têtes protège contre les attaques de type slowloris, où un client envoie les en-têtes octet par octet pour maintenir les connexions ouvertes indéfiniment.

La gestion du temps d'exécution PHP est entièrement déléguée à PHP. Lorsque max_execution_time est dépassé, le mécanisme d'annulation unifié d'OxPHP :

  • Définit connection_status() & PHP_CONNECTION_TIMEOUT pour que le code utilisateur puisse détecter la cause.
  • Exécute tous les callbacks register_shutdown_function(), exactement comme le fait PHP-FPM.
  • Renvoie un HTTP 504 Gateway Timeout avec le message Request cancelled (timeout) inscrit dans le journal des erreurs.

Codes de statut d'annulation

OxPHP annule une requête pour plusieurs raisons distinctes. Chacune correspond à un statut transmis sur le réseau qui reflète la condition réelle plutôt qu'un 500 générique :

Cause Statut Notes
max_execution_time / set_time_limit() dépassé 504 Gateway Timeout Épuisement du temps d'exécution côté serveur.
L'arrêt gracieux du serveur a purgé la requête 503 Service Unavailable Ajoute Retry-After: 5 pour que les clients réessaient sur une instance rétablie ou de remplacement.
Le client a fermé la connexion en cours de requête 499 « Client Closed Request » à la manière de nginx. La connexion a déjà disparu, ce statut n'apparaît donc que dans les journaux d'accès et les métriques — il n'est jamais transmis sur le réseau. Il expose les abandons initiés par le client comme des non-5xx afin qu'ils ne polluent pas les alertes d'erreur serveur.
Worker déclaré bloqué par le superviseur 500 Internal Server Error Erreur serveur générique — la cause (interblocage, appel système bloqué, …) est inconnue.
Annulation initiée par le code utilisateur 500 Internal Server Error Le code utilisateur peut définir son propre statut avec http_response_code() avant de déclencher l'annulation ; ce statut explicite est préservé.

Si votre ERROR_PAGES_DIR ne fournit qu'un 500.html, ajoutez 504.html, 503.html et (facultativement) 499.html pour garder des pages stylisées cohérentes pour toutes les causes d'annulation.

Configuration

Variable Valeur par défaut Description
HEADER_TIMEOUT_SECONDS 5 Nombre maximal de secondes pour recevoir les en-têtes de la requête après l'acceptation de la connexion. Protège contre les attaques slowloris. 0 n'est pas traité de façon particulière — il est transmis tel quel à hyper comme un délai d'expiration de zéro seconde, qui se déclenche immédiatement. Pour désactiver le délai d'expiration, laissez la variable non définie plutôt que de la fixer à 0

Le temps d'exécution PHP se configure via php.ini, et non via les variables d'environnement d'OxPHP :

php.ini
; php.ini max_execution_time = 30

Ou par script à l'exécution :

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

Valeurs recommandées

Scénario Délai d'expiration des en-têtes max_execution_time
Serveur d'API 5 s 30 s
Service web général 5 s 60 s
Téléversements de fichiers 10 s 300 s
SSE / long-polling 5 s 0 (désactivé, à définir par script)

Ajustez ces valeurs en fonction des caractéristiques de votre application. Pour les endpoints SSE, appelez set_time_limit(0) en tête du script de streaming plutôt que de désactiver max_execution_time globalement.

Dépannage

Les clients reçoivent un 504 avec « Request cancelled (timeout) » de manière inattendue

La limite de temps d'exécution PHP s'est déclenchée avant que le script ne se termine.

Solution : Augmentez max_execution_time pour le script concerné, ou appelez set_time_limit($seconds) pour l'étendre à l'exécution :

php
// at the top of a slow script set_time_limit(300);

Pour les endpoints SSE ou de streaming où les connexions doivent rester ouvertes indéfiniment, désactivez le minuteur pour ce script :

php
set_time_limit(0);
Les connexions sont abandonnées avant l'arrivée des en-têtes

Le délai d'expiration des en-têtes est trop court pour les clients sur des liaisons à forte latence ou derrière des répartiteurs de charge lents.

Solution : Augmentez le délai d'expiration des en-têtes :

bash
HEADER_TIMEOUT_SECONDS=15
OPcache provoque un délai d'expiration lors de la première requête après une modification de script

La recompilation OPcache ajoute de la latence lors de la première requête après une modification de fichier. C'est plus fréquent dans les environnements de développement comportant de nombreux fichiers. Augmentez max_execution_time ou désactivez-le pendant le développement.

Exemple Docker

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

Avec php.ini contenant :

php.ini
max_execution_time = 30

Bonnes pratiques

  • Ne définissez jamais max_execution_time = 0 globalement en production sauf si vous disposez d'endpoints SSE ou de long-polling qui nécessitent des connexions indéfinies. Préférez set_time_limit(0) par script.
  • Utilisez des limites plus courtes pour les serveurs d'API. Les API ont des temps de réponse prévisibles. Un max_execution_time de 30 secondes détecte rapidement les requêtes bloquées sans affecter le trafic normal.
  • Combinez avec la limitation de débit. Les délais d'expiration protègent les requêtes individuelles ; la limitation de débit protège contre un volume élevé de requêtes. Ensemble, elles couvrent aussi bien les schémas d'attaque lents que rapides.

Voir aussi