Changelog
Chaque changement notable du serveur, de l'API PHP et des images — une sélection de points marquants pour chaque version. L'historique complet et intégral se trouve dans CHANGELOG.md.
Capture automatique des exceptions non gérées sur le span racine — Une requête qui échoue sur une exception non interceptée ou une erreur fatale et renvoie un 5xx attache désormais un événement d'exception OpenTelemetry (type, message, stacktrace, fichier, ligne) au span SERVER racine, sans attribut #[OxPHP\Apm\Trace] ni appel à oxphp_apm_error() : un 500 est ainsi auto-descriptif dans les backends d'erreurs. Les applications framework qui affichent leur propre page d'erreur l'enregistrent toujours explicitement.
FRAME_OPTIONS vaut désormais SAMEORIGIN par défaut, et non DENY — Le framing same-origin (aperçus d'administration, widgets de tableau de bord, composants de paiement same-origin) fonctionne d'emblée tandis que le clickjacking cross-origin reste bloqué, à l'image de nginx et de Rails ; définissez FRAME_OPTIONS=DENY pour interdire tout framing.
Abandon des privilèges par défaut — Démarré en tant que root là où un compte www-data existe, le serveur lie ses sockets d'écoute et bascule vers www-data avant de servir le moindre trafic ; l'image officielle ne s'exécute plus en tant que root. Passez --user=root pour conserver l'ancien comportement.
Configuration TLS fail-closed — Une paire à moitié configurée (TLS_CERT sans TLS_KEY, ou l'inverse) ou une valeur de certificat/clé non-UTF-8 interrompt désormais le démarrage au lieu de servir silencieusement du HTTP en clair, en nommant la variable fautive.
Analyse stricte de ASYNC_* / SUPERGLOBALS — Une valeur invalide dans ASYNC_WORKERS, ASYNC_QUEUE_CAPACITY ou ASYNC_MAX_FIBERS provoque une erreur au démarrage au lieu d'un repli silencieux sur la valeur par défaut, et oxphp run n'ignore plus un SUPERGLOBALS_ENABLED invalide.
QUERY sans Content-Type renvoie 400, et non 415 — Le seul 415 généré par le serveur a disparu ; une page ERROR_PAGES_DIR/415.html personnalisée pour ce cas doit être renommée en 400.html.
Résolution de l'enveloppe d'export du profileur — PROFILER_EXPORT_XHGUI=false l'emporte désormais même sur une URL /run/import, et l'auto-détection de l'enveloppe se fonde uniquement sur le chemin canonique de chaque outil (xhgui /run/import, Buggregator /api/profiler/store), et non plus sur une sous-chaîne approximative "xhgui".
Export du profileur vers Buggregator — Le push HTTP du profileur encapsule les données xhprof dans l'enveloppe attendue par le POST /api/profiler/store de Buggregator ; PROFILER_EXPORT_BUGGREGATOR l'active, avec tags et app_name définis via l'environnement.
Export des traces OTLP via TLS — Un OTEL_EXPORTER_OTLP_ENDPOINT en https:// est désormais exporté chiffré aussi bien en gRPC qu'en http/protobuf, vérifié contre le magasin de confiance système via rustls ; l'image d'exécution doit embarquer un bundle de CA (c'est le cas de l'image officielle).
Données d'exception complètes sur les spans APM — L'événement d'exception du span porte désormais exception.message et exception.stacktrace en plus de exception.type, si bien que New Relic Errors Inbox, Jaeger et Tempo affichent le message et la pile ; plafonné par OTEL_APM_MESSAGE_MAX_BYTES / OTEL_APM_STACKTRACE_MAX_BYTES.
Drainage gracieux des connexions longue durée — À la réception de SIGTERM, le serveur envoie GOAWAY, ferme les keep-alive inactifs et termine promptement les flux SSE/flush ouverts ; la valeur par défaut de DRAIN_TIMEOUT_SECONDS est abaissée de 30 à 25 pour tenir dans la période de grâce de 30 s de Kubernetes.
TLS_MIN_VERSION — Version de protocole minimale acceptée : 1.2 (par défaut) ou 1.3. Toute autre valeur — y compris une syntaxe étrangère comme "TLSv1.2" — provoque une erreur fatale au démarrage, validée même lorsque TLS est désactivé et reportée sous tls_min_version dans /config.
STATIC_REVALIDATE économique — stat() au plus une fois toutes les 3 secondes par fichier ; un accès statique est servi depuis la mémoire avec au plus un appel système, et les modifications sur disque deviennent visibles sous 3 s. Reste désactivé par défaut.
Erreurs de démarrage TLS plus claires — Un chemin comportant une faute échoue avec "TLS_CERT: cannot read /etc/ssl/cert.pem: No such file or directory" au lieu d'une erreur brute du système d'exploitation ; les erreurs d'analyse PEM et de non-correspondance de clé sont préfixées de la même façon.
Ciblage de l'annulation en mode worker — Un abandon client ou un délai d'expiration de requête pouvait annuler la requête la plus récemment acceptée par le worker au lieu de celle réellement visée par l'annulation, interrompant une requête en cours sans rapport.
finish_request() face aux flux multiplexés — Dès qu'une requête sur un worker appelait oxphp_finish_request(), tout autre flux multiplexé sur ce worker perdait silencieusement la sortie de ses appels ultérieurs à oxphp_stream_flush().
Un await expiré annule la tâche — Une tâche abandonnée est annulée de force : les tâches parquées reprennent pour être annulées, celles liées au CPU sont interrompues à une frontière d'opcode. Auparavant, elle s'exécutait jusqu'à la fin de la requête.
oxphp_async() imbriqué — Une tâche asynchrone peut dispatcher ses propres tâches et se suspendre sur await_all / await_any / await_race sans bloquer son thread worker. La restriction interdisant l'async imbriqué a disparu.
Plafond de tâches en cours — ASYNC_MAX_FIBERS (256 par défaut) borne les tâches en file d'attente + en cours d'exécution ; toute distribution au-delà du plafond est rejetée immédiatement avec AsyncException, de sorte qu'un fan-out ne peut pas provoquer d'interblocage.
Liste d'autorisation du serveur interne — INTERNAL_ALLOW_IPS : une liste d'autorisation CIDR pour /metrics et /config ; les endpoints de santé restent accessibles aux orchestrateurs.
Exclusions d'échantillonnage du profileur — PROFILER_EXCLUDE_PATHS garde le trafic interne du framework (/_profiler, /_wdt) hors de l'échantillonnage statistique ; les déclencheurs explicites profilent toujours.
Réveil de l'émetteur de Channel — Un Fiber en attente d'envoi parqué sur un Shared\Channel plein se réveille lorsque recv_blocking libère un emplacement — sendTimeout ne se déclenche plus de façon intempestive en cas de saturation.
Cas limites asynchrones — Un crash lors d'un arrêt gracieux ; un await sur une promesse fermée qui se bloque au lieu de rejeter ; await_all laissant des promesses en cours d'exécution lors d'un abandon anticipé.
Contrat de routage du worker — Les ressources statiques depuis le disque, tout le reste vers l'ENTRY_FILE du worker — les fichiers .php arbitraires, les index de répertoire et le repli sur l'index.php racine ne sont plus résolus par requête.
PATH_INFO CGI honnête — En mode Framework, PATH_INFO n'est défini que pour une requête /index.php/extra explicite ; PHP_SELF renvoie le front controller. Routez sur REQUEST_URI.
Requêtes Range (RFC 9110) — 206 Partial Content pour les fichiers statiques : lecture vidéo avec repositionnement, téléchargements reprenables, chargement partiel de PDF ; If-Range honoré avec des validateurs forts.
Limites de connexion HTTP/2 — Cinq variables d'environnement bornent l'utilisation des ressources par connexion ; les valeurs résolues sont journalisées au démarrage.
ETags statiques forts — "<size>-<mtime>" remplace W/"…" — requis par If-Range ; les représentations compressées conservent un tag faible.
Les en-têtes de l'application l'emportent — X-Content-Type-Options, X-Frame-Options et CSP définis depuis PHP ne sont plus écrasés — les valeurs du serveur sont des replis insérés uniquement s'ils sont absents.
Streaming face à la compression — Les réponses diffusées via flush() passent sans compression au lieu d'être intégralement mises en tampon par la couche brotli jusqu'à la fin du script.
Durcissement DoS HTTP/2 — Les attaques Rapid Reset (CVE-2023-44487), les bombes HPACK et les attaques par blocage de fenêtre sont bornées ; les connexions mortes sont récupérées via un keepalive PING/PONG.
remote_ip dans les journaux d'accès — Le champ JSON remote_addr est renommé remote_ip et porte l'IP sans port — à l'image de nginx/Apache/Caddy.
CLI façon PHP — oxphp run script.php exécute un fichier avec la sémantique CLI et le moteur complet en dessous ; les scripts #!/usr/bin/env oxphp s'exécutent directement ; les surcharges ini via -d sont acceptées.
Abandon de privilèges via --user — Lie les ports privilégiés en tant que root, puis abandonne irréversiblement les privilèges vers un utilisateur non privilégié avant que le moindre thread de traitement des requêtes ne soit créé.
Port transféré honoré — Derrière TRUSTED_PROXIES, SERVER_PORT honore X-Forwarded-Port ; REMOTE_PORT est renseigné à partir du for= de la RFC 7239 lorsque le proxy en fournit un.
Plantages de décorateur — Deux SIGSEGV dans le chemin du décorateur de fonction (cache d'instances libéré ; zval pendant lors d'un redimensionnement) ont disparu ; un dépassement d'imbrication lève désormais StackOverflowException.
Les attributs du profileur respectent leurs arguments — #[SlowThreshold(ms:)], #[MemoryThreshold(kb:)] et #[Mark(label:)] ne se réduisent plus à des valeurs par défaut partagées ; les événements de span APM parviennent à OpenTelemetry.
API d'objets d'upload câblée — Request::file() / files() renvoient des objets UploadedFile pour toutes les formes de $_FILES au lieu de null.
API d'attente trichotomique — Channel et Mutex scindent chaque attente en try* / verbe nu / *Timeout(int $ms), renvoyant des SendResult / RecvResult typés par valeur au lieu de mixed|null.
await_any signifie Promise.any — Le premier résolu l'emporte ; si tous sont rejetés, AggregateAsyncException est levée. L'ancien comportement « premier réglé » correspond désormais à oxphp_async_await_race().
Statuts d'annulation honnêtes — Délai d'expiration → 504, drainage gracieux → 503 + Retry-After, déconnexion client → 499 (journalisé uniquement). Les arrêts forcés par le superviseur et les annulations en espace utilisateur conservent 500.
Renommages sans shims — Async\Exception → AsyncException, Shared\Exception → SharedException ; Worker abandonne les préfixes get* ; Counter devient minimal (add/set) ; Flag reflète Atomic ; Once gagne getOrInit()/status().
Shared\Atomic + Ordering — Un atomique int64 générique : load/store/swap/CAS/fetch* avec des ordres mémoire explicites, en correspondance directe avec std::sync::atomic.
Shared\Registry — Des handles globaux au processus indexés par nom : chaque worker converge vers la même entrée ; la fabrique s'exécute au plus une fois.
Une seule source de délai d'expiration — REQUEST_TIMEOUT_SECONDS et oxphp_request_heartbeat() ont disparu — max_execution_time et set_time_limit() subsistent.
connection_aborted() en SSE — Renvoie true après une déconnexion en cours de flux, si bien que les boucles while (!connection_aborted()) se terminent proprement.
Chemin critique de Shared\* — Le surcoût par opération se situe dans le bruit de mesure de criterion par rapport à une charge atomique brute ; ≈4,7× en moyenne géométrique sur 8 threads face à la forme précédente.
Analyse stricte des booléens d'environnement — Uniquement on/true/1/yes et off/false/0/no ; une faute comme ture refuse de démarrer au lieu de retomber silencieusement sur la valeur par défaut.
ENTRY_FILE + WORKER_MODE_ENABLED — Une variable sélectionne le mode de routage selon l'extension ; un interrupteur explicite active les workers persistants. INDEX_FILE / WORKER_FILE deviennent des alias dépréciés.
Classe OxPHP\Server\Worker — Un handle d'introspection à l'exécution : id, nombre de requêtes, mémoire, RSS — plus le recyclage piloté par l'application via scheduleExit().
Renommages du cache statique — STATIC_CACHE_TTL → STATIC_MAX_AGE ; STATIC_CACHE → STATIC_REVALIDATE avec polarité inversée. Valeurs par défaut inchangées.
WORKER_MAX_REQUESTS — Analysé et ignoré avec un WARN au démarrage — migrez vers WORKER_MAX_MEMORY_MIB ou scheduleExit().
Images PHP 8.5 — :php8.5, :php8.5-alpine et les tags épinglés au patch sont livrés aux côtés de 8.4 ; latest reste sur 8.4 jusqu'à la fin de la fenêtre de rodage.
Streaming chunked entre requêtes — Le contexte par requête du bridge fuyait entre les requêtes du worker, abandonnant le Transfer-Encoding chunked après le premier flush.
Shared\* dans les Fibers utilisateur — Channel::recv()/send() à l'intérieur de Fiber::start() n'empruntent plus le chemin de suspension oxphp-fiber ni ne plantent avec fiber_await rc=1.
Arguments nommés pour l'API de plugins — Les classes et fonctions définies par les plugins exposent désormais de vrais noms de paramètres — les appels à arguments nommés fonctionnent.
Handles partagés dans les closures asynchrones — Channel/Map/Pool capturés par une closure oxphp_async() n'arrivent plus à null sur le thread récepteur.
Namespace Async — Les classes Async sont déplacées sous OxPHP\Async\ ; les fonctions sont désormais livrées dans plugin-async.
Sept primitives Shared\* — Counter, Flag, Once, Mutex, Channel, Map, Pool — un état partagé atomique entre workers, avec une observabilité par registre et un détecteur d'interblocage.
Profileur par requête — Sorties xhprof / speedscope / pprof / collapsed ; déclencheurs par cookie, en-tête, chaîne de requête et statistique ; push HTTP avec une enveloppe xhgui.
Auto-instrumentation APM — plugin-apm avec un SDK de traçage PHP et la capture d'erreurs ; les sous-systèmes async et APM sont déplacés dans des plugins Rust.
Intégrations pour la production — TRUSTED_PROXIES (RFC 7239), Kubernetes /readyz + /livez, en-têtes de sécurité, limitation de débit par IP — le tout configuré via l'environnement.
Chemin critique de Pool — acquire/release sans contention ≈0,9 µs dans Docker, avec affinité par thread.
Multiplexage de Fibers — E/S concurrentes au sein d'un même thread worker ; traçage distribué avec W3C Trace Context et export OpenTelemetry.
Promesses oxphp_async() — Exécution PHP parallèle sur un pool de threads dédié.
API d'objets HTTP — OxPHP\Http\Request avec des accesseurs paresseux, plus le système de décorateurs basé sur les attributs.
Port et contre-pression — Port par défaut 8080 → 80 (443 avec TLS) ; la réponse de contre-pression 503 → 529.
Exactitude CGI — SERVER_PROTOCOL reflète la version HTTP réelle ; REQUEST_TIME correspond au début de la requête ; analyse du Host IPv6 ; 408 en cas de délai d'expiration de requête.
Cœur HTTP asynchrone — Un serveur Hyper + Tokio avec arrêt gracieux, un SAPI oxphp personnalisé avec des superglobales complètes, un pool de workers ZTS avec une file bornée et une contre-pression 529.
Trois modes de routage — Traditional, Framework et SPA ; fichiers statiques avec un cache en mémoire, ETag/304, brotli, TLS.
Mode worker — Workers persistants via oxphp_worker(), achèvement anticipé de la réponse, streaming SSE, délais d'expiration coopératifs.
Observabilité — /metrics, /health, /config, journaux d'accès JSON structurés, ID de requête.
Docker d'abord — Une build Alpine multi-étapes ; images amd64/arm64 sur GHCR.