HTTP Request API
OxPHP provides an object-oriented API for accessing HTTP request data. Instead of reading $_GET, $_POST, $_COOKIE, $_FILES, and $_SERVER, you call methods on a Request object that returns exactly what you ask for, no more and no less.
Table of contents
- Overview
- Getting the Request object
- RequestInterface methods
- SessionInterface
- UploadedFileInterface
- AttributesInterface
- Exceptions
- SUPERGLOBALS_ENABLED
- Worker Mode
- IDE support
- Examples
Overview
oxphp_http_request() returns a read-only proxy to the HTTP request data stored in the current worker thread. Data is fetched lazily. A single method call like $request->header('Accept') goes directly to the Rust-side data structure and returns just that value. Full-array calls like $request->headers() build the array once and cache it in the PHP object for the duration of the request.
Why use it instead of superglobals?
- JSON body parsing is built in.
$request->payload()parsesapplication/json,application/x-www-form-urlencoded, andmultipart/form-datawithout extra code. - No array key typos.
$request->method()is harder to mistype than$_SERVER['REQUEST_METHOD']. - Type-detected file uploads.
$request->file('avatar')->type()returns the MIME type determined from the file's actual contents, not the client-supplied value. - Testable. Because behavior is defined by interfaces, you can inject mock implementations in unit tests.
- Superglobals stay available. Setting
SUPERGLOBALS_ENABLED=falseis optional. The object API works regardless.
Getting the Request object
<?php
$request = oxphp_http_request();Call oxphp_http_request() anywhere in a script executing inside an active HTTP request, including inside the oxphp_worker() callback:
<?php
oxphp_worker(function () {
$request = oxphp_http_request();
$method = $request->method();
// ...
});RequestInterface methods
URI and method
$request->method(): stringReturns the HTTP method in uppercase: "GET", "POST", "PUT", "PATCH", "DELETE", etc.
$request->isMethod(string $method): boolCase-insensitive method check.
$request->path(): stringThe URI path without the query string: "/users/42".
$request->fullUri(): stringThe complete URI including scheme, host, optional non-standard port, path, and query string: "https://example.com:8080/users/42?page=2". Standard ports (80 for HTTP, 443 for HTTPS) are omitted.
$request->scheme(): string"https" or "http".
$request->isSecure(): booltrue when the scheme is "https".
$request->host(): stringHostname from the Host header. Returns an empty string when the header is absent (HTTP/1.0 requests without a Host header).
$request->port(): intPort from the Host header. When not explicitly present, returns the default for the scheme: 80 for HTTP, 443 for HTTPS.
scheme(), isSecure(), host(), and port() honor X-Forwarded-Proto and X-Forwarded-Host when TRUSTED_PROXIES includes the peer. Without trusted proxies they reflect the direct connection.
$request->queryString(): ?stringThe raw query string without the leading ?. Returns null when there is no query string.
Protocol
$request->httpProtocol(): stringThe full protocol string: "HTTP/1.1" or "HTTP/2".
$request->httpProtocolVersion(): stringThe version number only: "1.1" or "2".
Query parameters
$request->query(?string $key = null, mixed $default = null): mixedAccess query string parameters.
| Call | Returns |
|---|---|
$request->query() |
All parameters as an array, including nested arrays |
$request->query('page') |
The value of page, or null if absent |
$request->query('page', 1) |
The value of page, or 1 if absent |
Bracket notation (?tags[]=php&tags[]=async) is parsed into nested arrays:
// Request: GET /search?q=oxphp&tags[]=php&tags[]=async
$q = $request->query('q'); // "oxphp"
$tags = $request->query('tags'); // ["php", "async"]
$all = $request->query(); // ["q" => "oxphp", "tags" => ["php", "async"]]Found values are always strings. $default is returned as-is when the key is absent.
Parsed body
$request->payload(?string $key = null, mixed $default = null): mixedReturns the parsed request body. The body is parsed according to the Content-Type header:
| Content-Type | Returns |
|---|---|
application/x-www-form-urlencoded |
Associative array of field values |
multipart/form-data |
Associative array of text field values |
application/json |
Decoded array or scalar; null for invalid JSON |
| Any other value | null |
payload() is not limited to POST requests. It works with PUT, PATCH, and any other method that sends a body. The parsed result is cached on the first call and reused for the lifetime of the request.
| Call | Returns |
|---|---|
$request->payload() |
The entire parsed body |
$request->payload('email') |
A single field value, or null if absent |
$request->payload('email', '') |
A single field value, or '' if absent |
<?php
// JSON request: POST /api/users
// Content-Type: application/json
// Body: {"name": "Alice", "role": "admin"}
$name = $request->payload('name'); // "Alice"
$role = $request->payload('role'); // "admin"
$data = $request->payload(); // ["name" => "Alice", "role" => "admin"]Headers
$request->header(string $name, ?string $default = null): ?stringReturns the raw header value. Header names are case-insensitive. For multi-value headers (Accept, X-Forwarded-For), the full header line is returned as a single string. Parsing is your responsibility.
$request->hasHeader(string $name): boolReturns true if the named header is present.
$request->headers(): arrayReturns all headers as an associative array. Each key is the header name as received (no normalization), and each value is the raw header string.
<?php
$accept = $request->header('Accept');
// "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
if ($request->hasHeader('Authorization')) {
$token = $request->header('Authorization');
}
$all = $request->headers();
// ["Content-Type" => "application/json", "Accept" => "...", ...]Cookies
$request->cookie(string $name, ?string $default = null): ?stringReturns the value of a single cookie, or $default if the cookie is not present.
$request->cookies(): arrayReturns all cookies as an associative array of name-value pairs.
<?php
$theme = $request->cookie('theme', 'light'); // "dark" or "light"
$session = $request->cookie('session'); // null if absent
$all = $request->cookies(); // ["theme" => "dark", ...]Raw body
$request->body(): stringReturns the raw request body bytes. This is the OxPHP equivalent of file_get_contents('php://input'). Unlike payload(), body() is not cached. Each call goes to the underlying data structure.
$request->contentType(): ?stringReturns the value of the Content-Type header, or null if absent.
<?php
// Read raw body for signature verification
$raw = $request->body();
$signature = $request->header('X-Hub-Signature-256');
$valid = hash_hmac('sha256', $raw, $secret) === $signature;body() and payload() are independent. You can call both in the same request.
File uploads
$request->file(string $name): ?UploadedFileInterfaceReturns the uploaded file for the given field name, or null if the field is not present. For array fields (name="photos[]"), returns the first file.
$request->files(?string $name = null): array| Call | Returns |
|---|---|
$request->files() |
All uploaded files as a flat array of UploadedFileInterface |
$request->files('photos') |
All files for the photos field (supports name="photos[]") |
<?php
$avatar = $request->file('avatar');
if ($avatar && $avatar->isValid()) {
$mime = $avatar->type(); // Detected from file contents, not the client claim
$name = $avatar->name(); // Original filename
$avatar->moveTo('/var/uploads/' . basename($name));
}
// Multiple files
$photos = $request->files('photos'); // UploadedFileInterface[]
foreach ($photos as $photo) {
if ($photo->isValid()) {
$photo->moveTo('/var/uploads/' . basename($photo->name()));
}
}Client
$request->ip(): stringReturns the client IP address. When TRUSTED_PROXIES is configured and the request peer is in the trusted set, this is the rightmost untrusted address from X-Forwarded-For or RFC 7239 Forwarded. Otherwise it is the direct peer IP, typically your load balancer, not the end client.
The raw X-Forwarded-For header remains available via $request->header('X-Forwarded-For') for advanced cases, but parsing it manually is rarely correct (leftmost vs rightmost, no CIDR trust check). Configure TRUSTED_PROXIES instead. See Trusted Proxies.
Timing
$request->startTime(bool $asFloat = false): int|floatReturns the Unix timestamp for when this request was received.
| Call | Returns |
|---|---|
$request->startTime() |
Integer seconds: 1711234567 |
$request->startTime(true) |
Float with sub-second precision: 1711234567.3412 |
<?php
$elapsed = microtime(true) - $request->startTime(true);
error_log(sprintf("Request took %.3fs so far", $elapsed));Attributes
$request->attributes(): AttributesInterfaceReturns the mutable attributes container for the current request. Use attributes to share data between middleware, route handlers, and other code in the same request without using global variables.
<?php
// In authentication middleware
$request->attributes()->set('user', $authenticatedUser);
// In the route handler
$user = $request->attributes()->get('user');Attributes are per-request and are reset with each new request in worker mode. When using Fibers, attributes are shared across all Fibers running on the same worker thread for the same request. Because PHP Fibers are cooperative, concurrent access is not possible.
Session
$request->session(): ?SessionInterfaceReturns a read-only view of $_SESSION. Returns null if session_start() has not been called. Session management (starting, saving, destroying, writing values) uses the standard PHP session functions.
<?php
session_start();
$session = $request->session();
$userId = $session->get('user_id');
$isAdmin = $session->get('is_admin', false);
// Write session data using standard PHP functions
$_SESSION['last_seen'] = time();SessionInterface
SessionInterface is a read-only view of the active session.
namespace OxPHP\Http;
interface SessionInterface
{
public function id(): string;
public function name(): string;
public function get(string $key, mixed $default = null): mixed;
public function has(string $key): bool;
public function all(): array;
}| Method | Description |
|---|---|
id() |
The session ID |
name() |
The session name (default: "PHPSESSID") |
get(key, default) |
A single session value, or $default if the key is absent |
has(key) |
true if the key exists in $_SESSION |
all() |
All session data as an array |
Session values reflect the current state of $_SESSION at the time of the call, not the state when session() was first called.
UploadedFileInterface
UploadedFileInterface represents a single uploaded file.
namespace OxPHP\Http;
interface UploadedFileInterface
{
public function name(): string;
public function clientType(): string;
public function type(): string;
public function size(): int;
public function tmpPath(): string;
public function error(): int;
public function isValid(): bool;
public function moveTo(string $destination): bool;
}| Method | Description |
|---|---|
name() |
Original filename sent by the client |
clientType() |
MIME type declared by the client — do not trust this value for security decisions |
type() |
MIME type determined from the file's actual contents using magic byte detection. Returns "application/octet-stream" when the type cannot be determined. Cached on first call. |
size() |
File size in bytes |
tmpPath() |
Path to the temporary file on disk |
error() |
One of the UPLOAD_ERR_* constants |
isValid() |
true when error() is UPLOAD_ERR_OK |
moveTo(path) |
Moves the file to $path. Calls type() before moving. Returns false if the file is invalid or the move fails. |
Always check isValid() before using an uploaded file. Use type() rather than clientType() when making security-sensitive decisions:
<?php
$file = $request->file('document');
if (!$file || !$file->isValid()) {
http_response_code(400);
echo json_encode(['error' => 'Upload failed or missing']);
return;
}
$detectedMime = $file->type();
$allowed = ['application/pdf', 'image/jpeg', 'image/png'];
if (!in_array($detectedMime, $allowed, true)) {
http_response_code(415);
echo json_encode(['error' => "File type not allowed: $detectedMime"]);
return;
}
$file->moveTo('/var/uploads/' . bin2hex(random_bytes(8)) . '.pdf');AttributesInterface
AttributesInterface is the only mutable part of the request object. It is intended for storing per-request metadata — authenticated user, resolved route parameters, locale, feature flags — that multiple parts of the application need to access.
namespace OxPHP\Http;
interface AttributesInterface
{
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value): void;
public function has(string $key): bool;
public function remove(string $key): void;
public function all(): array;
}| Method | Description |
|---|---|
get(key, default) |
Returns the value for $key, or $default if absent |
set(key, value) |
Stores a value |
has(key) |
true if the key has been set |
remove(key) |
Removes the key |
all() |
All attributes as an associative array |
Exceptions
Calling oxphp_http_request() outside an active request context throws an exception from the OxPHP\Http\Exception namespace.
namespace OxPHP\Http\Exception;
class NoActiveRequestException extends \RuntimeException {}
class AsyncContextException extends NoActiveRequestException {}
class WorkerIdleException extends NoActiveRequestException {}| Exception | When thrown |
|---|---|
NoActiveRequestException |
No active HTTP request: CLI, MINIT, after shutdown, or a Fiber that outlives its request |
AsyncContextException |
Inside an oxphp_async() callback — async workers run on separate threads with no request context |
WorkerIdleException |
Worker mode, between requests — the worker is waiting for the next request |
AsyncContextException and WorkerIdleException both extend NoActiveRequestException, so catching the base class handles all cases.
<?php
try {
$request = oxphp_http_request();
} catch (\OxPHP\Http\Exception\AsyncContextException $e) {
// Inside oxphp_async() — no request context here
} catch (\OxPHP\Http\Exception\WorkerIdleException $e) {
// Worker is between requests — do not call oxphp_http_request() here
} catch (\OxPHP\Http\Exception\NoActiveRequestException $e) {
// Any other case with no active request
}In normal request-handling code, you do not need this try/catch. The exception guard is useful in bootstrap code that might run outside a request context.
SUPERGLOBALS_ENABLED
SUPERGLOBALS_ENABLED=true # default — full backward compatibility
SUPERGLOBALS_ENABLED=false # superglobals are empty arraysBy default, OxPHP populates $_GET, $_POST, $_COOKIE, $_FILES, and $_SERVER as usual. The HTTP Object API is available alongside superglobals in this mode.
Setting SUPERGLOBALS_ENABLED=false makes those arrays empty, which eliminates the cost of building them for every request. The following still work regardless of this setting:
| Feature | Behavior with SUPERGLOBALS_ENABLED=false |
|---|---|
oxphp_http_request() |
Always available |
php://input |
Available (it is a stream, not a superglobal) |
$_SESSION |
Available (managed by PHP's session module) |
header(), headers_list() |
Available (SAPI output functions) |
session_start(), session_*() |
Available (native PHP functions) |
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVER |
Empty arrays |
Use oxphp_superglobals_enabled() to check the current setting at runtime:
<?php
if (!oxphp_superglobals_enabled()) {
$method = oxphp_http_request()->method();
} else {
$method = $_SERVER['REQUEST_METHOD'];
}Worker Mode
In worker mode, a new Request object is created for each incoming request. The previous request's object becomes invalid once the request completes. Do not store a reference to it between requests.
<?php
// worker.php
require __DIR__ . '/vendor/autoload.php';
$app = new MyApp\Application();
oxphp_worker(function () use ($app) {
$request = oxphp_http_request();
$app->handle($request);
});All caches on the Request object (parsed headers, cookies, query parameters, payload) are cleared automatically when the next request begins.
IDE support
Install the stub package to get autocompletion and type checking in PhpStorm, VS Code, or any LSP-aware editor:
composer require --dev oxphp/stubsThe stubs package provides:
oxphp-stubs/
├── OxPHP/Http/
│ ├── RequestInterface.php
│ ├── SessionInterface.php
│ ├── UploadedFileInterface.php
│ ├── AttributesInterface.php
│ ├── Request.php
│ ├── Session.php
│ ├── UploadedFile.php
│ ├── Attributes.php
│ └── Exception/
│ ├── NoActiveRequestException.php
│ ├── AsyncContextException.php
│ └── WorkerIdleException.php
└── functions.phpNo runtime dependency is added. The package is require-dev only.
Examples
Traditional mode
<?php
$request = oxphp_http_request();
$method = $request->method(); // "GET"
$path = $request->path(); // "/api/articles"
$page = $request->query('page', 1); // "2" or 1 (default)
// Authorization header
if (!$request->hasHeader('Authorization')) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
$token = $request->header('Authorization');
// Structured logging with request metadata
error_log(sprintf(
'[%s] %s %s from %s',
oxphp_request_id(),
$method,
$path,
$request->ip()
));POST with JSON body
<?php
$request = oxphp_http_request();
if (!$request->isMethod('POST')) {
http_response_code(405);
exit;
}
$email = $request->payload('email');
$password = $request->payload('password');
if (!$email || !$password) {
http_response_code(400);
echo json_encode(['error' => 'email and password are required']);
exit;
}
// payload() handles JSON, form-urlencoded, and multipart
// No manual json_decode() or $_POST check needed
$user = authenticate($email, $password);
header('Content-Type: application/json');
echo json_encode(['token' => $user->generateToken()]);Middleware attributes
<?php
// auth-middleware.php
function authenticate_request(\OxPHP\Http\RequestInterface $request): void
{
$token = $request->header('Authorization');
if (!$token) {
http_response_code(401);
exit;
}
$user = verify_token(str_replace('Bearer ', '', $token));
if (!$user) {
http_response_code(403);
exit;
}
$request->attributes()->set('user', $user);
}
// route-handler.php
$request = oxphp_http_request();
authenticate_request($request);
$user = $request->attributes()->get('user');
echo json_encode(['id' => $user->id, 'name' => $user->name]);Worker Mode with session
<?php
// worker.php
require __DIR__ . '/vendor/autoload.php';
oxphp_worker(function () {
$request = oxphp_http_request();
if ($request->path() === '/login' && $request->isMethod('POST')) {
$username = $request->payload('username');
$password = $request->payload('password');
if (verify_credentials($username, $password)) {
session_start();
$_SESSION['user'] = $username;
$_SESSION['authenticated'] = true;
header('Location: /dashboard');
} else {
http_response_code(401);
echo 'Invalid credentials';
}
return;
}
if ($request->path() === '/dashboard') {
session_start();
$session = $request->session();
if (!$session || !$session->get('authenticated')) {
header('Location: /login');
return;
}
echo 'Welcome, ' . htmlspecialchars($session->get('user'));
}
});See also
- Superglobals — how OxPHP populates
$_SERVER,$_GET,$_POST,$_COOKIE, and$_FILES - PHP Functions — full reference for
oxphp_http_request(),oxphp_superglobals_enabled(), and all other built-in functions - Worker Mode — persistent PHP processes and the request lifecycle
- Configuration Reference —
SUPERGLOBALS_ENABLEDand other environment variables