HTTP 请求 API

OxPHP 提供了一个面向对象的 API 来访问 HTTP 请求数据。你不再直接读取 $_GET$_POST$_COOKIE$_FILES$_SERVER,而是调用 Request 对象上的方法,它只返回你所请求的内容,不多也不少。

目录

概述

oxphp_http_request() 返回一个只读代理,指向存储在当前工作线程中的 HTTP 请求数据。数据是延迟获取的。像 $request->header('Accept') 这样的单次方法调用会直接访问 Rust 端的数据结构,只返回那一个值。而像 $request->headers() 这样返回整个数组的调用则会构建一次数组,并在整个请求期间将其缓存在 PHP 对象中。

为什么用它而不是超全局变量?

  • 内置 JSON 请求体解析。 $request->payload() 无需额外代码即可解析 application/jsonapplication/x-www-form-urlencodedmultipart/form-data
  • 不会写错数组键。 $request->method()$_SERVER['REQUEST_METHOD'] 更不容易拼错。
  • 文件上传类型自动检测。 $request->file('avatar')->type() 返回的 MIME 类型是根据文件的实际内容确定的,而非客户端提供的值。
  • 可测试。 由于行为由接口定义,你可以在单元测试中注入 mock 实现。
  • 超全局变量仍然可用。 设置 SUPERGLOBALS_ENABLED=false 是可选的。无论如何,对象 API 都能正常工作。

获取 Request 对象

php
<?php $request = oxphp_http_request();

你可以在处于活动 HTTP 请求中执行的脚本的任何位置调用 oxphp_http_request(),包括在 oxphp_worker() 回调内部:

php
<?php oxphp_worker(function () { $request = oxphp_http_request(); $method = $request->method(); // ... });

RequestInterface 方法

URI 与方法

php
$request->method(): string

以大写形式返回 HTTP 方法:"GET""POST""PUT""PATCH""DELETE" 等。

php
$request->isMethod(string $method): bool

不区分大小写的方法检查。

php
$request->path(): string

不含查询字符串的 URI 路径:"/users/42"

php
$request->fullUri(): string

完整的 URI,包含协议(scheme)、主机、可选的非标准端口、路径和查询字符串:"https://example.com:8080/users/42?page=2"。标准端口(HTTP 为 80,HTTPS 为 443)会被省略。

php
$request->scheme(): string

"https""http"

php
$request->isSecure(): bool

当协议为 "https" 时返回 true

php
$request->host(): string

来自 Host 请求头的主机名。当该请求头缺失时(不带 Host 请求头的 HTTP/1.0 请求)返回空字符串。

php
$request->port(): int

来自 Host 请求头的端口。当未显式指定时,返回对应协议的默认端口:HTTP 为 80,HTTPS 为 443

位于反向代理之后

TRUSTED_PROXIES 包含对端时,scheme()isSecure()host()port() 会遵循 X-Forwarded-ProtoX-Forwarded-Host。没有可信代理时,它们反映的是直接连接的信息。

php
$request->queryString(): ?string

不含开头 ? 的原始查询字符串。当没有查询字符串时返回 null

协议

php
$request->httpProtocol(): string

完整的协议字符串:"HTTP/1.1""HTTP/2"

php
$request->httpProtocolVersion(): string

仅版本号:"1.1""2"

查询参数

php
$request->query(?string $key = null, mixed $default = null): mixed

访问查询字符串参数。

调用 返回
$request->query() 所有参数组成的数组,包括嵌套数组
$request->query('page') page 的值,若不存在则为 null
$request->query('page', 1) page 的值,若不存在则为 1

方括号表示法(?tags[]=php&tags[]=async)会被解析为嵌套数组:

php
// 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"]]

找到的值始终是字符串。当键不存在时,$default 会原样返回。

解析后的请求体

php
$request->payload(?string $key = null, mixed $default = null): mixed

返回解析后的请求体。请求体会根据 Content-Type 请求头进行解析:

Content-Type 返回
application/x-www-form-urlencoded 字段值组成的关联数组
multipart/form-data 文本字段值组成的关联数组
application/json 解码后的数组或标量;无效 JSON 返回 null
其他任何值 null

payload() 不限于 POST 请求。它适用于 PUT、PATCH 以及任何其他发送请求体的方法。解析结果会在首次调用时缓存,并在请求的整个生命周期内复用。

调用 返回
$request->payload() 整个解析后的请求体
$request->payload('email') 单个字段的值,若不存在则为 null
$request->payload('email', '') 单个字段的值,若不存在则为 ''
php
<?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"]

请求头

php
$request->header(string $name, ?string $default = null): ?string

返回原始的请求头值。请求头名称不区分大小写。对于多值请求头(AcceptX-Forwarded-For),整行请求头会作为单个字符串返回。解析由你自行负责。

php
$request->hasHeader(string $name): bool

如果指定的请求头存在,返回 true

php
$request->headers(): array

以关联数组的形式返回所有请求头。每个键是接收到的原始请求头名称(不做规范化),每个值是原始的请求头字符串。

php
<?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

php
$request->cookie(string $name, ?string $default = null): ?string

返回单个 cookie 的值,若该 cookie 不存在则返回 $default

php
$request->cookies(): array

以名值对的关联数组形式返回所有 cookie。

php
<?php $theme = $request->cookie('theme', 'light'); // "dark" or "light" $session = $request->cookie('session'); // null if absent $all = $request->cookies(); // ["theme" => "dark", ...]

原始请求体

php
$request->body(): string

返回原始的请求体字节。这是 OxPHP 中等价于 file_get_contents('php://input') 的方式。与 payload() 不同,body() 不会被缓存。每次调用都会访问底层数据结构。

php
$request->contentType(): ?string

返回 Content-Type 请求头的值,若不存在则返回 null

php
<?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()payload() 相互独立。你可以在同一个请求中同时调用两者。

文件上传

php
$request->file(string $name): ?UploadedFileInterface

返回给定字段名对应的上传文件,若该字段不存在则返回 null。对于数组字段(name="photos[]"),返回第一个文件。

php
$request->files(?string $name = null): array
调用 返回
$request->files() 所有上传文件,作为 UploadedFileInterface 的扁平数组
$request->files('photos') photos 字段的所有文件(支持 name="photos[]"
php
<?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())); } }

客户端

php
$request->ip(): string

返回客户端 IP 地址。当配置了 TRUSTED_PROXIES 且请求对端在可信集合中时,这是 X-Forwarded-For 或 RFC 7239 Forwarded 中最右侧的不可信地址。否则它是直接对端的 IP,通常是你的负载均衡器,而非最终客户端。

对于高级场景,原始的 X-Forwarded-For 请求头仍可通过 $request->header('X-Forwarded-For') 获取,但手动解析它很少能做对(最左还是最右、没有 CIDR 信任检查)。请改为配置 TRUSTED_PROXIES。参见 可信代理

计时

php
$request->startTime(bool $asFloat = false): int|float

返回接收到此请求时的 Unix 时间戳。

调用 返回
$request->startTime() 整数秒:1711234567
$request->startTime(true) 带亚秒精度的浮点数:1711234567.3412
php
<?php $elapsed = microtime(true) - $request->startTime(true); error_log(sprintf("Request took %.3fs so far", $elapsed));

属性

php
$request->attributes(): AttributesInterface

返回当前请求的可变属性容器。使用属性可以在中间件、路由处理器以及同一请求中的其他代码之间共享数据,而无需使用全局变量。

php
<?php // In authentication middleware $request->attributes()->set('user', $authenticatedUser); // In the route handler $user = $request->attributes()->get('user');

属性是按请求隔离的,在工作进程模式下每个新请求都会重置。使用纤程(Fiber)时,属性会在同一工作线程上为同一请求运行的所有纤程之间共享。由于 PHP 纤程是协作式的,因此不可能发生并发访问。

会话

php
$request->session(): ?SessionInterface

返回 $_SESSION 的只读视图。若尚未调用 session_start(),则返回 null。会话管理(启动、保存、销毁、写入值)使用标准的 PHP 会话函数。

php
<?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 是活动会话的只读视图。

php
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; }
方法 说明
id() 会话 ID
name() 会话名称(默认:"PHPSESSID"
get(key, default) 单个会话值,若键不存在则为 $default
has(key) 若键存在于 $_SESSION 中则为 true
all() 以数组形式返回所有会话数据

会话值反映的是调用时 $_SESSION 的当前状态,而非首次调用 session() 时的状态。

UploadedFileInterface

UploadedFileInterface 表示单个上传文件。

php
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; }
方法 说明
name() 客户端发送的原始文件名
clientType() 客户端声明的 MIME 类型——不要在安全决策中信任此值
type() 使用魔数(magic byte)检测,根据文件的实际内容确定的 MIME 类型。当无法确定类型时返回 "application/octet-stream"。首次调用时缓存。
size() 文件大小(字节)
tmpPath() 磁盘上临时文件的路径
error() UPLOAD_ERR_* 常量之一
isValid() error()UPLOAD_ERR_OK 时返回 true
moveTo(path) 将文件移动到 $path。移动前会调用 type()。若文件无效或移动失败则返回 false

在使用上传文件之前,务必先检查 isValid()。在做安全敏感的决策时,应使用 type() 而非 clientType()

php
<?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 是请求对象中唯一可变的部分。它用于存储按请求隔离的元数据——已认证用户、解析后的路由参数、区域设置、特性开关——这些数据需要被应用的多个部分访问。

php
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; }
方法 说明
get(key, default) 返回 $key 对应的值,若不存在则返回 $default
set(key, value) 存储一个值
has(key) 若该键已被设置则为 true
remove(key) 移除该键
all() 以关联数组形式返回所有属性

异常

在活动请求上下文之外调用 oxphp_http_request() 会抛出 OxPHP\Http\Exception 命名空间中的异常。

php
namespace OxPHP\Http\Exception; class NoActiveRequestException extends \RuntimeException {} class AsyncContextException extends NoActiveRequestException {} class WorkerIdleException extends NoActiveRequestException {}
异常 抛出时机
NoActiveRequestException 没有活动的 HTTP 请求:CLI、MINIT、shutdown 之后,或生命周期超过其请求的纤程
AsyncContextException oxphp_async() 回调内部——异步工作进程运行在没有请求上下文的独立线程上
WorkerIdleException 工作进程模式下,两次请求之间——工作进程正在等待下一个请求

AsyncContextExceptionWorkerIdleException 都继承自 NoActiveRequestException,因此捕获基类即可处理所有情况。

php
<?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 }

在正常的请求处理代码中,你不需要这个 try/catch。这种异常防护在可能于请求上下文之外运行的引导(bootstrap)代码中才有用。

SUPERGLOBALS_ENABLED

bash
SUPERGLOBALS_ENABLED=true # default — full backward compatibility SUPERGLOBALS_ENABLED=false # superglobals are empty arrays

默认情况下,OxPHP 会像往常一样填充 $_GET$_POST$_COOKIE$_FILES$_SERVER。在此模式下,HTTP 对象 API 与超全局变量并存可用。

设置 SUPERGLOBALS_ENABLED=false 会使这些数组为空,从而消除为每个请求构建它们的开销。无论此设置如何,以下功能仍然有效:

功能 SUPERGLOBALS_ENABLED=false 时的行为
oxphp_http_request() 始终可用
php://input 可用(它是一个流,不是超全局变量)
$_SESSION 可用(由 PHP 的会话模块管理)
header()headers_list() 可用(SAPI 输出函数)
session_start()session_*() 可用(原生 PHP 函数)
$_GET$_POST$_COOKIE$_FILES$_SERVER 空数组

使用 oxphp_superglobals_enabled() 在运行时检查当前设置:

php
<?php if (!oxphp_superglobals_enabled()) { $method = oxphp_http_request()->method(); } else { $method = $_SERVER['REQUEST_METHOD']; }

工作进程模式

在工作进程模式下,每个到来的请求都会创建一个新的 Request 对象。一旦请求完成,上一个请求的对象即失效。不要在多次请求之间保存对它的引用。

worker.php
<?php // worker.php require __DIR__ . '/vendor/autoload.php'; $app = new MyApp\Application(); oxphp_worker(function () use ($app) { $request = oxphp_http_request(); $app->handle($request); });

当下一个请求开始时,Request 对象上的所有缓存(已解析的请求头、cookie、查询参数、负载)都会自动清除。

IDE 支持

安装 stub 包,即可在 PhpStorm、VS Code 或任何支持 LSP 的编辑器中获得自动补全和类型检查:

bash
composer require --dev oxphp/stubs

stubs 包提供了:

text
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.php

不会添加任何运行时依赖。该包仅用于 require-dev

示例

传统模式

php
<?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() ));

带 JSON 请求体的 POST

php
<?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()]);

中间件属性

php
<?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.php
<?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')); } });

参见

  • 超全局变量 —— OxPHP 如何填充 $_SERVER$_GET$_POST$_COOKIE$_FILES
  • PHP 函数 —— oxphp_http_request()oxphp_superglobals_enabled() 及所有其他内置函数的完整参考
  • 工作进程模式 —— 持久化的 PHP 进程与请求生命周期
  • 配置参考 —— SUPERGLOBALS_ENABLED 及其他环境变量