Pliki statyczne
OxPHP serwuje pliki statyczne prosto z katalogu głównego dokumentów, bez uruchamiania PHP. Każdy plik jest serwowany z automatycznym wykrywaniem typu MIME, buforem w pamięci zapewniającym szybki dostęp przy powtarzających się żądaniach oraz pełnym buforowaniem HTTP: nagłówkami ETag, żądaniami warunkowymi i żądaniami Range dla pobrań częściowych.
Jak to działa
Gdy żądanie pasuje do pliku statycznego:
- Dopasowanie pliku — warstwa routingu rozwiązuje ścieżkę URL do pliku na dysku
- Wykrywanie typu MIME — typ zawartości jest ustalany na podstawie rozszerzenia pliku
- Sprawdzenie bufora — bufor plików jest sprawdzany przed sięgnięciem do systemu plików
- Sprawdzenie warunkowe — jeśli żądanie zawiera
If-None-MatchlubIf-Modified-Since, OxPHP ocenia warunek i może zwrócić304 Not Modifiedbez wysyłania ciała odpowiedzi - Sprawdzenie zakresu — jeśli żądanie GET lub HEAD zawiera nagłówek
Range, OxPHP odpowiada kodem206 Partial Content: GET otrzymuje tylko żądany zakres bajtów, a HEAD te same nagłówki zakresu bez ciała - Odpowiedź — pliki do 1 MiB są serwowane z bufora w pamięci; większe pliki są strumieniowane bezpośrednio z dysku
Konfiguracja
| Zmienna | Wartość domyślna | Opis |
|---|---|---|
STATIC_MAX_AGE |
30d |
Cache-Control: max-age dla plików statycznych. Przyjmuje 30s, 5m, 2h, 30d, 1w, 1y, samą liczbę sekund (np. 3600) lub off, aby całkowicie wyłączyć nagłówki buforowania. Zastępuje przestarzałą zmienną STATIC_CACHE_TTL. |
STATIC_REVALIDATE |
off |
Ustaw na on, aby włączyć rewalidację na podstawie mtime dla bufora zawartości w pamięci (ponowne sprawdzenie każdego pliku najwyżej raz na 3 sekundy; zmiany stają się widoczne w tym oknie czasowym). Zastępuje przestarzałą zmienną STATIC_CACHE (w której off miało odwrotne znaczenie). |
Wykrywanie typu MIME
Typy MIME są ustalane automatycznie na podstawie rozszerzenia pliku. Jeśli nie da się ustalić typu, serwer używa domyślnie application/octet-stream. Do typowych odwzorowań należą:
| Rozszerzenie | Content-Type |
|---|---|
.html |
text/html |
.css |
text/css |
.js |
text/javascript |
.json |
application/json |
.png |
image/png |
.svg |
image/svg+xml |
.woff2 |
font/woff2 |
Buforowanie plików
OxPHP używa bufora w pamięci, aby ograniczyć operacje I/O na dysku dla często żądanych plików:
- Pliki do 1 MiB (1 048 576 bajtów) są wczytywane do pamięci i buforowane. Całkowity budżet bufora wynosi 64 MiB (67 108 864 bajtów). Po przekroczeniu budżetu najdawniej używane wpisy są usuwane, aby zwolnić miejsce.
- Pliki większe niż 1 MiB są zawsze strumieniowane bezpośrednio z dysku. Nagłówek
Content-Lengthjest ustawiany na podstawie metadanych pliku, dzięki czemu klient zna z góry całkowity rozmiar.
Bufor plików jest wypełniany przy pierwszym żądaniu każdego pliku i utrzymywany między kolejnymi żądaniami. Domyślnie wpisy bufora pozostają, dopóki nie zostaną usunięte przez politykę LRU.
Rewalidacja zawartości
Ustaw STATIC_REVALIDATE=on, aby włączyć rewalidację na podstawie mtime. W tym trybie serwer ponownie sprawdza czas modyfikacji buforowanego pliku wywołaniem systemowym stat() najwyżej raz na 3 sekundy dla każdego pliku, a nie przy każdym żądaniu. Jeśli plik zmienił się na dysku, nieaktualny wpis jest usuwany, a plik zostaje automatycznie wczytany ponownie. W oknie 3-sekundowym buforowany wpis jest serwowany prosto z pamięci bez wywołania systemowego, więc koszt jest amortyzowany, a nie płacony przy każdym żądaniu. Zmiany na dysku stają się widoczne w ciągu 3 sekund.
Ustaw STATIC_REVALIDATE=on podczas pracy deweloperskiej, aby widzieć zmiany w plikach bez restartowania serwera. W środowisku produkcyjnym pozostaw tę zmienną nieustawioną (domyślne off), aby uzyskać maksymalną przepustowość przy zerowym narzucie wywołań systemowych na żądanie.
Buforowanie HTTP
Cache-Control
Gdy ustawiona jest zmienna STATIC_MAX_AGE (domyślnie 30d), każda odpowiedź z plikiem statycznym zawiera nagłówek Cache-Control:
Cache-Control: public, max-age=2592000Wartość max-age to TTL przeliczony na sekundy. Ustaw STATIC_MAX_AGE=off, aby całkowicie pominąć ten nagłówek.
ETag i Last-Modified
Każda odpowiedź z plikiem statycznym zawiera:
- ETag — silny ETag w formacie
"<size>-<mtime_hex>", wyprowadzony z rozmiaru pliku i czasu ostatniej modyfikacji. Silny walidator spełnia też warunekIf-Range, dzięki czemu przerwane pobrania można bezpiecznie wznowić. Gdy odpowiedź jest serwowana skompresowana algorytmem brotli, znacznik jest osłabiany do postaciW/"…"— skompresowane bajty to inna reprezentacja, a słaby znacznik nadal umożliwia rewalidację (304), ale zapobiega mieszaniu skompresowanych i nieskompresowanych fragmentów przy wznawianiu. - Last-Modified — data HTTP zgodna z RFC 7231, oparta na czasie modyfikacji pliku
Nagłówki te pozwalają przeglądarkom i sieciom CDN walidować buforowane kopie bez ponownego pobierania pliku.
Żądania warunkowe (304)
OxPHP ocenia nagłówki żądań warunkowych, aby uniknąć wysyłania niezmienionej zawartości pliku:
- If-None-Match — klient wysyła ETag, który ma w buforze. Jeśli pasuje on do bieżącego pliku, OxPHP zwraca
304 Not Modifiedbez ciała odpowiedzi. - If-Modified-Since — klient wysyła znacznik czasu. Jeśli plik nie był modyfikowany od tego momentu, OxPHP zwraca 304.
If-None-Match ma pierwszeństwo przed If-Modified-Since zgodnie z RFC 7232. Dla plików znajdujących się już w buforze w pamięci sprawdzenie warunkowe przebiega bez żadnych operacji I/O na dysku.
Żądania Range (206)
Odpowiedzi z plikami statycznymi ogłaszają Accept-Ranges: bytes, a żądania GET z pojedynczym zakresem w nagłówku Range otrzymują tylko żądane bajty:
GET /videos/intro.mp4 HTTP/1.1
Range: bytes=1048576-
HTTP/1.1 206 Partial Content
Content-Range: bytes 1048576-52428799/52428800
Content-Length: 51380224Umożliwia to przewijanie elementów <video>/<audio> w przeglądarkach, wznawialne pobrania (wget -c, menedżery pobierania) oraz częściowe wczytywanie plików PDF. Obsługiwane są wszystkie trzy formy zakresu z RFC 9110: bytes=N-M, bytes=N- (od przesunięcia do końca) oraz bytes=-N (ostatnie N bajtów).
- Zakres, którego nie da się spełnić (początek poza końcem pliku), zwraca
416 Range Not Satisfiablez nagłówkiemContent-Range: bytes */<size>. - If-Range jest respektowany: gdy klient wysyła ETag (lub datę
Last-Modified) swojej częściowej kopii, a plik zmienił się od tamtej pory, OxPHP zwraca pełną odpowiedź200zamiast niepasującego fragmentu. Forma z datą jest akceptowana dopiero po pełnym upływie sekundy modyfikacji pliku — świeżo zapisany plik mógłby zmienić się ponownie w tej samej sekundzie bez zmiany daty, więc data nie jest jeszcze silnym walidatorem (RFC 9110). - Żądania z wieloma zakresami (
bytes=0-1,4-5) otrzymują cały plik jako200 OK— odpowiedzimultipart/byterangesnie są generowane. - Żądania HEAD z nagłówkiem
Rangeotrzymują te same nagłówki206/Content-Rangeco GET, ale bez ciała, zgodnie z zachowaniem nginx i Apache. - Zakresy i kompresja wzajemnie się wykluczają. Dla klientów akceptujących brotli obsługa zakresów jest wyłączana dla reprezentacji, które byłyby serwowane skompresowane, a skompresowane odpowiedzi nie ogłaszają
Accept-Ranges— w przeciwnym razie wznowione pobranie mogłoby doszyć nieskompresowane bajty do skompresowanego prefiksu. Kompresowane są wyłącznie pliki serwowane z bufora w pamięci (do 1 MiB), więc zakresy zawsze działają dla zawartości, która faktycznie ich potrzebuje: wideo, archiwów, obrazów i każdego pliku strumieniowanego z dysku. Odpowiedzi dla plików kwalifikujących się do kompresji zawsze niosą nagłówekVary: Accept-Encoding— nawet gdy są serwowane nieskompresowane — dzięki czemu współdzielone bufory rozróżniają warianty. - Odpowiedzi
206nigdy nie są kompresowane, a obsługa zakresów nie dotyczy odpowiedzi PHP — tylko plików statycznych.
Przykład: wznowienie przerwanego pobrania za pomocą curl:
curl -C - -O https://example.com/dist/app-installer.dmgWyłączanie buforowania
Istnieją dwie niezależne warstwy bufora i jedna zmienna dla każdej z nich:
| Zmienna | Kontroluje | Efekt off |
|---|---|---|
STATIC_MAX_AGE=off |
Bufor przeglądarki (nagłówki HTTP) | Nie są wysyłane nagłówki Cache-Control, ETag ani Last-Modified |
STATIC_REVALIDATE=on |
Bufor serwera w pamięci | Ponowne sprawdzanie mtime pliku najwyżej raz na 3 s dla każdego pliku; nieaktualne wpisy usuwane automatycznie |
W środowisku deweloperskim ustaw STATIC_REVALIDATE=on, aby serwer zawsze serwował świeżą zawartość. Opcjonalnie ustaw też STATIC_MAX_AGE=off, aby całkowicie wyłączyć buforowanie w przeglądarce.
Rozwiązywanie problemów
Serwer wciąż serwuje nieaktualne pliki
Domyślnie bufor zawartości w pamięci nie sprawdza, czy pliki zmieniły się na dysku. Ustaw STATIC_REVALIDATE=on podczas pracy deweloperskiej, aby włączyć rewalidację na podstawie mtime — serwer automatycznie wykrywa zmiany plików (w ciągu 3 sekund).
Przeglądarka wciąż serwuje nieaktualne pliki
Jeśli serwer zwraca świeżą zawartość, a przeglądarka nadal pokazuje starą wersję, winowajcą jest własny bufor przeglądarki. Ustaw STATIC_MAX_AGE=off, aby przestać wysyłać nagłówki buforowania, albo użyj twardego przeładowania w przeglądarce (Shift+F5 lub Cmd+Shift+R).
Pliki są serwowane z typem `application/octet-stream`
OxPHP ustala typ MIME na podstawie rozszerzenia pliku. Jeśli rozszerzenia brakuje lub nie jest rozpoznane, używany jest domyślnie application/octet-stream. Dodaj poprawne rozszerzenie do pliku albo zadbaj o to, aby framework jawnie ustawiał nagłówek Content-Type w odpowiedziach PHP.
Duże pliki wydają się wolne
Pliki większe niż 1 MiB są strumieniowane z dysku przy każdym żądaniu i nie są buforowane w pamięci. W przypadku bardzo dużych plików umieść przed OxPHP sieć CDN, aby buforowała je na brzegu. Alternatywnie zrestrukturyzuj zasoby tak, aby często serwowane pliki mieściły się poniżej 1 MiB.
Zwracane są odpowiedzi 304, gdy oczekujesz 200
Kod 304 oznacza, że klient ma już bieżącą wersję. To poprawne zachowanie. Jeśli podczas pracy deweloperskiej musisz wymusić świeżą odpowiedź, ustaw STATIC_MAX_AGE=off, aby przestać wysyłać nagłówki ETag i Last-Modified.
Przykład dla Dockera
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=1yDobre praktyki
- Używaj długich TTL wraz z nazwami plików odpornymi na buforowanie (cache-busting) w środowisku produkcyjnym (np.
app.a1b2c3.js). UstawSTATIC_MAX_AGE=1y, aby uzyskać maksymalne buforowanie w przeglądarce i sieci CDN. - Ustaw
STATIC_REVALIDATE=onpodczas pracy deweloperskiej, aby serwer automatycznie wykrywał zmiany plików. Opcjonalnie ustaw teżSTATIC_MAX_AGE=off, aby pominąć buforowanie w przeglądarce. - Umieść sieć CDN przed OxPHP w przypadku witryn o dużym ruchu. Nagłówki
ETag,Last-ModifiediCache-Controlwspółpracują ze wszystkimi głównymi dostawcami CDN. - Pozwól narzędziu budującemu zająć się hashowaniem zasobów. Frameworki takie jak Vite i Laravel Mix automatycznie generują nazwy plików z hashami, dzięki czemu długie TTL bufora są bezpieczne.
Zobacz również
- Kompresja — kompresja Brotli dla kompresowalnych odpowiedzi z plikami statycznymi
- Routing — jak ścieżki URL są rozwiązywane do plików na dysku
- Dokumentacja konfiguracji — pełna lista zmiennych środowiskowych