API Index — Namespaces, Packages, Reports, Indices
Response
in package
Thin wrapper around \OpenSwoole\Http\Response. The __call / __get /
__set proxies forward to the underlying response — these @method
annotations expose the forwarded signatures to static analysis so call
sites are statically typed instead of treated as mixed.
Table of Contents
Constants
- MAX_RANGES : mixed = 200
- Maximum number of comma-separated range specs honoured in a single Range header. Mirrors Apache's AP_DEFAULT_MAX_RANGES (byterange_filter.c).
- REDIRECT_REASONS : mixed = [301 => 'Moved Permanently', 302 => 'Found', 30...
Properties
- $cookiesList : array<int, array{0: string, 1: string, 2: int, 3: string, 4: string, 5: bool, 6: bool, 7: string, 8: string}>
- $headersList : array<int, array{0: string, 1: string}>
- $parent : Response
- $rawCookiesList : array<int, array{0: string, 1: string, 2: int, 3: string, 4: string, 5: bool, 6: bool, 7: string, 8: string}>
- $fileMimeResolver : MimeResolver|null
- Serve a file with Range request support using OpenSwoole's zero-copy sendfile.
- $g : RequestContext
- $htmxBuilder : HtmxResponse|null
- $statusCode : int|null
Methods
- __call() : mixed
- Forward method calls to the underlying OpenSwoole response.
- __construct() : mixed
- __get() : mixed
- Proxy property reads to the underlying OpenSwoole response.
- __set() : mixed
- Proxy property writes to the underlying OpenSwoole response.
- close() : void
- cookie() : bool
- create() : Response|false
- detach() : bool
- end() : bool
- flush() : bool
- header() : bool
- Queue a response header.
- htmx() : HtmxResponse
- Returns a fluent HtmxResponse builder that queues HX-* headers into this response. Each call returns the same builder instance (one per Response object) so callers can chain or call multiple times.
- ifRangeDateMatches() : bool
- Shared If-Range HTTP-date evaluator — the SINGLE source of truth for
date-form
If-Rangestrong validation across both range-serving paths (this Response::sendFile() zero-copy path AND the buffered RangeMiddleware path), so they can never diverge on the same request (#258). - isWritable() : bool
- json() : void
- Emit a JSON response and end the connection.
-
parseRange()
: array{status: "ignore"|"unsatisfiable"|"ok", ranges: array
} - Parse a Range header value into a normalised list of [start, end] byte ranges (inclusive, clamped to the resource size).
- push() : bool
- rawCookie() : bool
- recv() : bool
- redirect() : void
- Send an HTTP redirect.
- sendfile() : int|false
- sendFile() : void
- serializeCookie() : string
- Serialize ONE
Set-Cookieheader value byte-identical to PHP 8.4'sphp_setcookie()(#293) — verified live against Apache 2.4.67 + mod_php 8.4: - setFileMimeResolver() : void
- Override the suffix→metadata resolver
sendFile()uses — pass your ownMimeResolver(e.g. extra types, no language map), ornullto restore the built-in Apache-parity default. - sse() : void
- Server-Sent Events endpoint. Sets the required headers and delegates to stream(). The $fn callback receives an $emit() closure: $emit(string $data, string $event = '', string $id = '') which formats and sends one SSE message.
- status() : bool
- stream() : void
- Stream a response body in chunks. Headers are flushed immediately.
- trailer() : bool
- upgrade() : void
- write() : bool
- coalesceRanges() : array<int, array{0: int, 1: int}>
- Coalesce a list of [start, end] byte ranges (inclusive) into the minimal set of disjoint ranges covering the same bytes.
- defaultFileMimeResolver() : MimeResolver
- Apache stock-conf parity maps: common
mime.typesentries, theAddEncodingcompression suffixes, and the default-confAddLanguageset. Kept deliberately modest — the full IANA registry belongs to a user-supplied resolver via {@see setFileMimeResolver()}. - defaultPort() : int
- Default port for a URL scheme (RFC 6454 origin). 0 = unknown scheme.
- emitMultiHeader() : void
- Emit a single header name with multiple values as one OpenSwoole
header()call carrying an ARRAY value, so the ext writes one wire line per value (#260). Isolated in its own method because the openswoole/ide-helper stub types the value param asstringwhile the real ext accepts any zval (incl. an array) — the array-value behaviour is a documented ext-vs-stub mismatch (see phpstan.neon ignoreErrors). - emitQueuedHeaders() : void
- Emit every queued header to the underlying OpenSwoole response, grouping same-name entries so multiple appends survive on the wire (#260).
- fileMimeResolver() : MimeResolver
- ifRangeMatches() : bool
- Evaluate an If-Range header value against the resource's validators.
- isSameOrigin() : bool
- RFC 6454 same-origin test for the open-redirect (CWE-601) guard. An
origin is the triple (scheme, host, port); two URLs are same-origin iff
all three match (#432). This is **strict by design**: ZealPHP commonly
runs several instances on the same host at different ports (`php app.php
-p 8081
vs-p 8082`), so a same-host different-PORT target is a *different instance* and must be treated as cross-origin — and a scheme/port downgrade to an attacker-controlled service on the same host is a genuine CWE-601 hole. Same-origin absolute redirects keep working because the request's own (scheme, host, port) is compared, not a default — fixing the prior port-blind over-block (a same-origin redirect 500'd on every non-default port) and the host-only under-block together. -
resolveFileMetadata()
: array{type: string, encoding: ?string, languages: list
} - Resolve a file's response metadata for
sendFile()(#317): walk the suffix chain via {@see MimeResolver} (Apachemod_mimesemantics), then fall back to magic-bytes sniffing only when no suffix mapped a type. - sendMultipart() : void
- Emit a 206 multipart/byteranges response for two or more ranges, streaming each slice straight from the file. Body framing mirrors RangeMiddleware so both paths produce byte-identical multipart output.
- splitHostPort() : array{0: string, 1: ?int}
- Split a host[:port] authority into [bare host, port]. Handles bracketed
IPv6 literals (
[::1],[::1]:8080) and thehost:portform. A missing or non-numeric port yieldsnullfor the port (the caller applies the scheme default).
Constants
MAX_RANGES
Maximum number of comma-separated range specs honoured in a single Range header. Mirrors Apache's AP_DEFAULT_MAX_RANGES (byterange_filter.c).
private
mixed
MAX_RANGES
= 200
Beyond this the Range header is ignored and the full body is served (200), matching Apache's CVE-2011-3192 mitigation.
REDIRECT_REASONS
private
mixed
REDIRECT_REASONS
= [301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect']
Properties
$cookiesList
public
array<int, array{0: string, 1: string, 2: int, 3: string, 4: string, 5: bool, 6: bool, 7: string, 8: string}>
$cookiesList
= []
$headersList
public
array<int, array{0: string, 1: string}>
$headersList
= []
$parent
public
Response
$parent
$rawCookiesList
public
array<int, array{0: string, 1: string, 2: int, 3: string, 4: string, 5: bool, 6: bool, 7: string, 8: string}>
$rawCookiesList
= []
$fileMimeResolver
Serve a file with Range request support using OpenSwoole's zero-copy sendfile.
private
static MimeResolver|null
$fileMimeResolver
= null
The default mod_mime suffix maps sendFile() resolves against (#317) —
an Apache mime.types / AddEncoding / AddLanguage stock-conf subset.
Built once per worker; override with setFileMimeResolver().
$g
private
RequestContext
$g
$htmxBuilder
private
HtmxResponse|null
$htmxBuilder
= null
$statusCode
private
int|null
$statusCode
= null
Methods
__call()
Forward method calls to the underlying OpenSwoole response.
public
__call(string $name, array<int, mixed> $arguments) : mixed
Parameters
- $name : string
- $arguments : array<int, mixed>
__construct()
public
__construct(Response $response) : mixed
Parameters
- $response : Response
__get()
Proxy property reads to the underlying OpenSwoole response.
public
& __get(string $name) : mixed
Parameters
- $name : string
__set()
Proxy property writes to the underlying OpenSwoole response.
public
__set(string $name, mixed $value) : mixed
Parameters
- $name : string
- $value : mixed
close()
public
close() : void
cookie()
public
cookie(string $key[, string $value = '' ][, int $expire = 0 ][, string $path = '/' ][, string $domain = '' ][, bool $secure = false ][, bool $httponly = false ][, string $samesite = '' ][, string $priority = '' ]) : bool
Parameters
- $key : string
- $value : string = ''
- $expire : int = 0
- $path : string = '/'
- $domain : string = ''
- $secure : bool = false
- $httponly : bool = false
- $samesite : string = ''
- $priority : string = ''
Return values
boolcreate()
public
static create([int $fd = = '-1' ]) : Response|false
Parameters
- $fd : int = = '-1'
Return values
Response|falsedetach()
public
detach() : bool
Return values
boolend()
public
end([string|null $data = null ]) : bool
Parameters
- $data : string|null = null
Return values
boolflush()
public
flush() : bool
Return values
boolheader()
Queue a response header.
public
header(string $key, string $value[, bool $replace = true ]) : bool
Mirrors PHP's native header($value, $replace = true) semantics (#260):
$replace = true (the default) drops any previously-queued header of the
same name before adding this one — last write wins, as every framework
caller that sets a single header (Content-Type, Cache-Control, …) relies
on. $replace = false is the APPEND form (header($h, false)) — it keeps
prior same-name entries, so multiple Link / WWW-Authenticate / CSP
headers all survive (flush() groups same-name appends into one
array-valued OpenSwoole call so they all reach the wire).
Parameters
- $key : string
- $value : string
- $replace : bool = true
Return values
boolhtmx()
Returns a fluent HtmxResponse builder that queues HX-* headers into this response. Each call returns the same builder instance (one per Response object) so callers can chain or call multiple times.
public
htmx() : HtmxResponse
Return values
HtmxResponseifRangeDateMatches()
Shared If-Range HTTP-date evaluator — the SINGLE source of truth for
date-form If-Range strong validation across both range-serving paths
(this Response::sendFile() zero-copy path AND the buffered
RangeMiddleware path), so they can never diverge on the same request
(#258).
public
static ifRangeDateMatches(string $ifRange, int $mtime, int|null $reqTime) : bool
Implements RFC 9110 §13.1.5's strong-validation requirement via Apache's one-minute clock-skew rule (ap_condition_if_range): a Last-Modified date is only a STRONG validator if it is at least 60 s older than the time the representation was served — a value younger than that window is treated as weak and a weak validator MUST NOT be used to short-circuit a Range request. The Range is honoured only when the supplied date matches the resource mtime exactly AND that 60 s skew window has elapsed.
Parameters
- $ifRange : string
-
Raw If-Range header value (already known to be the HTTP-date form, not an entity-tag).
- $mtime : int
-
Resource last-modified time (unix seconds).
- $reqTime : int|null
-
Time the response is/was served (unix seconds) — from the response Date header when available; null → use wall-clock now.
Return values
bool —true → the date is a strong match, honour the Range.
isWritable()
public
isWritable() : bool
Return values
booljson()
Emit a JSON response and end the connection.
public
json(mixed $data[, int $status = 200 ]) : void
Parameters
- $data : mixed
- $status : int = 200
parseRange()
Parse a Range header value into a normalised list of [start, end] byte ranges (inclusive, clamped to the resource size).
public
static parseRange(string $rangeHeader, int $total) : array{status: "ignore"|"unsatisfiable"|"ok", ranges: array}
Mirrors RangeMiddleware's spec handling so both the buffered and the zero-copy file paths agree on suffix / open-end / bounded semantics.
Returns one of:
- ['status' => 'ignore', 'ranges' => []]
Header is not a
bytes=range, contains a syntactically invalid spec (RFC 7233 §2.1: an invalid spec invalidates the whole header), contains no spec at all (bytes=,— the empty byte-range-set, also invalid per §2.1, #365), or the range count exceeds MAX_RANGES (CVE-2011-3192 mitigation). Caller serves the full body (200). - ['status' => 'unsatisfiable', 'ranges' => []] Every well-formed spec is out of bounds (416).
- ['status' => 'ok', 'ranges' => array<int, array{0: int, 1: int}>]
Parameters
- $rangeHeader : string
- $total : int
Return values
array{status: "ignore"|"unsatisfiable"|"ok", ranges: arraypush()
public
push(string $filename[, int $opcode = = '1' ][, bool $finish = = 'true' ]) : bool
Parameters
- $filename : string
- $opcode : int = = '1'
- $finish : bool = = 'true'
Return values
boolrawCookie()
public
rawCookie(string $key[, string $value = '' ][, int $expire = 0 ][, string $path = '/' ][, string $domain = '' ][, bool $secure = false ][, bool $httponly = false ][, string $samesite = '' ][, string $priority = '' ]) : bool
Parameters
- $key : string
- $value : string = ''
- $expire : int = 0
- $path : string = '/'
- $domain : string = ''
- $secure : bool = false
- $httponly : bool = false
- $samesite : string = ''
- $priority : string = ''
Return values
boolrecv()
public
recv([float $timeout = = '0' ]) : bool
Parameters
- $timeout : float = = '0'
Return values
boolredirect()
Send an HTTP redirect.
public
redirect(string $url[, int $status = 302 ][, bool $allowExternal = false ]) : void
Parameters
- $url : string
-
Destination URL (absolute or relative)
- $status : int = 302
-
301 Moved Permanently, 302 Found (default), 307 Temporary Redirect, 308 Permanent Redirect
- $allowExternal : bool = false
-
Permit cross-origin / protocol-relative targets. Default
false— safe-by-default: a//evil.comor different-host absolute URL is REFUSED (open-redirect / CWE-601 guard). Settrueonly when an external redirect is genuinely intended (and validate user input against an allowlist first).
sendfile()
public
sendfile(string $filename[, int $offset = = '0' ][, int $length = = '0' ]) : int|false
Parameters
- $filename : string
- $offset : int = = '0'
- $length : int = = '0'
Return values
int|falsesendFile()
public
sendFile(string $path[, string $filename = '' ]) : void
Parameters
- $path : string
-
Absolute path to the file
- $filename : string = ''
-
Optional download filename (triggers Content-Disposition: attachment)
serializeCookie()
Serialize ONE Set-Cookie header value byte-identical to PHP 8.4's
php_setcookie() (#293) — verified live against Apache 2.4.67 +
mod_php 8.4:
public
static serializeCookie(string $name[, string $value = '' ][, int $expire = 0 ][, string $path = '' ][, string $domain = '' ][, bool $secure = false ][, bool $httponly = false ][, string $samesite = '' ][, string $priority = '' ][, bool $raw = false ]) : string
expiresuses the IMF-fixdate form with SPACES (Wed, 18 May 2033 …), not the legacy dashed Netscape date.Max-Age=max(0, expire - now)is emitted alongsideexpires(RFC 6265 §5.2.2 — Max-Age wins where supported).- The value is
rawurlencode()d — space is%20(never+; cookies are not form-encoded, and a literal+corrupts base64 payloads like JWTs). - PHP's attribute order and casing:
path,domain,secure,HttpOnly,SameSite. - An EMPTY value emits PHP's canonical deletion form:
name=deleted; expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0.
#319: SameSite=None without Secure is emitted AS-IS — PHP/Apache do
not auto-coerce, and parity wins — but a warning is logged, because
modern browsers (Chrome 80+, RFC 6265bis) silently drop such cookies.
Parameters
- $name : string
-
Cookie name (validated upstream by setcookie/setrawcookie).
- $value : string = ''
-
Cookie value; '' → PHP's deletion form.
- $expire : int = 0
-
Unix expiry; 0 → session cookie (no expires/Max-Age).
- $path : string = ''
-
path=attribute ('' → omitted). - $domain : string = ''
-
domain=attribute ('' → omitted). - $secure : bool = false
-
Emit
secure. - $httponly : bool = false
-
Emit
HttpOnly. - $samesite : string = ''
-
SameSite=attribute ('' → omitted). - $priority : string = ''
-
Priority=attribute (Chrome extension; '' → omitted). - $raw : bool = false
-
true → value passes through verbatim (setrawcookie).
Return values
stringsetFileMimeResolver()
Override the suffix→metadata resolver sendFile() uses — pass your own
MimeResolver (e.g. extra types, no language map), or null to restore
the built-in Apache-parity default.
public
static setFileMimeResolver(MimeResolver|null $resolver) : void
Parameters
- $resolver : MimeResolver|null
sse()
Server-Sent Events endpoint. Sets the required headers and delegates to stream(). The $fn callback receives an $emit() closure: $emit(string $data, string $event = '', string $id = '') which formats and sends one SSE message.
public
sse(callable $fn) : void
Parameters
- $fn : callable
status()
public
status(int $statusCode[, string $reason = '' ]) : bool
Parameters
- $statusCode : int
- $reason : string = ''
Return values
boolstream()
Stream a response body in chunks. Headers are flushed immediately.
public
stream(callable $fn) : void
The $fn callback receives a $write(string $chunk) closure; call it for each piece of content. The response is closed when $fn returns.
Use inside a coroutine route — co::sleep() or channel ops between $write() calls yield the event loop so other requests aren't blocked.
Parameters
- $fn : callable
trailer()
public
trailer(string $key, string $value) : bool
Parameters
- $key : string
- $value : string
Return values
boolupgrade()
public
upgrade() : void
write()
public
write(string $content) : bool
Parameters
- $content : string
Return values
boolcoalesceRanges()
Coalesce a list of [start, end] byte ranges (inclusive) into the minimal set of disjoint ranges covering the same bytes.
private
static coalesceRanges(array<int, array{0: int, 1: int}> $ranges) : array<int, array{0: int, 1: int}>
Sorts by start, then merges any spec that overlaps OR is immediately
adjacent to the running union (next.start <= cur.end + 1). Every input
spec is already clamped to [0, total-1] by parseRange(), so the
union of the result can never exceed the resource size — the
DoS-amplification cap (#230). Result is start-ascending (RFC 9110 §14.2
permits the server to reorder/merge multipart ranges). Kept in lock-step
with RangeMiddleware::coalesceRanges().
Parameters
- $ranges : array<int, array{0: int, 1: int}>
Return values
array<int, array{0: int, 1: int}>defaultFileMimeResolver()
Apache stock-conf parity maps: common mime.types entries, the
AddEncoding compression suffixes, and the default-conf AddLanguage
set. Kept deliberately modest — the full IANA registry belongs to a
user-supplied resolver via {@see setFileMimeResolver()}.
private
static defaultFileMimeResolver() : MimeResolver
Return values
MimeResolverdefaultPort()
Default port for a URL scheme (RFC 6454 origin). 0 = unknown scheme.
private
defaultPort(string $scheme) : int
Parameters
- $scheme : string
Return values
intemitMultiHeader()
Emit a single header name with multiple values as one OpenSwoole
header() call carrying an ARRAY value, so the ext writes one wire line
per value (#260). Isolated in its own method because the
openswoole/ide-helper stub types the value param as string while the
real ext accepts any zval (incl. an array) — the array-value behaviour is
a documented ext-vs-stub mismatch (see phpstan.neon ignoreErrors).
private
emitMultiHeader(string $name, array<int, string> $values) : void
Parameters
- $name : string
- $values : array<int, string>
emitQueuedHeaders()
Emit every queued header to the underlying OpenSwoole response, grouping same-name entries so multiple appends survive on the wire (#260).
private
emitQueuedHeaders() : void
header($name, $value, false) (PHP's append form) queues each value as a
separate $headersList entry — correct. But OpenSwoole's scalar
$response->header($name, $scalar) OVERWRITES any prior entry of the same
name (it stores under the header name as the array key), so emitting one
scalar call per entry collapses Link / WWW-Authenticate /
Content-Security-Policy multi-value headers to the LAST value only.
OpenSwoole DOES support multiple same-name headers — pass an ARRAY value
and the ext emits one wire line per element (the same mechanism that makes
multiple Set-Cookie work). So we group by name (first-seen order
preserved), then emit a scalar for single-value names and an array for
names with >1 value.
fileMimeResolver()
private
static fileMimeResolver() : MimeResolver
Return values
MimeResolverifRangeMatches()
Evaluate an If-Range header value against the resource's validators.
private
ifRangeMatches(string $ifRange, string $etag, int $mtime) : bool
Parameters
- $ifRange : string
-
Raw If-Range header value
- $etag : string
-
The resource's current ETag (entity-tag form)
- $mtime : int
-
The resource's last-modified time (unix seconds)
Return values
bool —true → the validator still matches, honour the Range
isSameOrigin()
RFC 6454 same-origin test for the open-redirect (CWE-601) guard. An
origin is the triple (scheme, host, port); two URLs are same-origin iff
all three match (#432). This is **strict by design**: ZealPHP commonly
runs several instances on the same host at different ports (`php app.php
-p 8081 vs -p 8082`), so a same-host different-PORT target is a
*different instance* and must be treated as cross-origin — and a
scheme/port downgrade to an attacker-controlled service on the same host
is a genuine CWE-601 hole. Same-origin absolute redirects keep working
because the request's own (scheme, host, port) is compared, not a
default — fixing the prior port-blind over-block (a same-origin redirect
500'd on every non-default port) and the host-only under-block together.
private
isSameOrigin(string $url) : bool
A target with no host (relative URL) is same-origin; an undeterminable request host preserves the historical "allow" so a working relative redirect never regresses.
Parameters
- $url : string
Return values
boolresolveFileMetadata()
Resolve a file's response metadata for sendFile() (#317): walk the
suffix chain via {@see MimeResolver} (Apache mod_mime semantics), then
fall back to magic-bytes sniffing only when no suffix mapped a type.
private
static resolveFileMetadata(string $path) : array{type: string, encoding: ?string, languages: list}
When the chain yielded an ENCODING but no type (README.gz), magic
bytes must not label the encoding as the type (application/gzip +
Content-Encoding: gzip would double-decode) — emit octet-stream.
Parameters
- $path : string
Return values
array{type: string, encoding: ?string, languages: listsendMultipart()
Emit a 206 multipart/byteranges response for two or more ranges, streaming each slice straight from the file. Body framing mirrors RangeMiddleware so both paths produce byte-identical multipart output.
private
sendMultipart(string $path, array<int, array{0: int, 1: int}> $ranges, int $total, string $mime) : void
Parameters
- $path : string
- $ranges : array<int, array{0: int, 1: int}>
- $total : int
- $mime : string
splitHostPort()
Split a host[:port] authority into [bare host, port]. Handles bracketed
IPv6 literals ([::1], [::1]:8080) and the host:port form. A missing
or non-numeric port yields null for the port (the caller applies the
scheme default).
private
splitHostPort(string $authority) : array{0: string, 1: ?int}
Parameters
- $authority : string