Routing
OxPHP kieruje przychodzące żądania HTTP w jednym z trzech trybów, sterowanych pojedynczą zmienną środowiskową. Każdy tryb odzwierciedla znaną konfigurację try_files z nginx, więc możesz dokładnie przewidzieć, co stanie się z dowolnym URL-em.
Jak to działa
Każde żądanie przechodzi przez wspólny pipeline, zanim uruchomi się logika specyficzna dla danego trybu:
- Filtr ścieżek z kropką — ścieżki zawierające ukryte segmenty (
.git,.env) są blokowane, z wyjątkiem/.well-known/*(RFC 8615) - Odczyt z cache routingu — niedawno rozwiązane URI są zwracane z cache LRU (10 000 wpisów)
- Dekodowanie procentowe + sanityzacja — zakodowane sekwencje takie jak
%2e%2esą dekodowane, a segmenty trawersowania (..,., puste) są usuwane - Blokada well-known PHP — obrona w głąb: skrypty
.phpwewnątrz/.well-known/nigdy się nie wykonują - Klasyfikacja URI — zsanityzowana ścieżka jest jednorazowo klasyfikowana jako
NoExtension,PhplubOtherExtension - Rozdział na tryby — każdy tryb obsługuje trzy rodzaje URI według własnych reguł
- Walidacja dowiązań symbolicznych — każda rozwiązana ścieżka w systemie plików musi po kanonizacji mieścić się wewnątrz katalogu głównego dokumentów
Krokiem kluczowym dla wydajności jest klasyfikacja: sprawdzenie na dysku zasobów statycznych (/style.css, /logo.png) wykonywane jest raz we wspólnej warstwie dla URI typu OtherExtension, dzięki czemu wszystkie trzy tryby ponoszą ten sam koszt.
Konfiguracja
| Zmienna | Domyślnie | Opis |
|---|---|---|
DOCUMENT_ROOT |
/var/www/html/public |
Katalog główny do serwowania plików i skryptów PHP |
ENTRY_FILE |
(nieustawiona) | Pojedynczy kanoniczny skrypt wejściowy. Nieustawiona = Tradycyjny. *.php = Framework. Nie-.php = SPA. Z WORKER_MODE_ENABLED=true = Worker. Przyjmuje ścieżkę bezwzględną lub względną wobec DOCUMENT_ROOT (dozwolone ..); rozwiązana ścieżka musi istnieć |
WORKER_MODE_ENABLED |
false |
Włącza trwały tryb worker. Wymaga, aby ENTRY_FILE wskazywał na skrypt .php |
Przestarzałe zmienne INDEX_FILE i WORKER_FILE są nadal parsowane (z ostrzeżeniem WARN przy starcie) i mapowane na nowy model. Zobacz Konfiguracja → Przestarzałe.
Tryby routingu
Tryby Tradycyjny, Framework i SPA wybierane są przez ENTRY_FILE, gdy WORKER_MODE_ENABLED=false, a każdy z nich mapuje się na równoważną konfigurację try_files w nginx.
Aktywny, gdy ENTRY_FILE nie jest ustawiony (lub jest pusty) oraz WORKER_MODE_ENABLED=false. Równoważna konfiguracja nginx:
location / {
try_files $uri $uri/ /index.php /index.html =404;
}
location ~ \.php$ {
try_files $uri =404; # PATH_INFO splitting enabled
}Kolejność rozwiązywania:
$uri— dokładny plik na dysku → serwuj (lub wykonaj, jeśli.php)$uri/— katalog → poszukajindex.php, a następnieindex.htmlw jego wnętrzu- Podział PATH_INFO — gdy URI zawiera
.php/, prefiks skryptu jest dopasowywany na dysku, a reszta staje sięPATH_INFO(np./api.php/users/42→ skryptapi.php,PATH_INFO=/users/42) /index.php— fallback do kontrolera wejściowego w katalogu głównym/index.html— fallback do statycznego indeksu w katalogu głównym- 404
Przykłady:
| Żądanie | Wynik |
|---|---|
/about.php |
Wykonaj about.php |
/style.css |
Serwuj style.css |
/blog/ (z blog/index.php) |
Wykonaj blog/index.php |
/api.php/users/42 |
Wykonaj api.php z PATH_INFO=/users/42 |
/missing.txt |
Fallback do /index.php |
/some/route |
Fallback do /index.php |
Podział PATH_INFO jest zawsze włączony w trybie Tradycyjnym. Nie ma przełącznika środowiskowego — poprzednia flaga SPLIT_PATH_INFO_ENABLED została usunięta.
Aktywny, gdy ENTRY_FILE=index.php (lub dowolna wartość kończąca się na .php) oraz WORKER_MODE_ENABLED=false. Równoważna konfiguracja nginx:
location ~ \.(?!php$)[a-zA-Z0-9]+$ {
try_files $uri /index.php; # static assets: fall back to front controller
}
location / {
rewrite ^ /index.php last; # everything else → front controller
}
location = /index.php {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass ...;
}Reguły rozwiązywania:
| Rodzaj URI | Zachowanie |
|---|---|
.css, .png, .js, … (dowolne rozszerzenie inne niż php) |
Serwuj plik, jeśli istnieje, w przeciwnym razie przepisz na /index.php |
.php (dowolna ścieżka) |
Przepisz na /index.php |
brak rozszerzenia (/api/users, /) |
Przepisz na /index.php |
PATH_INFO jest ustawiane tylko wtedy, gdy żądanie jawnie wskazuje plik wejściowy z końcowym segmentem (/index.php/extra); dla tras aplikacji oryginalna ścieżka jest odczytywana z REQUEST_URI.
Przykłady:
| Żądanie | Wynik | $_SERVER['PATH_INFO'] |
|---|---|---|
/style.css (istnieje) |
Serwuj style.css |
— |
/style.css (brak) |
Wykonaj index.php |
(brak) |
/api/users |
Wykonaj index.php |
(brak) |
/about.php |
Wykonaj index.php |
(brak) |
/index.php/news/local |
Wykonaj index.php |
/news/local |
/index.php (bezpośrednio) |
Wykonaj index.php |
(brak) |
/ |
Wykonaj index.php |
(brak) |
Dla tras aplikacji oryginalna ścieżka jest udostępniana przez REQUEST_URI, więc Twój router odczytuje $_SERVER['REQUEST_URI'], aby ją rozdysponować. Bezpośredni dostęp do /index.php nie jest już blokowany — przepisanie jest idempotentne, więc odwołanie się do niego bezpośrednio daje ten sam wynik co odwołanie do /.
Brakujący zasób statyczny trafia do kontrolera wejściowego zamiast zwracać szybkie 404 — to samo zachowanie try_files $uri /index.php, które Laravel i Symfony dostarczają domyślnie, więc Twoja aplikacja renderuje własną stronę 404 dla brakujących zasobów. Kompromis polega na tym, że każde żądanie do nieistniejącego zasobu uruchamia teraz PHP; jeśli kontroler wejściowy nadal nie ma nic do zaserwowania (brakuje samego /index.php), żądanie zwraca twarde 404.
Aktywny, gdy ENTRY_FILE=index.html (lub dowolna wartość inna niż .php) oraz WORKER_MODE_ENABLED=false. Równoważna konfiguracja nginx:
location ~ \.php$ {
try_files $uri =404; # PHP: file must exist, no fallback
}
location ~ \. {
try_files $uri =404; # other extensions: hard 404 if missing
}
location / {
try_files /index.html =404; # no-extension paths: straight to index.html
}Reguły rozwiązywania:
| Rodzaj URI | Zachowanie |
|---|---|
.php |
Wykonaj plik, jeśli istnieje, w przeciwnym razie twarde 404 |
.css, .png, … (dowolne inne rozszerzenie) |
Serwuj plik, jeśli istnieje, w przeciwnym razie twarde 404 |
brak rozszerzenia (/dashboard, /api/users, /) |
Serwuj /index.html bezpośrednio — bez sprawdzania $uri na dysku |
Przykłady:
| Żądanie | Wynik |
|---|---|
/style.css (istnieje) |
Serwuj style.css |
/style.css (brak) |
404 |
/dashboard |
Serwuj /index.html |
/users/42/edit |
Serwuj /index.html |
/api.php (istnieje) |
Wykonaj api.php |
/api.php (brak) |
404 |
/index.html (bezpośrednio) |
Serwuj index.html |
Dwie zależności warte podkreślenia:
- Ścieżki bez rozszerzenia pomijają dysk — tryb SPA nigdy nie pyta „czy
/dashboardistnieje na dysku?" Zawsze zwraca indeks. Jest to poprawne dla routerów działających po stronie klienta i pozwala uniknąć zbędnych wywołaństat(). - Brakujące pliki statyczne dają twarde 404, a nie przejście dalej — brakujący
/style.cssnie serwuje po cichuindex.html. Wychwytuje to wcześnie zepsute odwołania do zasobów, zamiast zwracać HTML tam, gdzie JS spodziewał się CSS.
Ponieważ tryb SPA wykonuje istniejące pliki .php bezpośrednio, obowiązuje PHP_DENY_PATHS — użyj go, aby zablokować wykonywanie wewnątrz katalogów zapisywalnych, takich jak /uploads.
Tryb Worker
Tryb worker aktywuje się, gdy WORKER_MODE_ENABLED=true, a ENTRY_FILE wskazuje na skrypt .php. Router serwuje zasoby statyczne z dysku i kieruje każde inne żądanie do workera ENTRY_FILE — to worker jest jedynym kontrolerem wejściowym.
| Rodzaj URI | Zachowanie |
|---|---|
Zasoby statyczne (.css, .png, … — dowolne rozszerzenie inne niż .php) |
Serwowane bezpośrednio z dysku, jeśli są obecne; brakujący zasób przechodzi dalej do workera ENTRY_FILE (nie twarde 404) |
Wszystko inne (URI .php, ścieżki bez rozszerzenia, /) |
Kierowane do workera ENTRY_FILE |
Dowolne pliki .php w katalogu głównym dokumentów nigdy nie są wykonywane bezpośrednio w trybie worker — żądanie do /about.php dociera do callbacku workera jak każda inna trasa, nawet jeśli about.php istnieje na dysku. Nie ma tu również wyszukiwania indeksu katalogu ani fallbacku do głównego index.php; worker sam obsługuje te żądania.
Dwa wyjątki, oba będące zabezpieczeniami na poziomie serwera, które działają przed rozdziałem na tryby: ścieżki z segmentem-kropką (/.git/config, /.env, samo /.well-known) są odrzucane przez blokowanie ścieżek z kropką, a URI .php pod /.well-known/ są odrzucane jako obrona w głąb. Oba zwracają 404 i nigdy nie docierają do workera.
Walidacja przy starcie odrzuca dwie kombinacje:
WORKER_MODE_ENABLED=truebezENTRY_FILE→WORKER_MODE_ENABLED=true requires ENTRY_FILE to be set.WORKER_MODE_ENABLED=truezENTRY_FILEinnym niż.php→WORKER_MODE_ENABLED=true requires a .php ENTRY_FILE.
Zobacz Tryb Worker, aby poznać pełne szczegóły konfiguracji.
Zachowanie PATH_INFO
$_SERVER['PATH_INFO'] jest wypełniane różnie w zależności od trybu:
| Tryb | Kiedy ustawiane | Wartość |
|---|---|---|
| Traditional | Tylko gdy URI zawiera .php/ (podział PATH_INFO) |
Końcówka za segmentem skryptu, np. /users/42 |
| Framework | Tylko dla jawnego żądania /index.php/extra |
Końcówka za plikiem wejściowym, np. /news |
| SPA | Nigdy | (PHP jest wywoływane tylko dla dokładnych plików .php; brak PATH_INFO) |
PATH_INFO podąża za semantyką CGI: jest obecne tylko wtedy, gdy SCRIPT_NAME (wykonany skrypt) stanowi dosłowny prefiks ścieżki żądania. Przepisanie do kontrolera wejściowego, którego URL nie nazywa — trasa aplikacji, indeks katalogu, fallback po nietrafionym zasobie statycznym — nie niesie żadnego PATH_INFO; zamiast tego odczytaj REQUEST_URI. W trybie Tradycyjnym poprzednia zmienna środowiskowa SPLIT_PATH_INFO_ENABLED została usunięta.
Bezpieczeństwo ścieżek
OxPHP stosuje wiele warstw ochrony, aby zapobiec trawersowaniu katalogów, ujawnianiu ukrytych plików i atakom polegającym na ucieczce przez dowiązania symboliczne:
- Dekodowanie procentowe działa przed sanityzacją, więc zakodowane próby trawersowania, takie jak
/%2e%2e/etc/passwd, są wychwytywane - Filtrowanie segmentów usuwa segmenty
..,.oraz puste z rozwiązanej ścieżki - Walidacja dowiązań symbolicznych kanonizuje każdą rozwiązaną ścieżkę i weryfikuje, że pozostaje ona wewnątrz katalogu głównego dokumentów. Dowiązania symboliczne wskazujące poza serwowany katalog są blokowane
- Blokowanie ścieżek z kropką blokuje każdy segment ścieżki zaczynający się od
.(np./.git/config,/.env), z wyjątkiem/.well-known/*zgodnie z RFC 8615 - Blokada well-known PHP — nawet przy wyjątku dla ścieżek z kropką, skrypty
.phppod/.well-known/nigdy nie są wykonywane (obrona w głąb) - Lista blokowania wykonywania PHP — w trybach bezpośredniego mapowania (Tradycyjny i SPA)
PHP_DENY_PATHSblokuje wykonywanie.phpdla skonfigurowanych wzorców glob (np./uploads/**lub pojedynczy plik jak/admin/legacy.php) przed jakimkolwiek I/O dyskowym. Zobacz Lista blokowania wykonywania PHP
Jeśli katalog główny dokumentów nie istnieje przy starcie, serwer kończy działanie z błędem krytycznym. Ochrona przed ucieczką przez dowiązania symboliczne wymaga prawidłowej, rozwiązywalnej ścieżki katalogu głównego dokumentów.
Rozwiązywanie problemów
Wszystkie żądania zwracają 404 w trybie Tradycyjnym
Sprawdź, czy index.php lub index.html istnieje w katalogu głównym dokumentów. Łańcuch try_files w trybie Tradycyjnym korzysta z nich jako fallbacku — jeśli obu brakuje i żaden plik nie pasuje do URL-a, otrzymasz 404.
docker exec <container> ls /var/www/html/publicBrakujący zasób statyczny zwraca 404 zamiast powłoki SPA
W trybie SPA jest to zamierzone: brakujący /style.css daje twarde 404, a nie ciche przejście do index.html, co pozwala wcześnie wychwycić zepsute odwołania do zasobów. W trybach Framework i Tradycyjnym brakujący plik statyczny trafia do kontrolera wejściowego (/index.php), więc router Twojej aplikacji renderuje stronę 404. Użyj trybu SPA, jeśli chcesz twardych 404 dla brakujących zasobów.
Bezpośredni /index.php nie zwraca już 404
W trybie Framework bezpośredni dostęp do kontrolera wejściowego jest teraz dozwolony (przepisanie na /index.php jest idempotentne). Jeśli wcześniej polegałeś na 404, aby wykrywać bezpośrednie odwołania, przejdź na sprawdzanie REQUEST_URI z wnętrza kontrolera.
PATH_INFO jest puste w trybie Framework
Jest to oczekiwane dla tras aplikacji. Tryb Framework podąża za semantyką CGI: PATH_INFO jest ustawiane tylko wtedy, gdy żądanie jawnie wskazuje plik wejściowy z końcowym segmentem (/index.php/news → /news). Dla zwykłej trasy aplikacji, takiej jak /users/42, kontroler wejściowy jest osiągany przez wewnętrzne przepisanie, którego trasa nie nazywa, więc PATH_INFO jest nieobecne — odczytaj ścieżkę z $_SERVER['REQUEST_URI']. (Jeśli ENTRY_FILE nie kończy się na .php, OxPHP wybiera tryb SPA, który nigdy nie wypełnia PATH_INFO.)
Dowiązanie symboliczne wewnątrz katalogu głównego dokumentów zwraca 404
Dowiązania symboliczne wskazujące poza katalog główny dokumentów są blokowane z założenia. Przenieś docelową zawartość do wnętrza katalogu głównego dokumentów lub podmontuj ją jako katalog pod właściwą ścieżką.
Przykład 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.phpZobacz też
- Pliki statyczne — wykrywanie MIME, cache'owanie i strumieniowanie serwowanych plików
- Tryb Worker — trwałe procesy PHP i routing w trybie worker
- Dokumentacja konfiguracji — pełna lista zmiennych środowiskowych