HTTP Protocol Features
ZealPHP implements the full HTTP/1.1 feature set: conditional requests, content negotiation, proper method handling, and CORS.
| Feature | Status | How |
|---|---|---|
| HEAD method | ✅ Auto-mapped | ResponseMiddleware runs GET handler, strips body, adds Content-Length |
| OPTIONS method | ✅ Built-in | Returns 204 + Allow header with all methods registered for that URI |
| ETag / 304 | ✅ Middleware | ETagMiddleware generates W/"md5", returns 304 on If-None-Match hit |
| Gzip compression | ✅ OpenSwoole | http_compression handles bodies when Accept-Encoding includes gzip |
| CORS | ✅ Middleware | CorsMiddleware handles preflight + adds headers to every response |
| Redirects 301/302/307/308 | ✅ Built-in | $response->redirect($url, $status) |
| Cookie SameSite | ✅ Built-in | setcookie($name, $value, ..., $samesite) |
| HTTP/2 | ⚙️ Configure | Pass 'enable_http2' => true to $app->run() (requires TLS) |
| Range requests | ✅ Middleware | RangeMiddleware handles single + multi-range (RFC 7233); $response->sendFile() for zero-copy file serving |
HEAD — headers only, body stripped
// Register GET route — HEAD works automatically:
$app->route('/http/head-test', function() {
header('X-Custom-Header: zealphp');
echo str_repeat('x', 2048); // 2KB body
});
// curl -I https://php.zeal.ninja/http/head-test
// → Content-Length: 2048 (no body)
// → X-Custom-Header: zealphp
OPTIONS — Allow header for URI
$app->route('/http/options-test', ['methods' => ['GET','POST','PUT']], fn() => '');
// curl -X OPTIONS https://php.zeal.ninja/http/options-test -v
// → HTTP/1.1 204 No Content
// → Allow: OPTIONS, GET, HEAD, POST, PUT
Redirects — 301/302/307/308
// $response->redirect() sets Location + status
$app->route('/http/redirect/{code}', function($code, $response) {
$response->redirect('/http/redirect-target', (int)$code);
});
// Auto-302 on Location header:
header('Location: https://example.com');
// ZealPHP detects Location: → sets status 302 automatically
Range — 206 Partial Content
// Any buffered response supports Range via RangeMiddleware:
$app->route('/http/range-test', function() {
echo str_repeat('abcdefghij', 100); // 1000 bytes
});
// curl -H 'Range: bytes=0-9' https://php.zeal.ninja/http/range-test
// → HTTP/1.1 206 Partial Content
// → Content-Range: bytes 0-9/1000
// → abcdefghij
sendFile() — zero-copy file download
// Serve files with zero-copy + automatic Range support:
$app->route('/download/{file}', function($file, $response) {
$path = "/var/data/{$file}";
$response->sendFile($path, $file);
});
// curl https://php.zeal.ninja/http/sendfile-test
// → Content-Type: text/css
// → Accept-Ranges: bytes
//
// curl -H 'Range: bytes=0-99' https://php.zeal.ninja/http/sendfile-test
// → HTTP/1.1 206 Partial Content
Apache + mod_php Parity
ZealPHP overrides 53 PHP built-in functions via ext-zealphp so that code written for Apache + mod_php or nginx + PHP-FPM runs unchanged on a long-lived OpenSwoole server. It populates request state per coroutine and ships middleware mirroring the common Apache/nginx directives. Every remaining gap is listed below with its workaround — nothing is left undocumented.
Function overrides (53 functions)
At server boot, ext-zealphp intercepts these functions and routes them to per-request objects. Your code calls header() — the framework writes to the correct OpenSwoole response. No code changes needed.
Response
| Function | Status | Notes |
|---|---|---|
header() / header_remove() / headers_list() / headers_sent() | ✅ Native | Write to the per-request response; Location: auto-sets 302 |
http_response_code() | ✅ Native | Last code wins, like mod_php |
setcookie() / setrawcookie() | ✅ Native | Full 7-arg form incl. SameSite |
header_register_callback() | ✅ Native | Fires once before buffered headers flush |
Sessions (18 functions)
| Function | Status | Notes |
|---|---|---|
session_start() / session_id() / session_status() / session_name() | ✅ Native | Per-coroutine, file-backed |
session_write_close() / session_commit() / session_abort() | ✅ Native | |
session_destroy() / session_unset() / session_regenerate_id() | ✅ Native | |
session_get_cookie_params() / session_set_cookie_params() | ✅ Native | |
session_cache_limiter() / session_cache_expire() | ✅ Native | |
session_encode() / session_decode() / session_save_path() / session_module_name() | ✅ Native |
Output control
| Function | Status | Notes |
|---|---|---|
flush() / ob_flush() / ob_end_flush() | ✅ Native | Flush to OpenSwoole response |
ob_implicit_flush() | ✅ Native | |
output_add_rewrite_var() / output_reset_rewrite_vars() | ✅ Native |
Error handling
| Function | Status | Notes |
|---|---|---|
set_error_handler() / restore_error_handler() | ✅ Native | Per-coroutine stack |
set_exception_handler() / restore_exception_handler() | ✅ Native | Per-coroutine stack |
register_shutdown_function() | ✅ Native | Per-request |
error_log() / error_reporting() | ✅ Native | Routes into framework log (debug.log) |
Exec family
| Function | Status | Notes |
|---|---|---|
shell_exec() / backtick operator | ✅ Native | Coroutine-safe via App::exec() |
exec() / system() / passthru() | ✅ Native | Yields in coroutine, blocking fallback outside |
Other
| Function | Status | Notes |
|---|---|---|
phpinfo() | ✅ Native | ZealPHP-branded dark theme with runtime diagnostics |
filter_input() / filter_input_array() | ✅ Native | Read INPUT_GET/POST/COOKIE/SERVER/ENV from request state |
is_uploaded_file() / move_uploaded_file() | ✅ Native | Validate against the request's uploaded set |
apache_request_headers() / getallheaders() / apache_response_headers() | ✅ Native | Apache-only shims registered globally |
apache_setenv() / apache_getenv() / apache_note() | ✅ Native | |
php_sapi_name() | ⚙️ Opt-in | Default returns "cli"; set App::sapiName('apache2handler') for legacy parity |
set_time_limit() / ignore_user_abort() | ✅ Native | |
connection_status() / connection_aborted() | ✅ Native |
Superglobals
| Superglobal | Status | Notes |
|---|---|---|
$_GET / $_POST / $_COOKIE / $_FILES / $_REQUEST | ✅ Native | Populated per request. Per-coroutine safe with ext-zealphp. |
$_SERVER | ✅ Native | Includes PHP_SELF, SCRIPT_NAME, SCRIPT_FILENAME |
$_SESSION | ✅ Native | Per-coroutine via ext-zealphp + overridden session_start() |
Recommended: use $g->get / $g->session (zero overhead, always safe).
With ext-zealphp, $_GET / $_SESSION are also per-coroutine safe —
saved/restored on every yield/resume via OpenSwoole scheduler hooks (the superglobal-snapshot
isolation stage, S1).
$_SERVER keys
| Key(s) | Status | Source |
|---|---|---|
REQUEST_METHOD / REQUEST_URI / SERVER_PROTOCOL / QUERY_STRING | ✅ Native | OpenSwoole request |
REMOTE_ADDR / REMOTE_PORT / SERVER_PORT | ✅ Native | OpenSwoole request |
REQUEST_TIME / REQUEST_TIME_FLOAT | ✅ Native | OpenSwoole request |
HTTP_* headers | ✅ Native | Transcribed from request headers |
DOCUMENT_ROOT / SCRIPT_NAME / SCRIPT_FILENAME / PHP_SELF / SERVER_SOFTWARE / SERVER_NAME | ✅ Native | Built per request (mod_php convention) |
GATEWAY_INTERFACE / REQUEST_SCHEME / HTTPS | ✅ Native | Added by ZealPHP; scheme from HTTPS/X-Forwarded-Proto/port 443 |
Apache directives → middleware & config
| Apache / nginx | Status | ZealPHP |
|---|---|---|
RewriteRule . /index.php [L] | ✅ Native | App::setFallback() |
mod_headers / mod_expires / AddCharset / AddType / ForceType | ✅ Middleware | Header / Expires / Charset / MimeType middleware |
<FilesMatch> Cache-Control / mod_substitute | ✅ Middleware | CacheControl / BodyRewrite middleware |
AuthType Basic + AuthUserFile / Allow,Deny / Require ip | ✅ Middleware | BasicAuth / IpAccess middleware |
limit_req / limit_conn (nginx) / server_name vhosts | ✅ Middleware | RateLimit / ConcurrencyLimit / HostRouter middleware |
Redirect / RedirectMatch (mod_alias) | ✅ Middleware | RedirectMiddleware — declarative prefix + regex redirects |
SetEnvIf / BrowserMatch (mod_setenvif) | ✅ Middleware | SetEnvIfMiddleware — set request env vars on attribute/regex match |
RequestHeader (mod_headers) | ✅ Middleware | RequestHeaderMiddleware — set/append/unset inbound headers ($g->server) |
<Location> / <LocationMatch> / <FilesMatch> | ✅ Middleware | ScopedMiddleware — apply any middleware only to matching paths |
MergeSlashes (core / nginx) | ✅ Middleware | MergeSlashesMiddleware — collapse // in the path before routing |
client_max_body_size (nginx) / LimitRequestBody | ✅ Middleware | BodySizeLimitMiddleware — 413 when Content-Length exceeds the cap |
valid_referers (nginx) | ✅ Middleware | RefererMiddleware — 403 hotlink protection |
return (nginx) | ✅ Middleware | ReturnMiddleware — fixed status / redirect / body; pair with ScopedMiddleware |
mod_deflate | ✅ Middleware | CompressionMiddleware (or OpenSwoole native http_compression) |
ScriptAlias / Options +ExecCGI | ✅ Native | App::cgiScriptAlias() / App::registerCgiBackend() |
ErrorDocument | ✅ Native | App::instance()->setErrorHandler() |
ServerTokens / ServerSignature | ✅ Config | App::serverTokens() controls/omits the X-Powered-By header |
FileETag | ✅ Config | App::fileETag(false) disables ETag/304 (FileETag None) |
DocumentRoot / TraceEnable / ServerAdmin / ServerName / LimitRequest* / CustomLog | ✅ Config | App::* fluent setters (set before App::init()) |
Trusted proxies / X-Forwarded-For | ✅ Config | App::$trusted_proxies + App::clientIp() |
php.ini directives
| Directive | Status | Notes |
|---|---|---|
max_execution_time | 🚫 By design | set_time_limit() is a no-op — use OpenSwoole coroutine timeouts |
auto_prepend_file / auto_append_file | 🚫 By design | No per-request boot hook — include at your entry point / fallback handler |
post_max_size / upload_max_filesize | ⚠️ Gap | Not enforced per directive — OpenSwoole's package_max_length is the real cap; middleware planned |
default_mimetype | ✅ Middleware | CharsetMiddleware applies App::$default_mimetype (default text/html) to untyped responses |
max_input_vars | ⚠️ Gap | Not enforced (default 1000 rarely hit) |
exit() / die()
| Call | PHP 8.4+ | PHP 8.3 |
|---|---|---|
exit() / exit(0) | ✅ Worker survives, 200 response | ⚠️ Kills worker (max_request respawns) |
die("message") | ✅ Worker survives, message as body | ⚠️ Kills worker |
exit(404) | ✅ Worker survives, HTTP 404 | ⚠️ Kills worker |
On PHP 8.3, use throw new \ZealPHP\HaltException() instead of exit().
On PHP 8.4+, exit()/die() throw \ExitException which ZealPHP catches
and treats as a clean halt. The exit-hook isolation stage (S12, App::hookExit())
routes exit()/die() through \ZealPHP\HaltException so the worker survives.
Known gaps
| Gap | Why | Workaround |
|---|---|---|
.htaccess file parsing | No runtime file watcher | Use middleware — all major directives have equivalents above |
PHP_SAPI constant | Constants cannot be redefined at runtime | Use php_sapi_name() function (overridden, configurable) |
exit()/die() on PHP 8.3 | Language constructs, not functions | throw new HaltException() or upgrade to PHP 8.4+ (the S12 exit-hook stage) |
define() persists across requests | Long-running process — constants are permanent once defined | processIsolation(true) for define-heavy apps (WordPress/Drupal), or coroutine-legacy's opt-in constants stage (S10, App::defineIsolation(true)) |
| Static class properties persist | Class entries survive between requests | max_request recycles workers; avoid mutable static state. Coroutine-legacy isolates class statics per coroutine (S5b) and resets them to the boot template per request (S11c). |
getenv()/putenv() for CGI vars | Not request-scoped | Read from $g->server / $_SERVER; coroutine-legacy isolates the environment per coroutine (the S9h process-settings sub-stage) |
mail() | Relies on system sendmail | Configurable transport planned |