Fichiers statiques
OxPHP sert les fichiers statiques directement depuis la racine du document, sans invoquer PHP. Chaque fichier est servi avec une détection automatique du type MIME, un cache en mémoire pour un accès répété rapide, et une gestion complète du cache HTTP : ETags, requêtes conditionnelles et requêtes Range pour les téléchargements partiels.
Fonctionnement
Lorsqu'une requête correspond à un fichier statique :
- Fichier trouvé — la couche de routage résout le chemin de l'URL vers un fichier sur le disque
- Détection MIME — le type de contenu est déterminé à partir de l'extension du fichier
- Vérification du cache — le cache de fichiers est consulté avant de toucher au système de fichiers
- Vérification conditionnelle — si la requête porte un en-tête
If-None-MatchouIf-Modified-Since, OxPHP évalue la condition et peut renvoyer304 Not Modifiedsans transmettre de corps - Vérification Range — si une requête GET ou HEAD porte un en-tête
Range, OxPHP répond avec206 Partial Content: GET ne reçoit que la plage d'octets demandée, HEAD reçoit les mêmes en-têtes de plage sans corps - Réponse — les fichiers jusqu'à 1 Mio sont servis depuis le cache en mémoire ; les fichiers plus volumineux sont diffusés en flux directement depuis le disque
Configuration
| Variable | Valeur par défaut | Description |
|---|---|---|
STATIC_MAX_AGE |
30d |
Cache-Control: max-age pour les fichiers statiques. Accepte 30s, 5m, 2h, 30d, 1w, 1y, un simple nombre de secondes (par ex. 3600), ou off pour désactiver entièrement les en-têtes de cache. Remplace STATIC_CACHE_TTL, déprécié. |
STATIC_REVALIDATE |
off |
Réglez sur on pour activer la revalidation par mtime sur le cache de contenu en mémoire (revérifie chaque fichier au plus une fois toutes les 3 secondes ; les modifications deviennent visibles dans cette fenêtre). Remplace STATIC_CACHE, déprécié (où off avait le sens inverse). |
Détection MIME
Les types MIME sont déterminés automatiquement à partir de l'extension du fichier. Si aucun type ne peut être déterminé, le serveur se rabat sur application/octet-stream. Les correspondances courantes incluent :
| Extension | Content-Type |
|---|---|
.html |
text/html |
.css |
text/css |
.js |
text/javascript |
.json |
application/json |
.png |
image/png |
.svg |
image/svg+xml |
.woff2 |
font/woff2 |
Mise en cache des fichiers
OxPHP utilise un cache en mémoire pour réduire les I/O disque des fichiers fréquemment demandés :
- Les fichiers jusqu'à 1 Mio (1 048 576 octets) sont lus en mémoire et mis en cache. Le budget total du cache est de 64 Mio (67 108 864 octets). Lorsque ce budget est dépassé, les entrées les moins récemment utilisées sont évincées pour libérer de la place.
- Les fichiers de plus de 1 Mio sont toujours diffusés en flux directement depuis le disque. L'en-tête
Content-Lengthest renseigné à partir des métadonnées du fichier, afin que le client connaisse d'emblée la taille totale.
Le cache de fichiers est peuplé lors de la première requête vers chaque fichier et conservé lors des requêtes suivantes. Par défaut, les entrées du cache persistent jusqu'à ce qu'elles soient évincées par la politique LRU.
Revalidation du contenu
Réglez STATIC_REVALIDATE=on pour activer la revalidation basée sur le mtime. Dans ce mode, le serveur revérifie l'heure de modification d'un fichier mis en cache avec un appel système stat() au plus une fois toutes les 3 secondes par fichier, et non à chaque requête. Si le fichier a changé sur le disque, l'entrée périmée est évincée et le fichier est relu automatiquement. Dans la fenêtre de 3 secondes, une entrée mise en cache est servie directement depuis la mémoire sans appel système, si bien que le coût est amorti plutôt que payé à chaque requête. Les modifications sur le disque deviennent visibles en 3 secondes.
Activez STATIC_REVALIDATE=on en développement pour voir les modifications de fichiers sans redémarrer le serveur. Laissez-le désactivé en production (la valeur par défaut off) pour un débit maximal sans aucune surcharge d'appel système par requête.
Cache HTTP
Cache-Control
Lorsque STATIC_MAX_AGE est défini (la valeur par défaut est 30d), chaque réponse de fichier statique inclut un en-tête Cache-Control :
Cache-Control: public, max-age=2592000La valeur max-age correspond au TTL converti en secondes. Réglez STATIC_MAX_AGE=off pour omettre entièrement cet en-tête.
ETag et Last-Modified
Chaque réponse de fichier statique inclut :
- ETag — un ETag fort au format
"<size>-<mtime_hex>", dérivé de la taille du fichier et de sa dernière heure de modification. Un validateur fort satisfait aussiIf-Range, si bien que les téléchargements interrompus peuvent reprendre en toute sécurité. Lorsqu'une réponse est servie compressée en brotli, le tag est affaibli enW/"…"— les octets compressés constituent une représentation différente, et un tag faible permet encore de revalider (304) mais empêche de mélanger des fragments compressés et non compressés lors d'une reprise. - Last-Modified — une date HTTP au format RFC 7231 basée sur l'heure de modification du fichier
Ces en-têtes permettent aux navigateurs et aux CDN de valider les copies mises en cache sans retélécharger le fichier.
Requêtes conditionnelles (304)
OxPHP évalue les en-têtes de requête conditionnelle pour éviter d'envoyer un contenu de fichier inchangé :
- If-None-Match — le client envoie l'ETag qu'il a en cache. S'il correspond au fichier actuel, OxPHP renvoie
304 Not Modifiedsans corps. - If-Modified-Since — le client envoie un horodatage. Si le fichier n'a pas été modifié depuis cette date, OxPHP renvoie 304.
If-None-Match a la priorité sur If-Modified-Since conformément à la RFC 7232. Pour les fichiers déjà présents dans le cache en mémoire, la vérification conditionnelle s'effectue sans aucune I/O disque.
Requêtes Range (206)
Les réponses de fichiers statiques annoncent Accept-Ranges: bytes, et les requêtes GET avec un en-tête Range à plage unique ne reçoivent que les octets demandés :
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=1048576-
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-52428799/52428800
Content-Length: 51380224Cela permet le déplacement dans <video>/<audio> dans les navigateurs, les téléchargements reprenables (wget -c, gestionnaires de téléchargement) et le chargement partiel de PDF. Les trois formes de plage de la RFC 9110 sont prises en charge : bytes=N-M, bytes=N- (de l'offset jusqu'à la fin) et bytes=-N (les N derniers octets).
- Une plage qui ne peut être satisfaite (début au-delà de la fin du fichier) renvoie
416 Range Not SatisfiableavecContent-Range: bytes */<size>. - If-Range est honoré : lorsque le client envoie l'ETag (ou la date
Last-Modified) de sa copie partielle et que le fichier a changé depuis, OxPHP renvoie la réponse complète200plutôt qu'un fragment incohérent. La forme date n'est acceptée qu'une fois la seconde de modification du fichier entièrement écoulée — un fichier tout juste écrit pourrait changer à nouveau dans la même seconde sans faire évoluer la date, il ne s'agit donc pas encore d'un validateur fort (RFC 9110). - Les requêtes comportant plusieurs plages (
bytes=0-1,4-5) reçoivent le fichier complet en200 OK— les réponsesmultipart/byterangesne sont pas générées. - Les requêtes HEAD avec un en-tête
Rangereçoivent les mêmes en-têtes206/Content-Rangeque GET, sans corps, à l'image de nginx et Apache. - Les plages et la compression sont mutuellement exclusives. Pour les clients qui acceptent le brotli, la gestion des plages est désactivée sur les représentations qui seraient servies compressées, et les réponses compressées n'annoncent pas
Accept-Ranges— un téléchargement repris pourrait sinon coller des octets non compressés sur un préfixe compressé. Seuls les fichiers servis depuis le cache en mémoire (jusqu'à 1 Mio) sont un jour compressés, si bien que les plages fonctionnent toujours pour le contenu qui en a réellement besoin : vidéo, archives, images et tout fichier diffusé en flux depuis le disque. Les réponses pour les fichiers éligibles à la compression portent toujoursVary: Accept-Encoding— même servies non compressées — afin que les caches partagés gardent les variantes distinctes. - Les réponses
206ne sont jamais compressées, et la gestion des plages ne s'applique pas aux réponses PHP — uniquement aux fichiers statiques.
Exemple : reprendre un téléchargement interrompu avec curl :
curl -C - -O https://example.com/dist/app-installer.dmgDésactiver la mise en cache
Il existe deux couches de cache indépendantes et une variable pour chacune :
| Variable | Contrôle | Effet de off |
|---|---|---|
STATIC_MAX_AGE=off |
Cache navigateur (en-têtes HTTP) | Aucun en-tête Cache-Control, ETag ou Last-Modified envoyé |
STATIC_REVALIDATE=on |
Cache serveur en mémoire | Revérifie le mtime du fichier au plus une fois toutes les 3 s par fichier ; les entrées périmées sont évincées automatiquement |
En développement, réglez STATIC_REVALIDATE=on pour que le serveur serve toujours un contenu à jour. Vous pouvez éventuellement régler aussi STATIC_MAX_AGE=off pour empêcher entièrement la mise en cache par le navigateur.
Dépannage
Le serveur continue de servir des fichiers périmés
Par défaut, le cache de contenu en mémoire ne vérifie pas si les fichiers ont changé sur le disque. Réglez STATIC_REVALIDATE=on en développement pour activer la revalidation par mtime — le serveur détecte automatiquement les modifications de fichiers (dans un délai de 3 secondes).
Le navigateur continue de servir des fichiers périmés
Si le serveur renvoie un contenu à jour mais que le navigateur affiche encore l'ancienne version, c'est le propre cache du navigateur qui est en cause. Réglez STATIC_MAX_AGE=off pour cesser d'envoyer des en-têtes de cache, ou utilisez le rechargement forcé de votre navigateur (Shift+F5 ou Cmd+Shift+R).
Les fichiers sont servis avec `application/octet-stream`
OxPHP utilise l'extension du fichier pour déterminer le type MIME. Si une extension est absente ou non reconnue, il se rabat sur application/octet-stream. Ajoutez la bonne extension à votre fichier, ou assurez-vous que votre framework définit explicitement l'en-tête Content-Type dans les réponses PHP.
Les gros fichiers semblent lents
Les fichiers de plus de 1 Mio sont diffusés en flux depuis le disque à chaque requête et ne sont pas mis en cache en mémoire. Pour les très gros fichiers, placez un CDN devant OxPHP afin de les mettre en cache en périphérie. Vous pouvez aussi restructurer vos ressources pour que les fichiers fréquemment servis restent sous la barre des 1 Mio.
Des réponses 304 sont renvoyées là où vous attendez 200
Un 304 signifie que le client possède déjà la version actuelle. C'est le comportement attendu. Si vous devez forcer une réponse fraîche en développement, réglez STATIC_MAX_AGE=off pour cesser d'envoyer les en-têtes ETag et Last-Modified.
Exemple Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
- DOCUMENT_ROOT=/var/www/html/public
- ENTRY_FILE=index.php
- STATIC_MAX_AGE=1yBonnes pratiques
- Utilisez de longs TTL avec des noms de fichiers à cache-busting en production (par ex.
app.a1b2c3.js). RéglezSTATIC_MAX_AGE=1ypour une mise en cache maximale par le navigateur et le CDN. - Réglez
STATIC_REVALIDATE=onen développement afin que le serveur détecte automatiquement les modifications de fichiers. Vous pouvez éventuellement régler aussiSTATIC_MAX_AGE=offpour contourner le cache du navigateur. - Placez un CDN devant OxPHP pour les sites à fort trafic. Les en-têtes
ETag,Last-ModifiedetCache-Controlfonctionnent avec tous les grands fournisseurs de CDN. - Laissez votre outil de build gérer le hachage des ressources. Des frameworks comme Vite et Laravel Mix génèrent automatiquement des noms de fichiers hachés, ce qui rend les longs TTL de cache sûrs.
Voir aussi
- Compression — Compression Brotli pour les réponses de fichiers statiques compressibles
- Routage — comment les chemins d'URL sont résolus vers des fichiers sur le disque
- Référence de configuration — liste complète des variables d'environnement