Wydania

Changelog

Każda istotna zmiana w serwerze, API PHP i obrazach — wyselekcjonowane najważniejsze punkty każdego wydania. Pełna, nieskrócona historia znajduje się w CHANGELOG.md.

najnowsze v0.13.0 · 2026-10-08 · 13 wydań · AGPL-3.0 CHANGELOG.md →
oxphp — releases bash
$ docker pull ghcr.io/oxphp/oxphp:0.13.0
0.13.0: Pulling from oxphp/oxphp
Status: Image is up to date
# PHP 8.4 variant:
$ docker pull ghcr.io/oxphp/oxphp:0.13.0-php8.4
v0.13.0
2026-10-08
LATEST
⚠ breaking changes
Release notes →
BREAKING

Tryb worker: max_execution_time per żądanie — Żądanie, które worker przejmuje, mając w toku inne, dostaje teraz własny limit, liczony od chwili przejęcia, a set_time_limit() wpływa tylko na żądanie, które je wywołuje. Strumień SSE lub long polling, który utrzymywał się dzięki wspólnemu timerowi, musi sam wywołać set_time_limit(0); obraz wydania nie zawiera php.ini, więc limit wynosi 30 s.

RUNTIME_HOOKS=1, true i all obejmują net — Po aktualizacji nawiązywanie połączeń, uzgadnianie połączeń i odczyty TLS, zapisy czekające na miejsce w buforze oraz odczyty unix://, udp:// i udg:// parkują fiber bez żadnych zmian po Twojej stronie. runtime_hooks zgłasza teraz ["sleep","streams","net"], a strumień współdzielony przez fibery jest chroniony bez względu na transport. RUNTIME_HOOKS=sleep,streams zachowuje dawny zestaw.

Shareable jest zarezerwowany dla typów Shared — Klasa PHP nie może już implementować OxPHP\Shared\Shareable ani rozszerzać typu Shared, który jest teraz final; oba przypadki kończą się błędem krytycznym przy deklaracji. Przekazanie takiego obiektu do innego wątku powodowało dotąd awarię serwera. createMock() z PHPUnit dla typu Shared lub dla Shareable kończy się teraz niepowodzeniem; użyj własnego interfejsu znacznikowego.

Usunięte aliasy obserwowalności Shared\* — oxphp_shared_channel_pending i oxphp_shared_pool_size znikają z /metrics, a pending/size z type_specific w /__ox_shared/entry, razem z ostrzeżeniem WARN przy uruchamianiu, które o tym uprzedzało. Dashboardy i alerty muszą przejść na oxphp_shared_channel_count, oxphp_shared_pool_count i count.

TRUSTED_PROXIES odczytuje wpisy IP:port — Za load balancerem, który zapisuje IP:port w X-Forwarded-For (Azure Application Gateway), REMOTE_ADDR to teraz klient, a REMOTE_PORT jego port. Jeśli CDN przed taką bramą również dopisuje swój wpis, adresem jest teraz adres samego CDN; dodaj także jego zakresy (w przypadku Cloudflare słowo kluczowe cloudflare).

DOCUMENT_ROOT za dowiązaniem symbolicznym do wydania — Katalog główny jest rozwiązywany raz przy uruchamianiu, a każdy plik statyczny odczytywany z dysku i każdy skrypt PHP jest przy otwarciu sprawdzany względem niego, więc po przełączeniu /app/current odpowiadają one 404 aż do restartu serwera. Wskaż w DOCUMENT_ROOT ścieżkę, która nie zmienia się między wydaniami, albo restartuj serwer w ramach przełączenia.

Skrypty, których serwer nie może otworzyć — Poza trybem worker usunięty skrypt odpowiada 404 nawet wtedy, gdy OPcache przechowuje go przy wyłączonym validate_timestamps, więc wdrożenie, które usuwa i odtwarza pliki w miejscu (git checkout, rsync --delete), ma okno, w którym zwraca 404; zapisuj nowe pliki obok starych i zastępuj nimi stare przez zmianę nazwy. Nieczytelny skrypt odpowiada 500 i loguje Cannot open script.

Skrypty za dowiązaniami symbolicznymi działają jak ich cel — Poza trybem worker skrypt osiągnięty przez dowiązanie do innego katalogu działa z tym katalogiem jako katalogiem roboczym, a to, czy PHP traktuje go jako phar, zależy od rozwiązanej nazwy pliku, a nie od URL. Ścieżki względne buduj od __DIR__.

fiber.stack_size musi przekraczać zend.reserved_stack_size — W trybie worker i w zadaniach ASYNC_WORKERS limit stosu fibera pozostawia teraz wolną rezerwę PHP (48 KiB w kompilacji wydaniowej), więc fiber.stack_size równy tej rezerwie lub mniejszy powoduje niepowodzenie każdego żądania z komunikatem Maximum call stack size of 0 bytes, a głęboka rekurencja zatrzymuje się o 44 KiB wcześniej niż dotąd.

Profile\start() nie robi nic, gdy profiler jest wyłączony — Przy wyłączonym PROFILER_ENABLED nie sprawia już, że Profile\is_active() zwraca true, więc test, który rozpoczyna profil i sprawdza is_active(), wymaga włączonego profilera. Przy OTEL_APM_ENABLED spany APM, które to wywołanie dotąd odrzucało, są teraz zachowywane w śladzie.

UploadedFile::type() po moveTo() odczytuje plik docelowy — Wykrywanie typu nie działa już przed przeniesieniem, więc plik, który do pierwszego wywołania type() został przeniesiony dalej, usunięty lub nadpisany, zgłasza to, co znajduje się tam teraz, albo application/octet-stream z ostrzeżeniem. Wywołuj type() przed moveTo(), jeśli przeniesiony plik nie zostaje na miejscu lub leży za wrapperem strumienia.

ADDED

Reguły blokowania ścieżek .oxphpdeny — Plik w składni .gitignore na najwyższym poziomie DOCUMENT_ROOT sprawia, że wymienione w nim ścieżki nie są serwowane, w każdym trybie routingu, zarówno dla plików statycznych, jak i dla PHP, zanim nastąpi jakikolwiek dostęp do dysku. Na zablokowane ścieżki odpowiada PHP_DENY_FALLBACK, a reguła > przekazuje żądanie do skryptu wejściowego. Plik jest odczytywany raz przy uruchamianiu; blokady są liczone w oxphp_path_deny_total.

RUNTIME_HOOKS=net — Nawiązywanie połączeń przez transporty strumieni PHP, uzgadnianie połączeń i odczyty TLS, zapisy czekające na miejsce w buforze wysyłania oraz odczyty ze strumieni unix://, udp://, udg:// i par gniazd zawieszają fiber zamiast zajmować wątek workera. Wymaga, by na liście było też streams, i działa tylko na Linuksie. Nieobjęte: libcurl, libpq, libmemcached, DNS i nawiązywanie trwałych połączeń.

Słowo kluczowe cloudflare w TRUSTED_PROXIES — Słowa kluczowe, zakresy CIDR i pojedyncze adresy IP można teraz łączyć na jednej liście (private,100.64.0.0/10 było dotąd błędem przy uruchamianiu). cloudflare ufa zakresom serwerów brzegowych Cloudflare i odczytuje adres klienta z CF-Connecting-IP, gdy pierwszy węzeł za Twoimi własnymi proxy to serwer brzegowy Cloudflare; private,cloudflare pasuje do układu Cloudflare → load balancer → OxPHP.

User-Agent w śladach i logach dostępu — Główny span serwera niesie user_agent.original, a każdy wpis logu dostępu pole user_agent, przycięte do 512 bajtów, z zastąpionymi nieprawidłowymi sekwencjami UTF-8, więc crawlera da się odróżnić od przeglądarki po samych śladach.

Dlaczego żądanie zostało zrzucone — Wiersze logu o przeciążeniu rozbijają teraz odmowy na refused_queue_full, refused_wait_timeout, refused_waiting_full i refused_waiting_bytes, a główny span odrzuconego żądania niesie oxphp.shed_reason, więc limit do podniesienia widać bez listenera INTERNAL_ADDR.

Niepowodzenia eksportu OTLP w /metrics — oxphp_otel_export_failures_total, oxphp_otel_export_failed_spans_total i oxphp_otel_export_retries_total liczą nieudane eksporty, przenoszone w nich spany oraz ponowienia po szybkich niepowodzeniach. Nieudany eksport jest też logowany jako OTLP span export failed, z rozmiarem partii i przyczyną.

Liczba CPU przy uruchamianiu — Wiersz CPU count for default sizing zgłasza cpus, cpu_source (host, affinity, cgroup quota lub unknown), host_cpus oraz wynikające z nich PHP_WORKERS i TOKIO_WORKERS. cpu_source=host na planie sprzedawanym jako mniejsza liczba CPU, jak na DigitalOcean App Platform pod gVisor, oznacza, że obie zmienne należy ustawić jawnie.

Obraz nightly na Debianie (glibc) — :nightly-trixie, :php8.4-nightly-trixie i :php8.5-nightly-trixie uruchamiają ten sam serwer na php:<minor>-zts-trixie i w aplikacji Yii3 zużywały o 14–19% mniej CPU na odpowiedź niż Alpine. www-data ma tam UID 33, a rozszerzenia są budowane w etapie trixie; otagowane wydania pozostają na Alpine.

PERFORMANCE

Tańsze wywołania funkcji PHP — Zniknął obserwator silnika, który na potrzeby klasyfikacji zaciętych żądań liczył każde wywołanie, łącznie z wewnętrznymi. W aplikacji Yii3 zużycie CPU na żądanie spada o 5% w trybie klasycznym i o 7% w trybie worker; skrypt wykonujący 15 000 krótkich wywołań na żądanie zużywa o 23–25% mniej.

FIXED

APM: hGet, hSet, lPush i rPush z phpredis — Przy włączonym OTEL_APM_ENABLED te cztery metody zwracały NULL, nie docierając do Redisa: zapisy ginęły, a odczyty wracały puste, bez błędu i bez spana. Hook dopasowuje teraz nazwy metod bez rozróżniania wielkości liter, tak jak PHP.

PHP 8.5: memory_limit po błędzie braku pamięci — OOM przy głębokim stosie w trybie worker lub w zadaniu asynchronicznym zostawiał workera bez żadnego limitu, więc następne niekontrolowane żądanie rosło, aż jądro zabiło serwer wraz ze wszystkimi jego workerami. Limit jest przywracany wraz z zakończeniem żądania, a worker, który nadal go przekracza, jest recyklowany.

Przepełnienie stosu wywołań nie kończy się już segfaultem — Rekurencja dochodząca do limitu stosu PHP w trybie worker lub w zadaniu asynchronicznym kończy się zwykłym Error, zamiast kłaść serwer; wystarczało do tego realpath() na ostatnim dozwolonym poziomie. Limitem jest teraz ten, który PHP ustawia dla każdego fibera.

Tryb worker: błędy krytyczne wewnątrz Fibera — Błąd krytyczny w fiberze uruchomionym przez żądanie lub w destruktorze wywołanym przez garbage collector odczytywał zwolnione ramki wywołań i mógł położyć proces wraz ze wszystkimi połączeniami. Żądanie kończy się teraz kodem 500, a worker obsługuje dalej.

Tryb worker: limity czasu kończą właściwe żądanie — Jedyny timer na wątek kończył to żądanie, które akurat działało, a żądanie, dla którego był przeznaczony, liczył jako niepowodzenie handlera, recyklując workera po trzech takich przypadkach. Każde żądanie ma teraz własny termin; termin, który upłynie, gdy żądanie jest zawieszone, kończy je kodem 504, który nie liczy się do recyklingu.

Tryb worker: max_input_time po rozruchu — Przy max_execution_time = 0 i ustawionym max_input_time timer uzbrojony na czas rozruchu workera kończył żądanie max_input_time sekund później (60 przy szablonach php.ini dostarczanych z PHP). Worker zatrzymuje go teraz, zanim zacznie obsługiwać żądania.

Zadania asynchroniczne i termin wątku puli — Jedyny timer wątku puli wygasał raz, mniej więcej po max_execution_time od startu procesu, kończąc akurat działające zadanie komunikatem Maximum execution time of 0 seconds. Wątki puli nie mają teraz żadnego terminu, więc set_time_limit() nie ogranicza już zadania: ogranicz je za pomocą limitu czasu w oxphp_async_await().

Trwałe połączenia zamykane przy wyjściu workera — Każdy recykling w trybie worker i każde zmniejszenie puli dynamicznej zostawiały połączenia PDO::ATTR_PERSISTENT, mysqli p: i pfsockopen() otwarte aż do restartu procesu, więc gromadziły się, dopóki baza danych nie zaczęła odmawiać nowym klientom. Są teraz zamykane po ostatnim żądaniu wątku.

Tryb worker: pamięć pozostawiona przez anulowane wywołania wewnętrzne — Żądanie zakończone wewnątrz funkcji wewnętrznej (na przykład usort() w trakcie komparatora) powodowało wyciek pamięci zaalokowanej przez tę funkcję na cały czas życia workera. Worker, który w trakcie takiego żądania urósł o ponad 4 MiB, jest teraz recyklowany w najbliższej chwili bezczynności, z ostrzeżeniem.

Błędy krytyczne w callbackach Shared — Błąd krytyczny w Mutex::withLock(), Once::getOrInit(), fabryce Registry lub Pool::with() zostawiał na cały czas życia procesu zablokowany mutex, Once w stanie oczekiwania, klucz zawieszony w trakcie tworzenia lub pulę uboższą o jedno miejsce. Wszystko to jest teraz oddawane wraz z zakończeniem żądania.

RUNTIME_HOOKS=streams z APM — Wrappery APM dla PDO, mysqli i Redisa pomijały ochronę połączenia, więc fibery współdzielące połączenie mogły odczytywać nawzajem swoje odpowiedzi Redisa lub dostawać Cannot execute queries while other unbuffered queries are active. Wrappery wywołują teraz chroniony handler.

RUNTIME_HOOKS=streams: destruktory między żądaniami — Destruktor uruchomiony przez zbieranie cykli w pętli workera mógł wpisać się w wymianę innego żądania na współdzielonym połączeniu i odczytać jej odpowiedź. Oczekiwanie na gnieździe połączenia, na którym zaparkował jego posiadacz, kończy się teraz od razu niepowodzeniem, jak przy przekroczeniu limitu czasu, i niczego nie wysyła.

Konstruktory trwałego PDO czekają w obrębie źródła danych — Konstruktor zablokowany na bazie danych, która nigdy nie odpowiada, wstrzymywał każdy inny trwały konstruktor na workerze, aż ich oczekiwanie wygasło i ruszały dalej bez szeregowania. Czekają teraz tylko na konstruktory do tego samego DSN.

Zwalnianie uchwytu PDO na współdzielonym trwałym połączeniu — Przy RUNTIME_HOOKS=streams żądanie zwalniające swój uchwyt wycofywało transakcję innego żądania, a z pdo_mysql mogło zwolnić wynik w trakcie odczytu i zakończyć się segfaultem. To sprzątanie wykonuje teraz tylko bieżący posiadacz połączenia, a PDO::rollBack() z innego fibera niczego nie wysyła.

Planowane recyklingi to nie awarie — Worker recyklowany z powodu WORKER_MAX_MEMORY_MIB lub Worker::scheduleExit() jest logowany na poziomie INFO jako Worker mode thread recycled, wraz z przyczyną, zamiast ostrzeżenia WARN Dead workers detected, które wyglądało jak awaria.

Eksport OTLP przez http/protobuf — Przez ten protokół nie wyeksportowano nigdy ani jednego spana: wątek eksportu kończył się paniką, a każdy span logował Spans are being emitted even after Shutdown. Spany trafiają teraz do /v1/traces pod OTEL_EXPORTER_OTLP_ENDPOINT, który jest bazowym URL; usuń /v1/traces, jeśli już się w nim znajduje.

OTLP przez gRPC: nawiązywanie połączeń i martwe połączenia — OTEL_EXPORTER_OTLP_TIMEOUT ogranicza teraz także nawiązywanie połączenia, a na Linuksie połączenie, które przestaje przyjmować dane, jest zastępowane, zamiast przez około 15 minut powodować niepowodzenie każdego eksportu. Eksport, który szybko kończy się niepowodzeniem na połączeniu zerwanym z powodu bezczynności, jest wysyłany jeszcze raz. Ustawione, ale puste OTEL_EXPORTER_OTLP_ENDPOINT jest teraz odrzucane przy uruchamianiu.

Klasyfikacja oxphp_worker_stuck_total — Pętla PHP bez żadnych wywołań była odczytywana jako c_call, a pierwszy odczyt zaciętego żądania mógł być mierzony względem odczytu wcześniejszego żądania. Nadzorca pyta teraz bezpośrednio silnik, przez to samo przerwanie, którym dostarczane jest max_execution_time.

UploadedFile::moveTo() bez fileinfo — Bez mime_content_type() lub przy procedurze obsługi błędów zamieniającej ostrzeżenia w wyjątki metoda moveTo() rzucała wyjątek przed przeniesieniem, a przesłany plik był usuwany na końcu żądania. Teraz najpierw przenosi plik, a wykrywanie typu pozostawia type().

Wyjątki z destruktorów przy kończeniu zadania asynchronicznego — Wyjątek rzucony przez destruktor obiektu zwalnianego podczas sprzątania po zadaniu znikał bez śladu; teraz jest zgłaszany jako nieprzechwycony i trafia do logu błędów.

Profiler zapisuje user_agent w index.json — Udokumentowane pole zawsze miało wartość null.

Dokumentacja: przewodnik SSE — Przykład dla trybu worker odczytywał zdarzenia przez BRPOP, więc każde zdarzenie trafiało do jednego klienta; teraz czyta Redis Stream i wznawia od Last-Event-ID. Przewodnik nie twierdzi już, że pętla while (!connection_aborted()) kończy się dzięki swojemu warunkowi, a jego przykład wywołuje ignore_user_abort(true) i set_time_limit(0).

SECURITY

Pliki statyczne: dowiązania symboliczne przestawione po routingu — Przy następnym żądaniu serwer podążał za dowiązaniem wewnątrz DOCUMENT_ROOT, które zweryfikowano przy poprzednim, a potem przestawiono, na przykład na /etc/passwd. Pliki są teraz sprawdzane po otwarciu, względem lokalizacji, którą jądro zgłasza dla deskryptora; plik wciąż obecny w pamięci podręcznej w RAM zachowuje swoje zweryfikowane bajty.

Skrypty PHP: dowiązania symboliczne przestawione po routingu — Poza trybem worker takie dowiązanie mogło, gdy pamięć podręczna realpath PHP zwolniła stary cel, uruchomić skrypt atakującego, wypisać plik tekstowy lub zawiesić workera na FIFO. Worker sam otwiera teraz skrypt, sprawdza deskryptor i przekazuje PHP właśnie ten plik; podmienione dowiązanie odpowiada 404. Kosztuje to około 3% przepustowości przy trywialnym skrypcie.

PHP 8.5: error_get_last() między żądaniami — W trybie worker i w zadaniach asynchronicznych żądanie wstrzymane w punkcie zawieszenia mogło odczytać backtrace błędu krytycznego innego żądania wraz z argumentami, z którymi wywołano jego funkcje: hasłami, tokenami, danymi klientów. Każde żądanie przechowuje teraz własny backtrace i zwalnia go, gdy się kończy.

TRUSTED_PROXIES: nieparsowalne wpisy przekazywania — Reguła rightmost-non-trusted pomijała wpisy, których nie potrafiła sparsować, i czytała dalej, aż do tego, co zapisał klient, więc za bramą zapisującą IP:port klient sam wybierał swój adres. Nieparsowalny wpis lub nieczytelny wiersz nagłówka zatrzymuje teraz regułę.

Podzielone nagłówki Cookie — Ciasteczka rozdzielone na kilka wierszy pola, na co pozwala HTTP/2, są teraz łączone zgodnie z RFC 9113, zanim cokolwiek je odczyta. Ciasteczko __oxp_* pluginu w dalszym wierszu docierało dotąd do aplikacji, a usunięcie takiego ciasteczka z pierwszego wiersza gubiło własne ciasteczka aplikacji z pozostałych wierszy.

v0.12.0
2026-09-25
⚠ breaking changes
Release notes →
BREAKING

SHARED_ENABLED=false wyłącza teraz Shared\* — Ustawienie było dotąd parsowane i ignorowane; teraz żadna klasa OxPHP\Shared\* nie jest rejestrowana, a pierwsza konstruowana rzuca Error: Class "OxPHP\Shared\Counter" not found. Jest ono odczytywane także jako OX_SHARED_ENABLED i jako samo ENABLED, więc przed aktualizacją sprawdź wszystkie trzy; class_exists() mówi kodowi, w którym wariancie działa.

Tryb worker: rozłączenie klienta nie kończy już żądania — Handler działa do końca, więc jego bloki finally i sprzątanie się wykonują, jakby ignore_user_abort było zawsze włączone; ogranicza go tylko max_execution_time. Wywołaj connection_aborted(), aby zakończyć wcześniej. Odpowiedzi strumieniowe nadal są kończone przy następnym zapisie.

ignore_user_abort(true) działa do końca w każdym trybie — Jak pod PHP-FPM, wyjście jest odrzucane, a connection_aborted() zgłasza rozłączenie; pętla strumienia, która polegała na tym, że zatrzyma ją następny zapis, musi sprawdzać connection_aborted(). ignore_user_abort = On w php.ini lub w inicjalizacji workera dotyczy teraz każdego strumienia.

/health odpowiada 503, gdy pula jest utracona lub zablokowana — Ciało zyskuje pool_stalled i zgłasza "status": "degraded". Sonda liveness Kubernetesa skierowana na zbiorczy /health będzie teraz restartować pody; używaj /health/liveness, /health/readiness i /health/startup.

ACCESS_LOG nie jest już przesłaniany przez LOG_LEVEL — ACCESS_LOG=all z LOG_LEVEL=warn nie zapisywał dotąd niczego; teraz zapisuje jeden wiersz na żądanie, więc zaplanuj objętość logów. RUST_LOG=warn,access_log=off nadal wycisza sam log dostępu.

payload() zwraca ciała formularzy i skalary JSON — Ciała formularzy są teraz parsowane przy SUPERGLOBALS_ENABLED=false, a ciało JSON "hi", 42 lub false zwraca tę wartość zamiast null lub $default. null oznacza teraz wyłącznie puste ciało, nieparsowany Content-Type, nieprawidłowy JSON lub dosłowne null.

Rozłączenia klientów liczone jako anulowania — oxphp_request_cancelled_total{reason="client_abort"} zaczyna rosnąć, podczas gdy admission_refused_total{reason="wait_timeout"} spada, a wiersz ERROR Connection error dla klienta, który się rozłączył, ma teraz poziom debug; alerty na którąkolwiek z tych metryk warto przejrzeć.

Uruchomienia profilera rozdzielane według wyzwalacza, pod własnym identyfikatorem — oxphp_profiler_runs_total{source} dzieli się teraz na header, cookie, query, sample i nowe sdk; identyfikatory uruchomień mają postać <ts_ms>-<req_id_prefix>-<rand4>, więc /__profiler/runs/{id} odpowiada 404 dla identyfikatora żądania — wyszukaj je po request_id w /__profiler/runs.

PROFILER_MAX_DEPTH jest egzekwowany — Dotąd nie działał nigdzie. Wartość domyślna to teraz 0 (bez limitu); wartości powyżej 32 są przycinane z ostrzeżeniem WARN przy uruchamianiu, a wywołania poniżej limitu nie trafiają do profilu.

COMPRESSION_LEVEL przestarzały — Używaj COMPRESSION_BROTLI_LEVEL; stara nazwa nadal działa, z ostrzeżeniem WARN przy uruchamianiu. Wartość niezerowa nie oznacza już wyłącznie Brotli, ponieważ oferowane są teraz także Zstandard i gzip. /config zgłasza brotli_level, gzip_level i zstd_level zamiast compression_level.

SHARED_SOFT_LIMIT_RATIO i SHARED_POISON_STRICT ignorowane — Żadne z nich nigdy nie było odczytywane; oba zapisują teraz WARN przy uruchamianiu, a limits.soft_ratio i diagnostics.poison_strict zniknęły z /__ox_shared/summary. Alertuj na podstawie oxphp_shared_capacity_saturation; terminalny Once przyjmuje Once\FailureMode::Poison.

oxphp_queue_wait_us pokazuje mniej — Pomiar kończy się teraz, gdy worker przejmuje żądanie, czyli około 0,1 ms mniej na żądanie; progi alertów poniżej milisekundy będą odpalać rzadziej. oxphp_request_duration_us bez zmian.

ADDED

Kompresja Zstandard — Odpowiedzi dynamiczne wychodzą jako zstd (poziom 6) do klientów, które je akceptują: mniejsze niż Brotli przy mniej niż połowie CPU. COMPRESSION_ENCODINGS (domyślnie br,zstd,gzip lub off) wybiera kodowania, wraz z COMPRESSION_BROTLI_LEVEL (5), COMPRESSION_ZSTD_LEVEL (6) i COMPRESSION_GZIP_LEVEL (6).

gzip dla klientów bez Brotli — Chromium po zwykłym HTTP i większość narzędzi CLI dostawały dotąd nieskompresowane ciała. Accept-Encoding jest teraz szeregowany według wartości q zgodnie z RFC 9110, a q=0 jest respektowane jako odmowa; w efekcie kompresowalne pliki poniżej 3 MiB odpowiadają na żądania Range pełnym 200.

Wstępnie skompresowana pamięć podręczna plików statycznych — Buforowany plik statyczny dostaje kopię Brotli o maksymalnej jakości, budowaną raz w tle, o 8–12% mniejszą niż wynik kompresji na żądanie, w ramach tego samego budżetu pamięci podręcznej.

Widoczność zablokowanej puli — oxphp_pool_stalled, oxphp_queue_depth, oxphp_queue_capacity i oxphp_admission_slots_available ujawniają pulę, która przestała zdejmować pracę ze swojej kolejki, z WARN, dopóki to trwa, i INFO, gdy praca znów rusza (tryb worker).

Przeciążenie w logu — Żądania zrzucane z 529 dają teraz jeden WARN na epizod, powtarzany najwyżej raz na minutę, oraz INFO z sumą, gdy minie minuta bez odmów.

Zgłaszanie wyczerpania MAX_CONNECTIONS — Zaparkowana pętla accept loguje WARN wymieniający MAX_CONNECTIONS i INFO, gdy wznawia pracę; oxphp_accept_stalled i oxphp_accept_stalls_total to eksportują.

oxphp_admission_wait_wasted_total — Liczy odmowy wait_timeout, podczas których żaden worker nie rozpoczął żadnego żądania — znak, że QUEUE_WAIT_TIMEOUT_MS jest za długi albo aplikacja odwołuje się do własnego serwera.

oxphp_worker_request_fibers_active — Fibery żądań na workera: 256 oznacza, że worker przestał przyjmować pracę, a liczba, która w bezczynności nie wraca do 0, to wyciek fiberów (tryb worker).

Widoczne RUNTIME_HOOKS — /config i oxphp_server_info() zgłaszają runtime_hooks jako sparsowaną listę zainstalowanych kategorii, np. ["sleep","streams"], więc błędnie zapisana zmienna odczytuje się jako [].

CHANGED

Adaptacyjny budżet oczekiwania w kolejce — QUEUE_WAIT_TIMEOUT_MS jest teraz górnym pułapem: serwer skraca oczekiwanie o połowę, dopóki co najmniej ćwierć rozpoczętej pracy trafia do klientów, którzy już odeszli (dolna granica 10 ms lub 1/64 pułapu), i przywraca je, gdy kolejka się opróżni. Ujawniają to oxphp_admission_wait_budget_us, oxphp_admission_wait_budget_ceiling_us i oxphp_abandoned_work_total.

Brotli na żądanie z jakością 5 — Wzrost z 4, która dawała większy wynik niż gzip przy większym zużyciu CPU; ciała Brotli powyżej 4 KB są teraz kompresowane w puli blokującej. COMPRESSION_BROTLI_LEVEL=4 przywraca dawne zachowanie.

Poziomy logów końca połączenia według typu błędu — Zamknięcie połączenia przez klienta ma poziom debug, wymuszony przez serwer timeout pozostaje warn, a wszystko inne pozostaje error, zamiast rozstrzygać to na podstawie słowa "timeout" w komunikacie.

FIXED

Tryb worker: sesje wyciekały między żądaniami — Żądanie przyjęte, gdy inne było zawieszone, startowało wewnątrz sesji poprzednika, a po session_write_close() mogło przejąć jej identyfikator i otrzymać go w Set-Cookie. Sesja jest teraz zapisywana i zamykana, a $_SESSION usuwane przed każdym żądaniem.

Trwałe połączenia odpowiadające na niewłaściwe żądanie — Poza trybem worker przerwanie przez klienta mogło zakończyć żądanie między wysłaniem polecenia przez pfsockopen() lub trwały Predis a odczytem odpowiedzi, rozsynchronizowując połączenie aż do restartu. Na odejście klienta serwer reaguje teraz dopiero przy następnym zapisie, jak pod PHP-FPM.

Tryb worker: strony zmieniające skład po przerwaniach — Przerwane żądania pomijały bloki finally, a przechwycone na granicy opkodu wyłączały każdy destruktor na workerze, więc zabezpieczenia przed rekurencją pozostawały ustawione, a strony po cichu traciły bloki. Żądania działają teraz do końca.

Tryb worker: wartości ini per żądanie — Zmiany wprowadzone przez ini_set(), ignore_user_abort() lub error_reporting() nie wyciekają już do żądań działających obok ani nie przeżywają zajętego workera; podróżują z zawieszonym żądaniem i są przywracane, gdy się ono kończy. Żądanie nie startuje też już z poziomem error_reporting pozostawionym przez poprzednie żądanie na jego fiberze.

Trwałe PDO pod RUNTIME_HOOKS=streams — Nakładające się konstruktory z PDO::ATTR_PERSISTENT nie zamykają już połączenia pod używającym go żądaniem (błąd krytyczny lub segfault), a zapytanie w trakcie pingu żywotności innego żądania nie zabija już połączenia błędem 2006 MySQL server has gone away.

Tryb worker: błędy krytyczne nie zapętlają już workera na zawsze — Przy włączonym profilerze lub #[Trace] dwa błędy krytyczne na jednym workerze mogły zapętlić łańcuch obserwatorów przy 100% CPU, podczas gdy pula wyglądała na bezczynną. Błąd krytyczny zgłoszony podczas odzyskiwania po innym nie porzuca też już każdego żądania na workerze.

Tryb worker: nieprzechwycone wyjątki z __destruct — Klasa wyjątku z destruktorem zamieniała 500 w błąd krytyczny liczony do recyklingu (31 żądań wycofało 8 workerów), wyłączała wszystkie destruktory na workerze i powodowała wyciek wyjątku.

Tryb worker: rozłączenia klientów nie powodują już recyklingu workerów — Przerwania po ignore_user_abort() lub podczas przejmowania żądania odczytywały się jako błędy krytyczne i rotowały pulę po trzy naraz. Każde żądanie liczone przez bezpiecznik loguje teraz WARN, a wyjście przez bezpiecznik jest logowane na poziomie WARN.

Tryb worker: strumienie zakończone przez zapis — Taki strumień nie powoduje już wycieku zmiennych swoich ramek na cały czas życia workera, a błąd krytyczny w destruktorze podczas tego sprzątania liczy się teraz do bezpiecznika błędów.

QUEUE_WAIT_TIMEOUT_MS ogranicza żądania w kolejce — Budżet był sprawdzany dopiero, gdy worker przejmował żądanie, więc zablokowana pula nigdy nie odpowiadała na żądania w kolejce; teraz dostają one 529 z Retry-After: 3 w chwili upływu terminu.

Klienci, którzy odeszli, nie są już kolejkowani ani odrzucani — Tryb worker nie wykonuje już żądania, którego klient odszedł, gdy czekało ono w kolejce, a budżet, który wyczerpie się po odejściu klienta, odpowiada 499 jako anulowanie, a nie 529. client_abort liczy teraz klientów odchodzących, zanim worker przejął ich żądanie.

/health/readiness odzwierciedla stan puli — Sprawdzenie executora zawsze przechodziło; pula bez żywych wątków workerów lub zablokowana przez minutę w trybie worker odpowiada teraz 503. Przeciążenie nadal nie.

oxphp_apm_trace() uruchamia swój callback — Zwracała null, nie wywołując go. Callback działa teraz w spanie potomnym, otrzymuje identyfikator spana i zwraca swoją wartość, a typ zwracany to teraz mixed.

Utracone komunikaty klas Error — TypeError, ValueError i reszta hierarchii Error trafiały do spanów i powodów zatrucia Once bez komunikatu i kodu.

Typy zwracane mixed pod JIT — Funkcje takie jak oxphp_async_await() i Shared\Map::get() deklarowały zniekształcony typ mixed, więc gorące pętle pod tracing JIT powodowały wyciek każdej zwróconej wartości, a getReturnType() z Reflection kończyło się segfaultem.

oxphp_worker_memory_bytes zgłasza pamięć — Zawsze pokazywał 0; teraz zgłasza tę samą stertę PHP, z którą porównywane jest WORKER_MAX_MEMORY_MIB, próbkowaną na końcu każdego żądania.

Tryb worker: profile mieszane między żądaniami — Zawieszone żądania na jednym wątku współdzieliły stan profilera i spanów APM, dając profile i spany #[Trace]/#[SlowThreshold] przypisane do niewłaściwego żądania; każde żądanie niesie teraz własny.

Obcinanie profilera i OXPROF — Uruchomienia, które osiągnęły PROFILER_MAX_SPANS, są oznaczane jako truncated w index.json, /__profiler/stats i oxphp_profiler_truncated_total, a udokumentowane ciasteczko OXPROF teraz rozpoczyna uruchomienie.

Oczekiwanie w Shared\Mutex::withLock() da się przerwać — Żądanie czekające na blokadę ustępuje teraz co 100 ms przed max_execution_time, zamykaniem serwera i anulowaniem zadania, więc blokowanie w przeciwnej kolejności nie trzyma już dwóch workerów aż do restartu.

RUNTIME_HOOKS=streams i późno ładowane rozszerzenia — mysqli i phpredis włączane z pliku ini sortowanego po extension.ini są teraz chronione jak każde inne; RUNTIME_HOOKS jest też odczytywane raz przy uruchamianiu zamiast przez niezablokowane getenv() przy każdym żądaniu.

Przypadki brzegowe kompresji — Pliki statyczne między 1 MiB a 3 MiB są kompresowane; wartości Content-Type o mieszanej wielkości liter są kompresowane; kopie identity kompresowalnych odpowiedzi niosą Vary: Accept-Encoding; a ustawienia kompresji spoza UTF-8 przerywają uruchamianie.

Błędy pojemności Shared\Counter, Atomic i Flag — Ich konstruktory rzucają teraz Shared\CapacityException po przekroczeniu SHARED_MAX_ENTRIES lub SHARED_MAX_BYTES, jak każdy inny typ Shared.

Poprawki dokumentacji — Wartości SHARED_LOCK_DIAGNOSTICS to off, warn (domyślnie) i strict; recykling workerów według liczby żądań nie jest już reklamowany; pętla workera kończy się także przy wycofaniu z puli dynamicznej; 504 pochodzi z max_execution_time; do tego granice łagodnego zamykania, układ plików ini w obrazie środowiska uruchomieniowego i kolejność kluczy w /config.

SECURITY

rustls 0.23.45 — Naprawia lukę w uzgadnianiu połączenia TLS 1.3 (RUSTSEC-2026-0285), która akceptowała niezaszyfrowane komunikaty uzgadniania przez granicę szyfrowania, wpływając na listener TLS oraz eksportery OTLP i profilera; zapis wymiany pozostaje uwierzytelniony.

v0.11.0
2026-08-20
⚠ breaking changes
Release notes →
BREAKING

FRAME_OPTIONS domyślnie SAMEORIGIN — Osadzanie między pochodzeniami pozostaje zablokowane, a osadzanie z tego samego pochodzenia (podglądy w panelu administracyjnym, widżety dashboardów) działa od razu; wdrożenia, które polegały na wbudowanym DENY, muszą teraz ustawić je jawnie.

Tryb worker: bez pętli zdarzeń wewnątrz żądania — Żądanie w trybie worker jest teraz fiberem, a Revolt odmawia uruchomienia w nim swojej pętli: Revolt\EventLoop::run() w handlerze rzuca wyjątek. Przenieś je do inicjalizacji workera; defer(), delay() i getSuspension() nadal działają.

Tryb worker: stan żądania kończy się wraz z żądaniem — ini_set(), set_time_limit() i error_reporting() wracają między żądaniami do stanu bazowego z inicjalizacji; filter_input() odpowiada tylko dla pytającego żądania; uchwyt php://input zatrzymany po końcu swojego żądania odczytuje się jako zamknięty zasób, a nie ciało innego żądania.

query() i cookie() dekodują swoje wartości — Kod, który kompensował to przez rawurldecode(), dekoduje teraz podwójnie i musi usunąć nadmiarowe wywołanie. Ciasteczko wysłane puste odczytuje się jako pusty ciąg, a nie null, a query() parsuje co najwyżej 1000 parametrów.

Ścisłe parsowanie MAX_CONNECTIONS / QUEUE_CAPACITY — Nieprawidłowe wartości przerywają uruchamianie zamiast po cichu wracać do wartości domyślnych, a QUEUE_CAPACITY=0 oznacza teraz auto (workers × 128), a nie kolejkę o zerowej pojemności.

ADDED

Budżet oczekiwania w kolejce — Żądanie trafiające na pełną kolejkę czeka teraz na miejsce do QUEUE_WAIT_TIMEOUT_MS (domyślnie 1 s), zamiast dostawać natychmiastowe 529; jeden termin wyznaczony w chwili nadejścia obejmuje oczekiwanie na miejsce i w kolejce. QUEUE_MAX_WAITING i QUEUE_MAX_WAITING_BYTES ograniczają zbiór oczekujących w żądaniach i w bajtach ciał.

RUNTIME_HOOKS=streams — Blokujące odczyty gniazd tcp:// i stream_select() zawieszają fiber zamiast wątku workera, obejmując mysqlnd (PDO_MySQL, mysqli), phpredis i wrappery strumieni HTTP bez zmian w kodzie. Połączenia współdzielone między współbieżnymi fiberami są zajmowane na czas wymiany, więc żądania nie mogą czytać nawzajem swoich odpowiedzi.

oxphp_admission_refused_total — Żądania zrzucone przed dotarciem do workera zostawiają teraz ślad po stronie serwera, z etykietą reason oddzielającą cztery powody przeciążenia (529) od shutting_down (503) i pool_unavailable (500).

Automatyczne przechwytywanie wyjątków na spanie głównym — Nieobsłużony wyjątek lub błąd krytyczny odpowiadający 5xx dołącza zdarzenie exception (typ, komunikat, stacktrace, plik, wiersz) do spana głównego żądania we wszystkich czterech trybach obsługi — bez atrybutu i bez wywołania oxphp_apm_error().

Atrybuty spanów bazodanowych — Spany PDO i mysqli niosą db.statement (wartości literalne zamaskowane do ?), db.operation, db.system, server.address/port, db.name i oxphp.db.slow; mysqli_stmt::prepare dołącza do zbioru podpiętych jako funkcja numer 34.

CHANGED

oxphp_queue_wait_us sięga jednej sekundy — Cztery nowe przedziały (od 100 ms do 1 s) kwantyfikują oczekiwania w pobliżu domyślnego budżetu, zamiast wrzucać je do +Inf; odrzucone żądania są wyłączone z histogramu.

Recykling workerów liczy to, co rujnuje workera — Do recyklingu po trzech kolejnych niepowodzeniach liczą się tylko błędy krytyczne, brak pamięci i przepełnienia stosu; nieprzechwycone wyjątki i anulowane żądania są neutralne. Bezpiecznik zadziała teraz także na synchronicznej ścieżce dyspozycji, gdzie nigdy się nie uruchamiał.

Porada dotycząca rozmiarów w config --check — Ostrzega, nie zawodząc, gdy PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING osiąga MAX_CONNECTIONS — punkt, za którym przeciążenie nie odpowiada niczym zamiast 529.

Spłaszczona dokumentacja — Dokumentacja repozytorium przeniosła się z docs/en/ do docs/; tłumaczenia rosyjskie i chińskie przeniosły się na oxphp.dev, gdzie francuskie, polskie i japońskie już mieszkają.

FIXED

Tryb worker: $_POST i $_FILES wypełniane — Oba były puste dla każdego żądania, jakie worker kiedykolwiek obsłużył, więc formularze HTML i wysyłki plików po cichu nie działały; API JSON czytające php://input były nietknięte i dlatego pozostawało to ukryte.

Wyciek filter_input() między żądaniami — Własna kopia wejścia rozszerzenia filter narastała przez cały czas życia workera, odpowiadając jednemu żądaniu wartościami zapytania, ciasteczkami sesji i polami ciała innego klienta; teraz jest oddawana na początku każdego żądania.

Serie timerów uśpienia — Więcej niż 32 timery wygasające w jednym cyklu odpytywania schedulera zostawiały nadmiarowych śpiących zaparkowanych na zawsze; duża seria budzi się teraz partiami po 32.

Zamykanie współdzielonego połączenia — fclose(), $mysqli->close() lub helper ponownego łączenia na połączeniu, na którego odczycie zaparkowane było inne żądanie, wznawiały to żądanie w zwolnionej pamięci; teraz zawodzi ono natychmiast z 500 nazywającym, co się stało.

Błędy krytyczne nie wymazują już stanu współdzielonego — Odwijanie przerwanego żądania podwójnie zwalniało zmienne z include, po cichu resetując tablice i obiekty, w których aplikacje trybu worker trzymają swój stan między żądaniami.

Tempo puli async — Przekazywanie zadań nie jest już taktowane stałym 1-milisekundowym timerem bezczynności: transfer przez Shared\Channel spadł z 0,69 ms do 0,14 ms na element, a wybudzenia bezczynności z 777/s do 92/s.

Wstrzymane żądania zachowują swój czas startu — $request->startTime() i oxphp_server_info()[request_time] nie odczytują już po zawieszeniu 0 ani wartości współbieżnego żądania.

Ostrzeżenia odczytu ciała trafiają do właściwego żądania — Ostrzeżenia post_max_size i limitów wysyłki odpalają teraz wewnątrz żądania, które wysłało ciało, więc procedury obsługi błędów widzą jego superglobalne, a taka, która rzuci, odpowiada 500, zamiast zamykać połączenie bez niczego.

v0.10.0
2026-07-12
⚠ breaking changes
Release notes →
BREAKING

Domyślne zrzucanie uprawnień — Uruchomiony jako root, gdy istnieje konto www-data, serwer wiąże swoje gniazda nasłuchujące i zrzuca uprawnienia do www-data przed rozpoczęciem obsługi jakiegokolwiek ruchu; oficjalny obraz nie działa już jako root. Przekaż --user=root, aby zachować dawne zachowanie.

Konfiguracja TLS w trybie fail-closed — Połowicznie skonfigurowana para (TLS_CERT bez TLS_KEY lub odwrotnie) albo wartość certyfikatu/klucza spoza UTF-8 przerywa teraz uruchamianie, zamiast po cichu obsługiwać zwykły HTTP, wskazując z nazwy problematyczną zmienną.

Ścisłe parsowanie ASYNC_* / SUPERGLOBALS — Śmieci w ASYNC_WORKERS, ASYNC_QUEUE_CAPACITY lub ASYNC_MAX_FIBERS to teraz błąd uruchamiania zamiast cichej wartości domyślnej, a oxphp run nie ignoruje już nieprawidłowego SUPERGLOBALS_ENABLED.

QUERY bez Content-Type zwraca 400, a nie 415 — Jedyny generowany przez serwer 415 zniknął; własny plik ERROR_PAGES_DIR/415.html dla tego przypadku trzeba zmienić na 400.html.

Ustalanie koperty eksportu profilera — PROFILER_EXPORT_XHGUI=false ma teraz priorytet nawet dla adresu URL /run/import, a automatyczne wykrywanie koperty opiera się wyłącznie na kanonicznej ścieżce każdego narzędzia (xhgui /run/import, Buggregator /api/profiler/store), a nie na nieprecyzyjnym dopasowaniu podłańcucha "xhgui".

ADDED

Eksport profilera do Buggregatora — HTTP push profilera opakowuje dane xhprof w kopertę, której oczekuje POST /api/profiler/store Buggregatora; PROFILER_EXPORT_BUGGREGATOR to włącza, a tags i app_name ustawia się przez zmienne środowiskowe.

Eksport śladów OTLP przez TLS — Endpoint OTEL_EXPORTER_OTLP_ENDPOINT z https:// jest teraz eksportowany w postaci zaszyfrowanej zarówno przez gRPC, jak i http/protobuf, z weryfikacją względem systemowego magazynu zaufanych certyfikatów za pomocą rustls; obraz środowiska uruchomieniowego musi zawierać pakiet certyfikatów CA (oficjalny zawiera).

Pełne dane o wyjątku w spanach APM — Zdarzenie wyjątku w spanie niesie teraz exception.message i exception.stacktrace obok exception.type, więc New Relic Errors Inbox, Jaeger i Tempo renderują komunikat i stos; ograniczane przez OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES.

Łagodne opróżnianie długotrwałych połączeń — Po SIGTERM serwer wysyła GOAWAY, zamyka bezczynne połączenia keep-alive i niezwłocznie kończy otwarte strumienie SSE/flush; domyślna wartość DRAIN_TIMEOUT_SECONDS obniżona z 30 → 25, aby zmieścić się w 30-sekundowym okresie karencji Kubernetesa.

TLS_MIN_VERSION — Minimalna akceptowana wersja protokołu: 1.2 (domyślnie) lub 1.3. Wszystko inne — w tym obca składnia w rodzaju "TLSv1.2" — to twardy błąd uruchamiania, walidowany nawet przy wyłączonym TLS i raportowany jako tls_min_version w /config.

CHANGED

Tani STATIC_REVALIDATE — stat() najwyżej raz na 3 sekundy na plik; trafienie w statykę jest obsługiwane z pamięci najwyżej jednym wywołaniem systemowym, a zmiany na dysku stają się widoczne w ciągu 3 s. Domyślnie pozostaje wyłączone.

Czytelniejsze błędy uruchamiania TLS — Ścieżka z literówką kończy się komunikatem "TLS_CERT: cannot read /etc/ssl/cert.pem: No such file or directory" zamiast surowego błędu systemu operacyjnego; błędy parsowania PEM i niedopasowania klucza otrzymują taki sam prefiks.

FIXED

Celowanie anulowania w trybie worker — Przerwanie po stronie klienta lub przekroczenie limitu czasu żądania mogło anulować to żądanie, które worker przyjął jako ostatnie, zamiast tego, które faktycznie było anulowane, przerywając niezwiązane, przetwarzane właśnie żądanie.

finish_request() a strumienie multipleksowane — Gdy tylko jakiekolwiek żądanie na workerze wywołało oxphp_finish_request(), każdy inny strumień multipleksowany na tego workera po cichu porzucał swoje kolejne dane z oxphp_stream_flush().

v0.9.0
2026-06-25
⚠ breaking changes
Release notes →
BREAKING

await po przekroczeniu limitu czasu anuluje zadanie — Porzucone zadanie jest anulowane przymusowo: zaparkowane zadania wznawiają się już w stanie anulowania, a te obciążające CPU są przerywane na granicy opkodu. Wcześniej działało do końca żądania.

ADDED

Zagnieżdżony oxphp_async() — Zadanie asynchroniczne może rozdzielać własne zadania i zawieszać się na await_all / await_any / await_race bez blokowania swojego wątku worker. Ograniczenie „bez zagnieżdżonego async” zniknęło.

Limit zadań w toku — ASYNC_MAX_FIBERS (domyślnie 256) ogranicza liczbę zadań w kolejce + wykonywanych; wysłanie ponad limit jest natychmiast odrzucane z AsyncException, więc fan-out nie może się zakleszczyć.

Lista dozwolonych dla serwera wewnętrznego — INTERNAL_ALLOW_IPS: lista dozwolonych w formacie CIDR dla /metrics i /config; endpointy kontroli stanu pozostają osiągalne dla orkiestratorów.

Wykluczenia próbkowania profilera — PROFILER_EXCLUDE_PATHS utrzymuje własny ruch frameworka (/_profiler, /_wdt) poza próbkowaniem statystycznym; jawne wyzwalacze nadal uruchamiają profilowanie.

FIXED

Wybudzanie nadawcy w Channel — Fiber czekający na wysłanie, zaparkowany na pełnym Shared\Channel, wybudza się, gdy recv_blocking zwolni slot — sendTimeout nie wyzwala się już fałszywie przy nasyceniu.

Przypadki brzegowe async — Awaria podczas łagodnego zamknięcia; await na zamkniętym promisie zawieszał się zamiast odrzucać; await_all pozostawiał promisy działające przy wczesnym przerwaniu.

v0.8.0
2026-06-13
⚠ breaking changes
Release notes →
BREAKING

Kontrakt routingu workerów — Zasoby statyczne z dysku, wszystko inne do pliku wejściowego workera ENTRY_FILE — dowolne pliki .php, indeksy katalogów i awaryjny root index.php nie są już rozwiązywane per żądanie.

Uczciwe CGI PATH_INFO — W trybie Framework PATH_INFO jest ustawiane tylko dla jawnego żądania /index.php/extra; PHP_SELF podaje front controller. Routing opieraj na REQUEST_URI.

ADDED

Żądania Range (RFC 9110) — 206 Partial Content dla plików statycznych: przewijanie wideo, wznawiane pobierania, częściowe ładowanie PDF; If-Range uwzględniane z silnymi walidatorami.

Limity połączeń HTTP/2 — Pięć zmiennych środowiskowych ogranicza zużycie zasobów na połączenie; ustalone wartości są logowane przy uruchomieniu.

CHANGED

Silne ETagi dla statyki — "<size>-<mtime>" zastępuje W/"…" — wymaga tego If-Range; reprezentacje skompresowane zachowują słaby tag.

FIXED

Nagłówki aplikacji wygrywają — X-Content-Type-Options, X-Frame-Options i CSP ustawione z PHP nie są już nadpisywane — wartości serwera są używane awaryjnie, tylko gdy nagłówek jest nieobecny.

Strumieniowanie a kompresja — Odpowiedzi strumieniowane przez flush() przechodzą bez kompresji, zamiast być w całości buforowane przez warstwę brotli do końca skryptu.

SECURITY

Utwardzenie HTTP/2 przeciw DoS — Rapid Reset (CVE-2023-44487), bomby HPACK i ataki zawieszające okno są ograniczone; martwe połączenia odzyskiwane przez keepalive PING/PONG.

v0.7.0
2026-06-05
⚠ breaking changes
Release notes →
BREAKING

remote_ip w logach dostępu — Pole JSON remote_addr zostało przemianowane na remote_ip i zawiera adres IP bez portu — zgodnie z nginx/Apache/Caddy.

ADDED

CLI w stylu PHP — oxphp run script.php wykonuje plik z semantyką CLI, mając pod spodem pełny silnik; skrypty z #!/usr/bin/env oxphp uruchamiają się bezpośrednio; przyjmowane są nadpisania ini przez -d.

Zrzucanie uprawnień przez --user — Zwiąż uprzywilejowane porty jako root, a następnie nieodwracalnie zrzuć uprawnienia do nieuprzywilejowanego użytkownika, zanim powstanie jakikolwiek wątek obsługujący żądania.

Przekazany port uwzględniany — Za TRUSTED_PROXIES SERVER_PORT uwzględnia X-Forwarded-Port; REMOTE_PORT jest wypełniane z for= z RFC 7239, gdy proxy je dostarcza.

FIXED

Awarie dekoratorów — Dwa SIGSEGV w ścieżce dekoratora funkcji (zwolniony cache instancji; wiszący zval przy zmianie rozmiaru) zniknęły; przepełnienie zagnieżdżenia rzuca teraz StackOverflowException.

Atrybuty profilera uwzględniają argumenty — #[SlowThreshold(ms:)], #[MemoryThreshold(kb:)] i #[Mark(label:)] nie sprowadzają się już do wspólnych wartości domyślnych; zdarzenia spanów APM docierają do OpenTelemetry.

Podłączone API obiektów przesyłania — Request::file() / files() zwracają obiekty UploadedFile dla każdej postaci $_FILES zamiast null.

v0.6.0
2026-05-28
⚠ breaking changes
Release notes →
BREAKING

Trychotomiczne API oczekiwania — Channel i Mutex dzielą każde oczekiwanie na try* / goły czasownik / *Timeout(int $ms), zwracając typowane wartościami SendResult / RecvResult zamiast mixed|null.

await_any oznacza Promise.any — Wygrywa pierwszy spełniony; odrzucenie wszystkich rzuca AggregateAsyncException. Dawne zachowanie „pierwszy rozstrzygnięty” to teraz oxphp_async_await_race().

Uczciwe statusy anulowania — Limit czasu → 504, łagodne opróżnianie → 503 + Retry-After, rozłączenie klienta → 499 (tylko log). Zabicia przez nadzorcę i anulowania z kodu użytkownika zachowują 500.

Zmiany nazw bez warstw pośrednich — Async\Exception → AsyncException, Shared\Exception → SharedException; Worker porzuca prefiksy get*; Counter staje się minimalny (add/set); Flag odwzorowuje Atomic; Once zyskuje getOrInit()/status().

ADDED

Shared\Atomic + Ordering — Ogólny atomik int64: load/store/swap/CAS/fetch* z jawnymi porządkami pamięci, jeden do jednego ze std::sync::atomic.

Shared\Registry — Globalne dla procesu uchwyty kluczowane nazwą: każdy worker zbiega się na tym samym wpisie; fabryka wykonuje się najwyżej raz.

REMOVED

Jedno źródło limitu czasu — REQUEST_TIMEOUT_SECONDS i oxphp_request_heartbeat() zniknęły — max_execution_time i set_time_limit() pozostają.

FIXED

connection_aborted() w SSE — Zwraca true po rozłączeniu w środku strumienia, więc pętle while (!connection_aborted()) kończą się czysto.

PERFORMANCE

Gorąca ścieżka Shared\* — Narzut na operację mieści się w szumie criterion względem surowego atomowego odczytu; ≈4.7× średniej geometrycznej przy 8 wątkach względem poprzedniej postaci.

v0.5.0
2026-05-05
⚠ breaking changes
Release notes →
BREAKING

Ścisłe parsowanie logicznych zmiennych środowiskowych — Tylko on/true/1/yes oraz off/false/0/no; literówka w rodzaju ture powoduje odmowę uruchomienia zamiast cichego przyjęcia wartości domyślnej.

ADDED

ENTRY_FILE + WORKER_MODE_ENABLED — Jedna zmienna wybiera tryb routingu na podstawie rozszerzenia; osobny przełącznik włącza trwałe workery. INDEX_FILE / WORKER_FILE stają się przestarzałymi aliasami.

Klasa OxPHP\Server\Worker — Uchwyt do introspekcji w czasie działania: id, liczba żądań, pamięć, RSS — plus sterowany przez aplikację recykling przez scheduleExit().

CHANGED

Zmiany nazw cache statyki — STATIC_CACHE_TTL → STATIC_MAX_AGE; STATIC_CACHE → STATIC_REVALIDATE z odwróconą polaryzacją. Wartości domyślne bez zmian.

DEPRECATED

WORKER_MAX_REQUESTS — Parsowane i ignorowane z ostrzeżeniem WARN przy uruchomieniu — przejdź na WORKER_MAX_MEMORY_MIB lub scheduleExit().

v0.4.0
2026-05-02
Release notes →
ADDED

Obrazy PHP 8.5 — :php8.5, :php8.5-alpine oraz tagi przypięte do wersji łatki są dostarczane obok 8.4; latest pozostaje na 8.4, dopóki nie zamknie się okno wygrzewania.

FIXED

Chunkowane strumieniowanie między żądaniami — Kontekst mostka przypisany do żądania przeciekał między żądaniami workera, gubiąc chunkowane Transfer-Encoding po pierwszym flush.

Shared\* wewnątrz Fiberów użytkownika — Channel::recv()/send() wewnątrz Fiber::start() nie idą już ścieżką zawieszenia oxphp-fiber i nie padają z fiber_await rc=1.

Argumenty nazwane dla API wtyczek — Klasy i funkcje zdefiniowane przez wtyczki ogłaszają teraz prawdziwe nazwy parametrów — wywołania z argumentami nazwanymi działają.

Współdzielone uchwyty w domknięciach async — Channel/Map/Pool przechwycone przez domknięcie oxphp_async() nie przychodzą już jako null w wątku odbierającym.

v0.3.0
2026-04-22
⚠ breaking changes
Release notes →
BREAKING

Przestrzeń nazw Async — Klasy Async przeniesione do OxPHP\Async\; funkcje są teraz dostarczane w plugin-async.

ADDED

Siedem prymitywów Shared\* — Counter, Flag, Once, Mutex, Channel, Map, Pool — stan atomowy współdzielony między workerami, z obserwowalnością przez rejestr i detektorem zakleszczeń.

Profiler na żądanie — Wyjścia xhprof / speedscope / pprof / collapsed; wyzwalacze przez cookie, nagłówek, query oraz statystyczne; HTTP push z kopertą xhgui.

Automatyczne instrumentowanie APM — plugin-apm z SDK do śledzenia dla PHP i przechwytywaniem błędów; podsystemy async i APM przeniesione do wtyczek w Rust.

Integracje produkcyjne — TRUSTED_PROXIES (RFC 7239), Kubernetes /readyz + /livez, nagłówki bezpieczeństwa, ograniczanie liczby żądań na IP — wszystko konfigurowane przez zmienne środowiskowe.

PERFORMANCE

Gorąca ścieżka Pool — Bezkonfliktowe acquire/release ≈0.9 µs w Dockerze, z powinowactwem per wątek.

v0.2.0
2026-03-27
Release notes →
ADDED

Multipleksowanie Fiberów — Współbieżne I/O w obrębie jednego wątku workera; śledzenie rozproszone z W3C Trace Context i eksportem do OpenTelemetry.

Promisy oxphp_async() — Równoległe wykonanie PHP na dedykowanej puli wątków.

Obiektowe API HTTP — OxPHP\Http\Request z leniwymi akcesorami, plus system dekoratorów oparty na atrybutach.

CHANGED

Port i przeciwciśnienie — Domyślny port 8080 → 80 (443 z TLS); odpowiedź przeciwciśnienia 503 → 529.

FIXED

Poprawność CGI — SERVER_PROTOCOL odzwierciedla rzeczywistą wersję HTTP; REQUEST_TIME to początek żądania; parsowanie IPv6 w Host; 408 przy przekroczeniu limitu czasu żądania.

v0.1.0
2026-03-08
Release notes →
ADDED

Asynchroniczne jądro HTTP — Serwer na Hyper + Tokio z łagodnym zamknięciem, własny SAPI oxphp z pełnymi superglobalnymi, pula workerów ZTS z ograniczoną kolejką i przeciwciśnieniem 529.

Trzy tryby routingu — Traditional, Framework i SPA; pliki statyczne z cache w pamięci, ETag/304, brotli, TLS.

Tryb worker — Trwałe workery przez oxphp_worker(), wczesne zakończenie odpowiedzi, strumieniowanie SSE, kooperatywne limity czasu.

Obserwowalność — /metrics, /health, /config, ustrukturyzowane logi dostępu JSON, ID żądań.

Docker-first — Wieloetapowa kompilacja na Alpine; obrazy amd64/arm64 na GHCR.