react / http
基于 ReactPHP 的事件驱动、流式 HTTP 客户端和服务器实现
Requires
- php: >=5.3.0
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- fig/http-message-util: ^1.1
- psr/http-message: ^1.0
- react/event-loop: ^1.2
- react/promise: ^3 || ^2.3 || ^1.2.1
- react/socket: ^1.12
- react/stream: ^1.2
Requires (Dev)
- clue/http-proxy-react: ^1.8
- clue/reactphp-ssh-proxy: ^1.4
- clue/socks-react: ^1.4
- phpunit/phpunit: ^9.6 || ^5.7 || ^4.8.36
- react/async: ^4 || ^3 || ^2
- react/promise-stream: ^1.4
- react/promise-timer: ^1.9
This package is auto-updated.
Last update: 2024-08-30 10:07:27 UTC
README
基于 ReactPHP 的事件驱动、流式 HTTP 客户端和服务器实现。
开发版本:此分支包含即将发布的 v3 版本的代码。要查看当前稳定版 v1 的代码,请查看
1.x
分支。即将发布的 v3 版本将是此包的未来发展方向。然而,我们仍将积极支持 v1 版本,以满足尚未升级到最新版本的用户。有关更多信息,请参阅 安装说明。
此 HTTP 库提供了基于 ReactPHP 的 Socket
和 EventLoop
组件的可重用 HTTP 客户端和服务器实现。其客户端组件允许您并发发送任意数量的异步 HTTP/HTTPS 请求。其服务器组件允许您构建接受来自 HTTP 客户端(如网页浏览器)的 HTTP 请求的纯文本 HTTP 和安全 HTTPS 服务器。此库为此提供了异步、流式的方法,因此您可以在不阻塞的情况下处理多个并发 HTTP 请求。
目录
快速入门示例
安装后,您可以使用以下代码访问 HTTP 服务器并发送一些简单的 HTTP GET 请求
<?php require __DIR__ . '/vendor/autoload.php'; $client = new React\Http\Browser(); $client->get('http://www.google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
这是一个响应所有请求的 Hello World!
的 HTTP 服务器。
<?php require __DIR__ . '/vendor/autoload.php'; $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return React\Http\Message\Response::plaintext( "Hello World!\n" ); }); $socket = new React\Socket\SocketServer('127.0.0.1:8080'); $http->listen($socket);
有关示例,请参阅 示例。
客户端使用
请求方法
最重要的是,此项目提供了一个 Browser
对象,该对象提供了一些类似于 HTTP 协议方法的方法
$browser->get($url, array $headers = []); $browser->head($url, array $headers = []); $browser->post($url, array $headers = [], string|ReadableStreamInterface $body = ''); $browser->delete($url, array $headers = [], string|ReadableStreamInterface $body = ''); $browser->put($url, array $headers = [], string|ReadableStreamInterface $body = ''); $browser->patch($url, array $headers = [], string|ReadableStreamInterface $body = '');
这些方法中的每一个都需要一个 $url
和一些可选参数来发送 HTTP 请求。每个方法名都与相应的 HTTP 请求方法匹配,例如 get()
方法发送一个 HTTP GET
请求。
你可以选择性地传递一个额外的 $headers
关联数组,这些头信息将与 HTTP 请求一起发送。此外,如果提供了出站请求体并且其大小已知且非空,每个方法将自动添加一个匹配的 Content-Length
请求头。对于空请求体,如果请求方法通常期望请求体(仅适用于 POST
、PUT
和 PATCH
HTTP 请求方法),则将只包括一个 Content-Length: 0
请求头。
如果你使用的是 流式请求体,它将默认使用 Transfer-Encoding: chunked
,除非你显式传递一个匹配的 Content-Length
请求头。有关更多详细信息,请参阅 流式请求。
默认情况下,所有上述方法都默认使用 HTTP/1.1 协议版本发送请求。如果你想要显式使用旧版 HTTP/1.0 协议版本,可以使用 withProtocolVersion()
方法。如果你想要使用任何其他或自定义的 HTTP 请求方法,可以使用 request()
方法。
上述每个方法都支持异步操作,要么通过 fulfills 返回一个 PSR-7 ResponseInterface
,要么通过 rejects 抛出一个 Exception
。有关更多详细信息,请参阅有关 promises 的下一章。
承诺
发送请求是异步的(非阻塞),因此实际上可以并行发送多个请求。Browser
将对每个请求以 PSR-7 ResponseInterface
消息进行响应,顺序不能保证。发送请求使用基于 Promise 的接口,这使得当 HTTP 请求完成时(即成功满足或由于错误而拒绝)很容易做出反应。
$browser->get($url)->then( function (Psr\Http\Message\ResponseInterface $response) { var_dump('Response received', $response); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; } );
如果你觉得这很奇怪,也可以使用更传统的 阻塞 API。
请注意,使用完整的响应消息解析 Promise 意味着整个响应体必须保存在内存中。这很容易开始,并且对于较小的响应(例如常见的 HTML 页面或 RESTful 或 JSON API 请求)来说表现良好。
你可能还想了解 流式 API
- 如果你正在处理大量的并发请求(100+)或者
- 如果你想要在发生时处理单个数据块(而无需等待完整的响应体)或者
- 如果你预计响应体的大小很大(例如,下载二进制文件时,1 MiB 或更多)或者
- 如果你不确定响应体的大小(在访问任意远程 HTTP 端点且事先不知道响应体大小时,最好是安全第一)。
取消
返回的 Promise 是这样实现的,它可以在它仍然挂起时被取消。取消挂起的 Promise 将用 Exception 拒绝其值,并清理任何底层资源。
$promise = $browser->get($url); Loop::addTimer(2.0, function () use ($promise) { $promise->cancel(); });
超时
该库使用了一种非常高效的HTTP实现,因此大多数HTTP请求通常仅需几毫秒即可完成。然而,当通过不可靠的网络(互联网)发送HTTP请求时,可能会发生一些错误,导致请求在一定时间后失败。因此,该库尊重PHP的default_socket_timeout
设置(默认60秒)作为发送外出HTTP请求并等待成功响应的超时时间,否则将取消挂起的请求并使用异常拒绝其值。
请注意,此超时值包括创建底层传输连接、发送HTTP请求、接收HTTP响应头及其完整响应体以及遵循任何最终的重定向。有关配置要遵循的重定向数量(或完全禁用重定向)的说明,请参阅下面的重定向,以及下面的流式响应,不将接收大型响应体计入此超时。
您可以使用withTimeout()
方法传递自定义的超时值(以秒为单位),如下所示
$browser = $browser->withTimeout(10.0); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // response received within 10 seconds maximum var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
类似地,您可以使用布尔值false
来不应用超时,或使用布尔值true
来恢复默认处理。有关更多详细信息,请参阅withTimeout()
。
如果您使用流式响应体,接收响应体流所需的时间不会计入超时。这允许您将此传入流保持开启更长时间,例如在下载非常大的流或在长时间连接上流式传输数据时。
如果您使用流式请求体,发送请求体流所需的时间不会计入超时。这允许您将此传出流保持开启更长时间,例如在上传非常大的流时。
请注意,此超时处理适用于更高级别的HTTP层。底层(如套接字和DNS)也可能应用(不同的)超时值。特别是,底层套接字连接使用相同的default_socket_timeout
设置来建立底层传输连接。要控制此连接超时行为,您可以注入自定义Connector
,如下所示
$browser = new React\Http\Browser( new React\Socket\Connector( [ 'timeout' => 5 ] ) );
身份验证
此库支持使用Authorization: Basic …
请求头或允许您设置显式的Authorization
请求头来实现HTTP基本身份验证。
默认情况下,此库不包含外出Authorization
请求头。如果服务器需要身份验证,它可能会返回一个401
(未授权)状态码,该状态码将默认拒绝请求(有关详细信息,请参阅下面的withRejectErrorResponse()
方法)。
为了传递身份验证详细信息,您只需将用户名和密码作为请求URL的一部分传递即可,如下所示
$promise = $browser->get('https://user:[email protected]/api');
请注意,在认证详情中特殊字符需要进行百分号编码,请参阅rawurlencode()
。本示例将自动使用出站请求头 Authorization: Basic …
传递base64编码的认证详情。如果您所访问的HTTP端点需要其他认证方案,也可以显式传递此请求头。这在使用(RESTful)HTTP API并使用OAuth访问令牌或JSON Web Tokens (JWT) 时很常见。
$token = 'abc123'; $promise = $browser->get( 'https://example.com/api', [ 'Authorization' => 'Bearer ' . $token ] );
在遵循重定向时,默认情况下,Authorization
请求头不会发送到任何远程主机。当遵循一个包含认证详情的 Location
响应头的重定向时,这些详情将被发送以进行后续请求。请参阅下面的重定向。
重定向
默认情况下,此库遵循任何重定向,并遵循来自远程服务器的 Location
响应头中的 3xx
(重定向)状态码。承诺将在重定向链的最后一个响应中得到满足。
$browser->get($url, $headers)->then(function (Psr\Http\Message\ResponseInterface $response) { // the final response will end up here var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
任何重定向请求都将遵循原始请求的语义,并将包括与原始请求相同的请求头,除了以下列出的那些。如果原始请求是一个临时(307)或永久(308)重定向,请求体和请求头将被传递给重定向请求。否则,请求体将永远不会传递给重定向请求。因此,每个重定向请求都将删除任何 Content-Length
和 Content-Type
请求头。
如果原始请求使用了带有 Authorization
请求头的HTTP认证,则此请求头只有在重定向URL使用相同的宿主机时才会作为重定向请求的一部分传递。换句话说,由于可能存在隐私/安全顾虑,Authorizaton
请求头不会转发到其他外部主机。当遵循一个包含认证详情的 Location
响应头的重定向时,这些详情将被发送以进行后续请求。
您可以使用withFollowRedirects()
方法来控制要遵循的最大重定向数或以原样返回任何重定向响应并应用自定义重定向逻辑,如下所示:
$browser = $browser->withFollowRedirects(false); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // any redirects will now end up here var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关更多详细信息,请参阅withFollowRedirects()
。
阻塞
如上所述,此库默认提供了一个强大、异步的API。
您也可以使用reactphp/async将其集成到您传统的阻塞环境中。这允许您像这样简单地等待异步HTTP请求
use function React\Async\await; $browser = new React\Http\Browser(); $promise = $browser->get('http://example.com/'); try { $response = await($promise); // response successfully received } catch (Exception $e) { // an error occurred while performing the request }
同样,您也可以并发处理多个请求,并等待一个Response
对象数组
use function React\Async\await; use function React\Promise\all; $promises = [ $browser->get('http://example.com/'), $browser->get('http://www.example.org/'), ]; $responses = await(all($promises));
这是由于PHP 8.1+中可用的fibers和我们的兼容性API实现的,这些API也适用于所有支持的PHP版本。请参阅reactphp/async以获取更多详细信息。
请记住上述关于在内存中缓冲整个响应消息的说明。作为替代方案,您也可以查看以下章节中的流式API。
并发
如上所述,此库提供了一个强大、异步的API。能够一次发送大量的请求是此项目的核心功能之一。例如,您可以在同时处理SQL查询的同时轻松并发发送100个请求。
请记住,权力越大,责任越大。发送过多的请求可能会占用您方面的所有资源,甚至可能会因为远程端检测到您的不合理请求数量而被禁用。
// watch out if array contains many elements foreach ($urls as $url) { $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); }
因此,通常建议将发送端的并发限制在合理值。使用相当小的限制是常见的,因为同时做超过十几件事情可能会很容易压倒接收端。您可以使用clue/reactphp-mq作为轻量级内存队列,以同时并发执行许多(但不是太多)事情。
// wraps Browser in a Queue object that executes no more than 10 operations at once $q = new Clue\React\Mq\Queue(10, null, function ($url) use ($browser) { return $browser->get($url); }); foreach ($urls as $url) { $q($url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); }
超出并发限制的附加请求将自动入队,直到挂起的请求之一完成。这很好地与现有的基于Promise的API集成。有关详细信息,请参阅clue/reactphp-mq。
此内存方法对于数千个待处理请求来说效果相当好。如果您正在处理非常大的输入列表(例如CSV或NDJSON文件中的数百万行),则可能需要考虑使用流式处理方法。有关详细信息,请参阅clue/reactphp-flux。
流式响应
上述所有示例都假设您想将整个响应体存储在内存中。这很容易开始,并且对于较小的响应来说效果相当好。
然而,在某些情况下,使用流式处理方法通常是一个更好的选择,其中只需要在内存中保留小块数据。
- 如果你正在处理大量的并发请求(100+)或者
- 如果你想要在发生时处理单个数据块(而无需等待完整的响应体)或者
- 如果你预计响应体的大小很大(例如,下载二进制文件时,1 MiB 或更多)或者
- 如果你不确定响应体的大小(在访问任意远程 HTTP 端点且事先不知道响应体大小时,最好是安全第一)。
您可以使用requestStreaming()
方法发送任意HTTP请求并接收流式响应。它使用相同的HTTP消息API,但不缓冲内存中的响应体。它仅以接收到的数据的小块处理响应体,并通过ReactPHP的Stream API转发这些数据。这对于任意大小(任何数量的)响应都适用。
这意味着它以正常的PSR-7 ResponseInterface
解析,可以像往常一样访问响应消息参数。您可以像往常一样访问消息正文,但现在它还实现了ReactPHP的ReadableStreamInterface
以及PSR-7 StreamInterface
的部分。
$browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) { $body = $response->getBody(); assert($body instanceof Psr\Http\Message\StreamInterface); assert($body instanceof React\Stream\ReadableStreamInterface); $body->on('data', function ($chunk) { echo $chunk; }); $body->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); $body->on('close', function () { echo '[DONE]' . PHP_EOL; }); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
您可以在消息正文中调用以下方法:
$body->on($event, $callback); $body->eof(); $body->isReadable(); $body->pipe(React\Stream\WritableStreamInterface $dest, array $options = []); $body->close(); $body->pause(); $body->resume();
由于消息体处于流状态,调用以下方法没有太大意义。
$body->__toString(); // '' $body->detach(); // throws BadMethodCallException $body->getSize(); // null $body->tell(); // throws BadMethodCallException $body->isSeekable(); // false $body->seek(); // throws BadMethodCallException $body->rewind(); // throws BadMethodCallException $body->isWritable(); // false $body->write(); // throws BadMethodCallException $body->read(); // throws BadMethodCallException $body->getContents(); // throws BadMethodCallException
请注意,在流式处理模式下,超时的应用略有不同。在流式处理模式下,超时值涵盖了创建底层传输连接、发送HTTP请求、接收HTTP响应头以及任何可能的重定向。特别是,超时值不考虑接收(可能很大)的响应体。
如果您想将流式响应集成到更高层次的API中,那么与解析为流对象的Promise对象一起工作通常不方便。考虑也使用react/promise-stream。生成的流式代码可能如下所示
use function React\Promise\Stream\unwrapReadable; function download(Browser $browser, string $url): React\Stream\ReadableStreamInterface { return unwrapReadable( $browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) { return $response->getBody(); }) ); } $stream = download($browser, $url); $stream->on('data', function ($data) { echo $data; }); $stream->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关requestStreaming()
方法的详细信息,请参阅。
流式请求
除了流式传输响应体之外,您还可以流式传输请求体。如果您想发送大的POST请求(上传文件等)或同时处理多个出站流,这会非常有用。您不需要将正文作为字符串传递,只需将实现ReactPHP的ReadableStreamInterface
的实例传递给请求方法即可,如下所示
$browser->post($url, [], $stream)->then(function (Psr\Http\Message\ResponseInterface $response) { echo 'Successfully sent.'; }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
如果您使用的是流式请求体(React\Stream\ReadableStreamInterface
),它将默认使用Transfer-Encoding: chunked
,或者您必须显式传递匹配的Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->post($url, ['Content-Length' => '11'], $body);
如果流式请求体触发一个error
事件或在未首先触发成功的end
事件的情况下显式关闭,请求将自动关闭并拒绝。
HTTP 代理
您还可以通过添加对clue/reactphp-http-proxy的依赖来通过HTTP CONNECT代理服务器建立您的出站连接。
HTTP CONNECT代理服务器(也通常称为“HTTPS代理”或“SSL代理”)通常用于通过中介(“代理”)隧道HTTPS流量,以隐藏原始地址(匿名性)或绕过地址封锁(地理位置封锁)。虽然许多(公共)HTTP CONNECT代理服务器通常仅限于HTTPS端口443
,但从技术上讲,这可以用于隧道任何基于TCP/IP的协议,例如纯HTTP和TLS加密的HTTPS。
$proxy = new Clue\React\HttpProxy\ProxyConnector('127.0.0.1:8080'); $connector = new React\Socket\Connector([ 'tcp' => $proxy, 'dns' => false ]); $browser = new React\Http\Browser($connector);
请参阅HTTP代理示例。
SOCKS 代理
您还可以通过添加对clue/reactphp-socks的依赖来通过SOCKS代理服务器建立您的出站连接。
SOCKS代理协议族(SOCKS5、SOCKS4和SOCKS4a)通常用于通过中介(“代理”)隧道HTTP(S)流量,以隐藏原始地址(匿名性)或绕过地址封锁(地理位置封锁)。虽然许多(公共)SOCKS代理服务器通常仅限于HTTP(S)端口80
和443
,但从技术上讲,这可以用于隧道任何基于TCP/IP的协议。
$proxy = new Clue\React\Socks\Client('127.0.0.1:1080'); $connector = new React\Socket\Connector([ 'tcp' => $proxy, 'dns' => false ]); $browser = new React\Http\Browser($connector);
请参阅SOCKS代理示例。
SSH 代理
您还可以通过添加对clue/reactphp-ssh-proxy的依赖来通过SSH服务器建立您的出站连接。
安全外壳(SSH)是一种安全网络协议,最常用于访问远程服务器上的登录外壳。其架构允许它在单个连接上使用多个安全通道。除此之外,这也可以用来创建“SSH隧道”,通常用于通过中介(“代理”)隧道HTTP(S)流量,以隐藏原始地址(匿名性)或绕过地址封锁(地理位置封锁)。这可以用来隧道任何基于TCP/IP的协议(HTTP、SMTP、IMAP等),允许您访问否则无法从外部访问的本地服务(例如防火墙后面的数据库),因此也可以用于纯HTTP和TLS加密的HTTPS。
$proxy = new Clue\React\SshProxy\SshSocksConnector('[email protected]'); $connector = new React\Socket\Connector([ 'tcp' => $proxy, 'dns' => false ]); $browser = new React\Http\Browser($connector);
请参阅SSH代理示例。
Unix 域套接字
默认情况下,此库支持通过明文TCP/IP和安全的TLS连接分别对http://
和https://
URL方案进行传输。此外,此库还支持显式配置的Unix域套接字(UDS)。
为了使用UDS路径,您必须显式配置连接器以覆盖目标URL,这样在请求URL中给出的主机名将不再用于建立连接
$connector = new React\Socket\FixedUriConnector( 'unix:///var/run/docker.sock', new React\Socket\UnixConnector() ); $browser = new React\Http\Browser($connector); $client->get('http://localhost/info')->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders(), (string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
另请参阅Unix域套接字(UDS)示例。
服务器使用
HttpServer
React\Http\HttpServer
类负责处理传入的连接,然后处理每个传入的HTTP请求。
收到完整的HTTP请求后,它将调用给定的请求处理函数。这个请求处理函数需要传递给构造函数,并将与相应的请求对象一起调用,并期望返回一个响应对象。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return React\Http\Message\Response::plaintext( "Hello World!\n" ); });
每个传入的HTTP请求消息始终由PSR-7 ServerRequestInterface
表示,有关更多详细信息,请参阅以下请求章节。
每个输出的HTTP响应消息始终由PSR-7 ResponseInterface
表示,有关更多详细信息,请参阅以下响应章节。
此类接受一个可选的LoopInterface|null $loop
参数,可以用来传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确定要显式使用给定的事件循环实例,否则不应提供此值。
为了开始监听任何传入的连接,HttpServer
需要通过listen()
方法附加到React\Socket\ServerInterface
的实例,如以下章节所述。在其最简单的形式中,您可以将其附加到React\Socket\SocketServer
以启动一个文本HTTP服务器,如下所示
$http = new React\Http\HttpServer($handler); $socket = new React\Socket\SocketServer('0.0.0.0:8080'); $http->listen($socket);
有关详细信息,请参阅listen()
方法和hello world服务器示例。
默认情况下,HttpServer
会在内存中缓冲并解析完整的传入HTTP请求。它将在收到完整的请求头和请求体后调用给定的请求处理函数。这意味着传递给您的请求处理函数的请求对象将与PSR-7(http-message)完全兼容。这为80%的使用场景提供了合理的默认设置,除非您确定您知道自己在做什么,否则这是推荐使用此库的方式。
另一方面,在内存中缓冲完整的HTTP请求,直到可以由您的请求处理函数处理,意味着此类必须采用一些限制以避免消耗过多的内存。为了提供更高级的配置,它将尊重您的php.ini
设置来应用其默认设置。以下是此类尊重的PHP设置及其相应的默认值列表
memory_limit 128M
post_max_size 8M // capped at 64K
enable_post_data_reading 1
max_input_nesting_level 64
max_input_vars 1000
file_uploads 1
upload_max_filesize 2M
max_file_uploads 20
特别是,post_max_size
设置限制了单个 HTTP 请求在缓冲其请求体时可以消耗多少内存。这需要限制,因为服务器可以并发处理大量请求,否则服务器可能会消耗大量内存。为了默认支持更高的并发性,此值被限制在 64K
。如果您分配一个更高的值,它将默认只允许 64K
。如果请求超出此限制,其请求体将被忽略,并且它将被处理为没有任何请求体的请求。有关如何覆盖此设置的显式配置,请参见下文。
默认情况下,此类将尝试避免为缓冲多个并发 HTTP 请求而消耗超过您 memory_limit
的一半。因此,在上述默认的 128M
最大值下,它将尝试为缓冲多个并发 HTTP 请求消耗不超过 64M
。因此,它将并发性限制在上述默认值下的 1024
个 HTTP 请求。
您必须为您的 PHP ini 设置分配合理的值。通常建议不支持使用大 HTTP 请求体(例如,大文件上传)来缓冲传入的 HTTP 请求。如果您想增加此缓冲区大小,您还必须增加总内存限制以允许更多并发请求(设置 memory_limit 512M
或更多)或显式限制并发性。
为了覆盖上述缓冲默认值,您可以显式配置 HttpServer
。您可以使用 LimitConcurrentRequestsMiddleware
和 RequestBodyBufferMiddleware
(见下文)来显式配置一次可以处理的请求数量,如下所示
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request new React\Http\Middleware\RequestBodyParserMiddleware(), $handler );
在此示例中,我们允许同时处理高达 100 个并发请求,并且每个请求可以缓冲高达 2M
。这意味着您可能需要保留最多 200M
的内存用于传入请求体缓冲区。相应地,您需要调整 memory_limit
ini 设置以允许这些缓冲区以及您的实际应用程序逻辑内存需求(例如 512M
或更多)。
内部,当没有提供
StreamingRequestMiddleware
时,此类会自动分配这些中间件处理程序。因此,您可以使用此示例覆盖所有默认设置以实现自定义限制。
作为在内存中缓冲整个请求体的替代方案,您还可以使用流式方法,其中只需要在内存中保留少量数据块。
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), $handler );
在这种情况下,它将在收到 HTTP 请求头后调用请求处理函数,即在收到可能更大的 HTTP 请求体之前。这意味着传递给您的请求处理函数的 请求 可能与 PSR-7 不完全兼容。这是专门设计来帮助处理更复杂的用例,其中您希望完全控制消费传入 HTTP 请求体和并发设置。有关更多详细信息,请参阅下文 流式传入请求。
listen()
可以使用 listen(React\Socket\ServerInterface $socket): void
方法在给定的套接字服务器实例上开始监听 HTTP 请求。
给定的 React\Socket\ServerInterface
负责发射底层的流连接。这个HTTP服务器需要附加到它上面,以便处理任何连接并将传入的流数据作为传入的HTTP请求消息处理。在其最常见的形式中,您可以将其附加到 React\Socket\SocketServer
以启动一个类似于这样的纯文本HTTP服务器
$http = new React\Http\HttpServer($handler); $socket = new React\Socket\SocketServer('0.0.0.0:8080'); $http->listen($socket);
有关更多详细信息,请参阅 hello world服务器示例。
此示例将在所有接口(公开)上监听HTTP请求的备用HTTP端口8080
。作为替代方案,非常常见的是使用反向代理,并让这个HTTP服务器仅在本地主机(环回)接口上监听,使用监听地址 127.0.0.1:8080
替代。这样,您可以在默认HTTP端口80
上托管应用程序,并且只将特定请求路由到这个HTTP服务器。
同样,通常建议使用反向代理设置在默认HTTPS端口443
(TLS终止)上接受安全的HTTPS请求,并且只将纯文本请求路由到这个HTTP服务器。作为替代方案,您还可以使用安全的TLS监听地址、证书文件以及可选的passphrase
将安全的HTTPS请求与这个HTTP服务器一起接受,如下所示
$http = new React\Http\HttpServer($handler); $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', [ 'tls' => [ 'local_cert' => __DIR__ . '/localhost.pem' ] ]); $http->listen($socket);
有关更多详细信息,请参阅 hello world HTTPS示例。
服务器请求
如上所示,HttpServer
类负责处理传入的连接,然后处理每个传入的HTTP请求。
在客户端接收到请求后,将处理请求对象。此请求对象实现了PSR-7 ServerRequestInterface
,该接口又扩展了PSR-7 RequestInterface
,并将其传递给回调函数,如下所示。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $body = "The method of the request is: " . $request->getMethod() . "\n"; $body .= "The requested path is: " . $request->getUri()->getPath() . "\n"; return React\Http\Message\Response::plaintext( $body ); });
有关请求对象的更多详细信息,请参阅PSR-7 ServerRequestInterface
和PSR-7 RequestInterface
的文档。
请求参数
可以使用getServerParams(): mixed[]
方法获取类似于$_SERVER
变量的服务器端参数。以下参数目前可用
REMOTE_ADDR
请求发送者的IP地址REMOTE_PORT
请求发送者的端口号SERVER_ADDR
服务器的IP地址SERVER_PORT
服务器的端口号REQUEST_TIME
接收到完整的请求头时的Unix时间戳,作为整数,类似于time()
REQUEST_TIME_FLOAT
接收到完整的请求头时的Unix时间戳,作为浮点数,类似于microtime(true)
HTTPS
如果请求使用了HTTPS,则设置为'on',否则不会设置
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $body = "Your IP is: " . $request->getServerParams()['REMOTE_ADDR'] . "\n"; return React\Http\Message\Response::plaintext( $body ); });
有关更多示例,请参阅 whatsmyip服务器示例。
高级:请注意,如果您正在监听Unix域套接字(UDS)路径,则地址参数将不会设置,因为该协议缺乏主机/端口号的概念。
查询参数
可以使用getQueryParams(): array
方法获取查询参数,类似于$_GET
变量。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $queryParams = $request->getQueryParams(); $body = 'The query parameter "foo" is not set. Click the following link '; $body .= '<a href="/?foo=bar">to use query parameter in your request</a>'; if (isset($queryParams['foo'])) { $body = 'The value of "foo" is: ' . htmlspecialchars($queryParams['foo']); } return React\Http\Message\Response::html( $body ); });
上述示例中的响应将返回一个包含链接的响应体。URL中包含查询参数foo
,其值为bar
。使用像以下示例中的htmlentities
一样来防止跨站脚本(简称为XSS)。
另请参阅服务器查询参数示例。
请求体
默认情况下,Server
将在内存中缓冲和解析完整的请求体。这意味着给定的请求对象包括解析后的请求体和任何文件上传。
作为默认缓冲逻辑的替代方案,您还可以使用
StreamingRequestMiddleware
。跳到下一章以了解更多关于如何处理流式请求的信息。
如上所述,每个传入的HTTP请求始终由PSR-7 ServerRequestInterface
表示。此接口提供了一些在处理传入请求体时非常有用的方法,如下所述。
可以使用getParsedBody(): null|array|object
方法获取解析后的请求体,类似于PHP的$_POST
变量。此方法可能返回包含所有正文参数的(可能嵌套的)数组结构或null
值,如果请求体无法解析。默认情况下,此方法仅返回使用Content-Type: application/x-www-form-urlencoded
或Content-Type: multipart/form-data
请求头(通常用于HTML表单提交数据的POST请求)的请求的解析数据。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $name = $request->getParsedBody()['name'] ?? 'anonymous'; return React\Http\Message\Response::plaintext( "Hello $name!\n" ); });
有关更多详细信息,请参阅表单上传示例。
可以使用getBody(): StreamInterface
方法获取请求体的原始数据,类似于PHP的php://input
流。此方法返回一个表示请求体的PSR-7 StreamInterface
实例。这在使用默认情况下不会被解析的自定义请求体时特别有用,例如JSON(Content-Type: application/json
)或XML(Content-Type: application/xml
)请求体(这通常用于基于JSON的或RESTful/RESTish API的POST、PUT或PATCH请求)。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $data = json_decode((string)$request->getBody()); $name = $data->name ?? 'anonymous'; return React\Http\Message\Response::json( ['message' => "Hello $name!"] ); });
有关更多详细信息,请参阅JSON API服务器示例。
可以使用getUploadedFiles(): array
方法获取请求中的上传文件,类似于PHP的$_FILES
变量。此方法返回一个(可能嵌套的)数组结构,包含所有文件上传,每个上传都由PSR-7 UploadedFileInterface
表示。当使用Content-Type: multipart/form-data
请求头(通常用于HTML文件上传的POST请求)时,此数组才会被填充。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $files = $request->getUploadedFiles(); $name = isset($files['avatar']) ? $files['avatar']->getClientFilename() : 'nothing'; return React\Http\Message\Response::plaintext( "Uploaded $name\n" ); });
有关更多详细信息,请参阅表单上传服务器示例。
getSize(): ?int
方法可以用来获取请求体的尺寸,类似于PHP的 $_SERVER['CONTENT_LENGTH']
变量。此方法返回根据消息边界定义的请求体完整尺寸,以字节为单位。此值可能为 0
,如果请求消息不包含请求体(例如简单的 GET
请求)。此方法操作于缓冲的请求体,即请求体尺寸始终已知,即使在请求未指定 Content-Length
请求头或使用 Transfer-Encoding: chunked
进行 HTTP/1.1 请求时也是如此。
注意:
HttpServer
会自动处理带有附加Expect: 100-continue
请求头的请求。当 HTTP/1.1 客户端想要发送更大的请求体时,它们可以仅发送带有附加Expect: 100-continue
请求头的请求头,并在发送实际(大)消息体之前等待。在这种情况下,服务器将自动向客户端发送一个中间的HTTP/1.1 100 Continue
响应。这确保您将如预期的那样立即收到请求体。
流式接收请求
如果您正在使用高级 StreamingRequestMiddleware
,则会在接收到请求头后处理请求对象。这意味着这发生在接收(可能很大的)请求体之前。
注意,这是非标准行为,被认为是高级用法。跳转到上一章了解更多关于如何处理缓冲的 请求体 的信息。
虽然在 PHP 生态系统中这种情况可能不常见,但这实际上是一种非常强大的方法,给您带来了一些其他方法无法实现的优点
- 在接收到大请求体之前对请求做出反应,例如拒绝未经认证的请求或超出允许的消息长度(文件上传)的请求。
- 在请求体的其余部分到达之前或发送者缓慢流式传输数据之前开始处理请求体的部分。
- 在不将任何内容缓冲到内存中的情况下处理大请求体,例如接受巨大的文件上传或可能的无限请求体流。
getBody(): StreamInterface
方法可以用来访问请求体流。在流模式中,此方法返回一个实现了 PSR-7 StreamInterface
和 ReactPHP ReadableStreamInterface
的流实例。然而,大多数 PSR-7 StreamInterface
方法都是在假设控制同步请求体的前提下设计的。鉴于这一点不适用于此服务器,以下 PSR-7 StreamInterface
方法未被使用,不应调用:tell()
、eof()
、seek()
、rewind()
、write()
和 read()
。如果这对您的用例有问题,并且/或者您想访问上传的文件,强烈建议使用缓冲的 请求体 或使用 RequestBodyBufferMiddleware
。
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), function (Psr\Http\Message\ServerRequestInterface $request) { $body = $request->getBody(); assert($body instanceof Psr\Http\Message\StreamInterface); assert($body instanceof React\Stream\ReadableStreamInterface); return new React\Promise\Promise(function ($resolve, $reject) use ($body) { $bytes = 0; $body->on('data', function ($data) use (&$bytes) { $bytes += strlen($data); }); $body->on('end', function () use ($resolve, &$bytes){ $resolve(React\Http\Message\Response::plaintext( "Received $bytes bytes\n" )); }); // an error occures e.g. on invalid chunked encoded data or an unexpected 'end' event $body->on('error', function (Exception $e) use ($resolve, &$bytes) { $resolve(React\Http\Message\Response::plaintext( "Encountered error after $bytes bytes: {$e->getMessage()}\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST)); }); }); } );
上面的示例简单地计算了请求体中接收到的字节数。这可以用作缓冲或处理请求体的框架。
有关详细信息,请参阅 流式请求服务器示例。
每当请求体流中出现新数据时,将发出 data
事件。服务器也会自动处理使用 Transfer-Encoding: chunked
编码的任何传入请求,并将仅作为数据发出实际有效载荷。
当请求体流成功终止时,将发出 end
事件,即已读取到其预期结束。
如果请求流包含对 Transfer-Encoding: chunked
无效的数据,或者在接收完整请求流之前连接关闭时,将发出 error
事件。服务器将自动停止从连接读取并丢弃所有传入数据,而不是关闭它。仍然可以发送响应消息(除非连接已关闭)。
在 error
或 end
事件之后将发出 close
事件。
有关请求体流的更多详细信息,请参阅 ReactPHP ReadableStreamInterface
文档。
可以使用 getSize(): ?int
方法来获取请求体的大小,类似于 PHP 的 $_SERVER['CONTENT_LENGTH']
变量。此方法返回按字节计数的请求体完整大小,如消息边界定义。如果请求消息不包含请求体(如简单的 GET
请求),则此值可能为 0
。此方法针对流式请求体,即使用 Transfer-Encoding: chunked
对 HTTP/1.1 请求进行时,请求体大小可能未知(null
)。
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), function (Psr\Http\Message\ServerRequestInterface $request) { $size = $request->getBody()->getSize(); if ($size === null) { $body = "The request does not contain an explicit length. "; $body .= "This example does not accept chunked transfer encoding.\n"; return React\Http\Message\Response::plaintext( $body )->withStatus(React\Http\Message\Response::STATUS_LENGTH_REQUIRED); } return React\Http\Message\Response::plaintext( "Request body size: " . $size . " bytes\n" ); } );
注意:
HttpServer
自动处理带有额外Expect: 100-continue
请求头的请求。当 HTTP/1.1 客户端想要发送更大的请求体时,它们可以只发送带有额外Expect: 100-continue
请求头的请求头,并在发送实际(大的)消息体之前等待。在这种情况下,服务器将自动向客户端发送一个中间的HTTP/1.1 100 Continue
响应。这确保您将无延迟地接收到预期的流式请求体。
请求方法
请注意,服务器支持 任何 请求方法(包括自定义和非标准方法),以及每个相应方法在 HTTP 规范中定义的所有请求目标格式,包括 普通 origin-form
请求以及 absolute-form
和 authority-form
中的代理请求。可以使用 getUri(): UriInterface
方法获取有效请求 URI,这为您提供了访问个别 URI 组件的权限。请注意(根据给定的 request-target
),某些 URI 组件可能存在或不一定存在,例如,对于 asterisk-form
或 authority-form
中的请求,getPath(): string
方法将返回空字符串。它的 getHost(): string
方法将返回根据有效请求 URI 确定的主机,默认为主机地址,如果 HTTP/1.0 客户端未指定(即没有 Host
头)。它的 getScheme(): string
方法将根据请求是否通过到目标主机的安全 TLS 连接返回 http
或 https
。
如果该 URI 方案非标准,则将清洗 Host
头的值以匹配此主机组件和端口号组件。
您可以使用 getMethod(): string
和 getRequestTarget(): string
检查这是否是一个可接受的请求,并可能想要用适当的错误代码拒绝其他请求,例如 400
(错误请求)或 405
(方法不允许)。
《CONNECT》方法在隧道设置(HTTPS代理)中很有用,但不是大多数HTTP服务器所关心的。请注意,如果您想处理此方法,客户端可以发送与《Host》头值不同的请求目标(例如删除默认端口),并且请求目标在转发时必须优先。
Cookie 参数
可以使用《getCookieParams(): string[]》方法获取当前请求发送的所有cookie。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $key = 'greeting'; if (isset($request->getCookieParams()[$key])) { $body = "Your cookie value is: " . $request->getCookieParams()[$key] . "\n"; return React\Http\Message\Response::plaintext( $body ); } return React\Http\Message\Response::plaintext( "Your cookie has been set.\n" )->withHeader('Set-Cookie', $key . '=' . urlencode('Hello world!')); });
上面的示例将尝试在第一次访问时设置cookie,并将尝试在所有后续尝试中打印cookie值。注意示例如何使用《urlencode()`》函数来编码非字母数字字符。这种编码在解码cookie的名称和值时也用于内部(与其他实现一致,如PHP的cookie函数)。
有关更多详细信息,请参阅cookie服务器示例。
无效请求
《HttpServer》类支持HTTP/1.1和HTTP/1.0请求消息。如果客户端发送无效的请求消息,使用无效的HTTP协议版本或发送无效的《Transfer-Encoding》请求头值,则服务器将自动向客户端发送《400》(错误请求)HTTP错误响应并关闭连接。除此之外,它还会发出一个《error》事件,可用于记录此事件。
$http->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
请注意,如果您的请求处理函数没有返回有效的响应对象,服务器也会发出一个《error》事件。有关更多详细信息,请参阅无效响应。
服务器响应
传递给《HttpServer》构造函数的回调函数负责处理请求并返回响应,该响应将发送给客户端。
此函数必须返回一个实现《PSR-7 ResponseInterface》的实例或一个解析为《PSR-7 ResponseInterface》对象的《ReactPHP Promise》。
该项目提供了一个实现《PSR-7 ResponseInterface》的《Response》类。在其最简单形式中,您可以使用它如下
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return React\Http\Message\Response::plaintext( "Hello World!\n" ); });
我们在我们的项目示例中使用了这个《Response》类,但您也可以使用《PSR-7 ResponseInterface》的任何其他实现。有关更多详细信息,请参阅《Response》类。
延迟响应
上面的示例直接返回响应,因为它不需要处理时间。使用数据库、文件系统或长时间计算(实际上任何将需要≥1ms的操作)来创建您的响应,将减慢服务器的速度。为了防止这种情况,您应该使用《ReactPHP Promise》。此示例显示了这种长期操作可能的样子
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $promise = new Promise(function ($resolve, $reject) { Loop::addTimer(1.5, function() use ($resolve) { $resolve(); }); }); return $promise->then(function () { return React\Http\Message\Response::plaintext( "Hello World!" ); }); });
上述示例将在1.5秒后创建响应。这个示例表明,如果你的响应需要时间来创建,你需要一个承诺。当请求体结束时,ReactPHP的承诺将在响应对象中解决。如果客户端在承诺待定期间关闭了连接,承诺将自动取消。在这种情况下,可以取消承诺处理程序来清理(如果适用)分配的任何待定资源。如果客户端关闭后承诺被解决,它将被简单地忽略。
流式发送响应
本项目中的Response
类支持为响应体添加实现ReactPHP的ReadableStreamInterface
的实例。因此,您可以直接将数据流式传输到响应体中。请注意,其他实现PSR-7的ResponseInterface
可能只支持字符串。
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $stream = new ThroughStream(); // send some data every once in a while with periodic timer $timer = Loop::addPeriodicTimer(0.5, function () use ($stream) { $stream->write(microtime(true) . PHP_EOL); }); // end stream after a few seconds $timeout = Loop::addTimer(5.0, function() use ($stream, $timer) { Loop::cancelTimer($timer); $stream->end(); }); // stop timer if stream is closed (such as when connection is closed) $stream->on('close', function () use ($timer, $timeout) { Loop::cancelTimer($timer); Loop::cancelTimer($timeout); }); return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/plain' ], $stream ); });
上述示例将每0.5秒向客户端发出当前Unix时间戳(以浮点数表示),并在5秒后结束。这只是您可以使用流的一个示例,您也可以通过小块数据发送大量数据或用于需要计算的正文数据。
如果请求处理程序使用已关闭的响应流解决,它将简单地发送一个空响应体。如果客户端在流仍然打开时关闭连接,响应流将自动关闭。如果客户端关闭后承诺使用流式传输正文解决,响应流将自动关闭。在这种情况下,可以使用close
事件来清理(如果适用)分配的任何待定资源。
请注意,如果您使用的是实现ReactPHP的
DuplexStreamInterface
(例如上述示例中的ThroughStream
)的实例,则需要特别注意。对于大多数情况,这只会消耗它的可读部分,并将流发出的任何数据转发(发送),从而完全忽略流的可写部分。然而,如果这是一个
101
(切换协议)响应或对CONNECT
方法的2xx
(成功)响应,它还将写入流的可写部分。可以通过拒绝所有使用CONNECT
方法的请求(这是大多数正常原点HTTP服务器可能做的事情)或确保始终使用ReactPHP的ReadableStreamInterface
实例来避免这种情况。
101
(切换协议)响应代码对于更高级的Upgrade
请求很有用,例如升级到WebSocket协议或实现超出HTTP规范和此HTTP库的HTTP协议逻辑。如果您想处理Upgrade: WebSocket
头,您可能想考虑使用Ratchet。如果您想处理自定义协议,您可能需要查看HTTP规范,并查看示例#81和#82以获取更多详细信息。特别是,除非您发送的Upgrade
响应头值也存在于相应的HTTP/1.1Upgrade
请求头值中,否则不得使用101
(切换协议)响应代码。在这种情况下,服务器会自动处理发送Connection: upgrade
头值,因此您不需要这样做。《CONNECT》方法在隧道设置(HTTPS代理)中非常有用,但并不是大多数原始HTTP服务器所关心的事情。HTTP规范为该方法定义了一个不透明的“隧道模式”,并且不使用消息体。出于一致性的原因,此库在隧道应用数据响应体中使用
DuplexStreamInterface
。这意味着实际上对CONNECT
请求的2xx
(成功)响应可以使用流响应体来传输隧道应用数据,因此客户端通过连接发送的任何原始数据都将通过可写流进行传输。请注意,尽管HTTP规范没有使用CONNECT
请求的消息体,但仍可能存在。正常请求体处理适用于此,并且只有在请求体被处理后(在大多数情况下应该是空的)连接才会转换为“隧道模式”。有关更多详细信息,请参阅HTTP CONNECT服务器示例。
响应长度
如果已知响应体大小,将自动添加Content-Length
响应头。这是最常见的情况,例如当使用如下所示的string
响应体时
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { return React\Http\Message\Response::plaintext( "Hello World!\n" ); });
如果未知响应体大小,则无法自动添加Content-Length
响应头。当使用没有显式Content-Length
响应头的流式输出响应时,HTTP/1.1响应消息将自动使用Transfer-Encoding: chunked
,而传统HTTP/1.0响应消息将包含纯响应体。如果您知道流式响应体的长度,您可以像这样显式指定它
$http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { $stream = new ThroughStream(); Loop::addTimer(2.0, function () use ($stream) { $stream->end("Hello World!\n"); }); return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Length' => '13', 'Content-Type' => 'text/plain', ], $stream ); });
根据HTTP规范,对HEAD
请求的任何响应以及具有1xx
(信息)、204
(无内容)或304
(未修改)状态码的任何响应都将不包含消息体。这意味着您的回调函数不必对此进行特殊处理,并且任何响应体都将被简单地忽略。
类似地,对CONNECT
请求的任何2xx
(成功)响应以及任何具有1xx
(信息)或204
(无内容)状态码的响应都将不包含Content-Length
或Transfer-Encoding
头,因为这些不适用于这些消息。请注意,对HEAD
请求的响应以及任何具有304
(未修改)状态码的响应可能包含这些头,即使消息不包含响应体,因为这些头如果相同的请求使用(无条件)GET
,将适用于该消息。
无效响应
如上所述,每个输出HTTP响应始终由PSR-7 ResponseInterface
表示。如果您的请求处理函数返回无效值或抛出未处理的Exception
或Throwable
,服务器将自动向客户端发送500
(内部服务器错误)HTTP错误响应。除此之外,它还将触发一个error
事件,可用于记录目的,如下所示
$http->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; if ($e->getPrevious() !== null) { echo 'Previous: ' . $e->getPrevious()->getMessage() . PHP_EOL; } });
请注意,如果客户端发送一个无效的HTTP请求且该请求从未到达您的请求处理函数,服务器也将触发一个error
事件。有关更多详细信息,请参阅无效请求。此外,流式输入请求体也可以在请求体上触发一个error
事件。
如果发生未处理的错误,服务器将仅向客户端发送一个非常通用的500
(间隔服务器错误)HTTP错误响应,不包含任何其他详细信息。虽然我们理解这可能会使得初始调试变得困难,但也意味着服务器默认不会向外部泄露任何应用程序详情或堆栈跟踪。通常建议在您的请求处理函数中捕获任何Exception
或Throwable
,或者使用一个middleware
来避免这种通用的错误处理,并创建您自己的HTTP响应消息。
默认响应头
当从请求处理函数返回响应时,它将被HttpServer
处理,然后发送回客户端。
将自动添加Server: ReactPHP/1
响应头。您可以像这样添加自定义的Server
响应头:
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Server' => 'PHP/3' ] ); });
如果您根本不希望发送这个Server
响应头(例如,当您不想暴露底层的服务器软件时),可以使用这样的空字符串值:
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Server' => '' ] ); });
如果没有提供,将自动添加包含当前系统日期和时间的Date
响应头。您可以像这样添加自定义的Date
响应头:
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Date' => gmdate('D, d M Y H:i:s \G\M\T') ] ); });
如果您根本不希望发送这个Date
响应头(例如,当您没有合适的时钟可依赖时),可以使用这样的空字符串值:
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Date' => '' ] ); });
HttpServer
类将自动添加请求的协议版本,因此您无需手动添加。例如,如果客户端使用HTTP/1.1协议版本发送请求,则响应消息也将使用相同的协议版本,无论请求处理函数返回什么版本。
服务器支持持久连接。将自动添加适当的Connection: keep-alive
或Connection: close
响应头,尊重匹配的请求头值和HTTP默认头值。服务器负责处理Connection
响应头,因此您不应该自己传递此响应头,除非您明确希望使用Connection: close
响应头覆盖用户的选择。
中间件
如上所述,HttpServer
接受一个请求处理函数参数,该参数负责处理传入的HTTP请求,然后创建并返回一个出去的HTTP响应。
许多常见的用例涉及在将请求传递给最终业务逻辑请求处理函数之前验证、处理和操作传入的HTTP请求。因此,该项目支持中间件请求处理函数的概念。
自定义中间件
中间件请求处理函数应遵守以下规则:
- 它是一个有效的
callable
。 - 它接受一个实现PSR-7
ServerRequestInterface
的实例作为第一个参数,以及一个可选的callable
作为第二个参数。 - 它返回以下之一:
- 一个实现PSR-7
ResponseInterface
的实例,以便直接消费。 - 任何可以被
Promise\resolve()
消费的承诺,解析为PSR-7ResponseInterface
的实例,以便延迟消费。 - 它可以抛出
Exception
(或返回一个拒绝的承诺)以表示错误条件并终止链。
- 一个实现PSR-7
- 它调用
$next($request)
以继续处理下一个中间件请求处理函数,或者明确返回而不调用$next
以终止链。- 下一个请求处理程序(递归地)使用与上述相同的逻辑从链中调用下一个请求处理程序,并返回(或抛出)与上述相同的结果。
- 在调用
$next($request)
之前,可以修改$request
,以便更改下一个中间件操作的要处理的外来请求。 - 可以消费
$next
的返回值来修改传出响应。 - 如果您想实现自定义的“重试”逻辑等,则可能需要多次调用
$next
请求处理程序。
请注意,这个非常简单的定义允许您使用匿名函数或任何使用魔法方法 __invoke()
的类。这使您能够轻松地即时创建自定义中间件请求处理程序,或使用基于类的做法来简化使用现有的中间件实现。
虽然该项目提供了使用中间件实现的方法,但它并不旨在定义中间件实现的外观。我们认识到,存在一个充满活力的中间件实现生态系统,以及通过 PSR-15(HTTP 服务器请求处理程序)标准化这些实现之间接口的持续努力,并支持这一目标。因此,该项目仅捆绑了少量必要的中间件实现,以匹配 PHP 的请求行为(见下文),并积极鼓励使用第三方中间件实现。
要使用中间件请求处理程序,只需将上面定义的所有可调用对象列表传递给 HttpServer
。以下示例添加了一个中间件请求处理程序,该处理程序将当前时间作为头部(Request-Time
)添加到请求中,并添加了一个始终返回不带主体的 200 OK
状态码的最终请求处理程序。
$http = new React\Http\HttpServer( function (Psr\Http\Message\ServerRequestInterface $request, callable $next) { $request = $request->withHeader('Request-Time', time()); return $next($request); }, function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } );
注意,中间件请求处理程序和最终请求处理程序具有非常简单(并且类似)的接口。唯一的区别是最终请求处理程序不接收
$next
处理程序。
同样,您可以使用 $next
中间件请求处理程序函数的结果来修改传出响应。请注意,根据上述文档,$next
中间件请求处理程序可以直接返回一个 PSR-7 ResponseInterface
或一个包装在延迟解析的承诺中的对象。为了简化处理这两条路径,您可以像这样将其包装在一个 Promise\resolve()
调用中
$http = new React\Http\HttpServer( function (Psr\Http\Message\ServerRequestInterface $request, callable $next) { $promise = React\Promise\resolve($next($request)); return $promise->then(function (ResponseInterface $response) { return $response->withHeader('Content-Type', 'text/html'); }); }, function (Psr\Http\Message\ServerRequestInterface $request) { return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } );
请注意,$next
中间件请求处理程序还可以抛出一个 Exception
(或返回一个被拒绝的承诺),如上所述。上一个示例没有捕获任何异常,因此会向 HttpServer
信号错误条件。或者,您也可以捕获任何 Exception
来实现自定义错误处理逻辑(或记录等),方法是将此包裹在一个 Promise
中,如下所示
$http = new React\Http\HttpServer( function (Psr\Http\Message\ServerRequestInterface $request, callable $next) { $promise = new React\Promise\Promise(function ($resolve) use ($next, $request) { $resolve($next($request)); }); return $promise->then(null, function (Exception $e) { return React\Http\Message\Response::plaintext( 'Internal error: ' . $e->getMessage() . "\n" )->withStatus(React\Http\Message\Response::STATUS_INTERNAL_SERVER_ERROR); }); }, function (Psr\Http\Message\ServerRequestInterface $request) { if (mt_rand(0, 1) === 1) { throw new RuntimeException('Database error'); } return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); } );
第三方中间件
虽然该项目提供了使用中间件实现的方法(见上文),但它并不旨在定义中间件实现的外观。我们认识到,存在一个充满活力的中间件实现生态系统,以及通过 PSR-15(HTTP 服务器请求处理程序)标准化这些实现之间接口的持续努力,并支持这一目标。因此,该项目仅捆绑了少量必要的中间件实现,以匹配 PHP 的请求行为(见 中间件实现),并积极鼓励第三方中间件实现。
虽然我们希望直接在 react/http
中支持 PSR-15,但我们理解这个接口并没有特别针对异步 API,因此没有充分利用承诺来处理延迟响应。简单来说,PSR-15 强制返回 PSR-7 ResponseInterface
,我们同时也接受 PromiseInterface
。因此,我们建议使用外部的 PSR-15 中间件适配器,它会对这些返回值进行即时猴子补丁,这样就可以在不做任何修改的情况下使用此包中的大多数 PSR-15 中间件。
除此之外,您还可以使用上面的中间件定义来创建自定义中间件。第三方中间件的非详尽列表可以在中间件维基中找到。如果您构建或知道自定义中间件,请确保让世界知道,并欢迎将其添加到此列表中。
API
浏览器
React\Http\Browser
负责向您的 HTTP 服务器发送 HTTP 请求,并跟踪挂起的传入 HTTP 响应。
$browser = new React\Http\Browser();
此类接受两个可选参数以实现更高级的使用。
$browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null);
如果您需要自定义连接器设置(DNS 解析、TLS 参数、超时、代理服务器等),您可以显式传递一个自定义的 ConnectorInterface
实例。
$connector = new React\Socket\Connector([ 'dns' => '127.0.0.1', 'tcp' => [ 'bindto' => '192.168.10.1:0' ], 'tls' => [ 'verify_peer' => false, 'verify_peer_name' => false ] ]); $browser = new React\Http\Browser($connector);
此类接受一个可选的LoopInterface|null $loop
参数,可以用来传递用于此对象的事件循环实例。您可以使用null
值来使用默认循环。除非您确定要显式使用给定的事件循环实例,否则不应提供此值。
请注意,浏览器类是最终的,不应该被扩展,它可能在未来的版本中被标记为最终。
get()
可以使用 get(string $url, array $headers = []): PromiseInterface
方法发送 HTTP GET 请求。
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump((string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关 GET 请求客户端示例,请参阅GET 请求客户端示例。
post()
可以使用 post(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface
方法发送 HTTP POST 请求。
$browser->post( $url, [ 'Content-Type' => 'application/json' ], json_encode($data) )->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump(json_decode((string)$response->getBody())); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关 POST JSON 客户端示例,请参阅POST JSON 客户端示例。
此方法也常用于提交 HTML 表单数据。
$data = [ 'user' => 'Alice', 'password' => 'secret' ]; $browser->post( $url, [ 'Content-Type' => 'application/x-www-form-urlencoded' ], http_build_query($data) );
如果出站请求体是一个 string
,此方法将自动添加匹配的 Content-Length
请求头。如果您正在使用流式请求体(ReadableStreamInterface
),它将默认使用 Transfer-Encoding: chunked
,或者您必须显式传递一个匹配的 Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->post($url, ['Content-Length' => '11'), $body);
head()
可以使用 head(string $url, array $headers = []): PromiseInterface
方法发送 HTTP HEAD 请求。
$browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
patch()
可以使用 patch(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface
方法发送 HTTP PATCH 请求。
$browser->patch( $url, [ 'Content-Type' => 'application/json' ], json_encode($data) )->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump(json_decode((string)$response->getBody())); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
如果出站请求体是一个 string
,此方法将自动添加匹配的 Content-Length
请求头。如果您正在使用流式请求体(ReadableStreamInterface
),它将默认使用 Transfer-Encoding: chunked
,或者您必须显式传递一个匹配的 Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->patch($url, ['Content-Length' => '11'], $body);
put()
可以使用 put(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface
方法发送 HTTP PUT 请求。
$browser->put( $url, [ 'Content-Type' => 'text/xml' ], $xml->asXML() )->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump((string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关 PUT XML 客户端示例,请参阅PUT XML 客户端示例。
如果出站请求体是一个 string
,此方法将自动添加匹配的 Content-Length
请求头。如果您正在使用流式请求体(ReadableStreamInterface
),它将默认使用 Transfer-Encoding: chunked
,或者您必须显式传递一个匹配的 Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->put($url, ['Content-Length' => '11'], $body);
delete()
可以使用 delete(string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface
方法发送 HTTP DELETE 请求。
$browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump((string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
request()
可以使用 request(string $method, string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface
方法发送任意 HTTP 请求。
发送 HTTP 请求的首选方式是使用上面的请求方法,例如使用get()
方法发送 HTTP GET
请求。
作为替代方案,如果您想使用自定义的HTTP请求方法,可以使用此方法
$browser->request('OPTIONS', $url)->then(function (Psr\Http\Message\ResponseInterface $response) { var_dump((string)$response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
此方法会自动添加匹配的 Content-Length
请求头,如果已知的 outgoing 请求体大小非空。对于空的请求体,如果请求方法通常期望请求体(仅适用于 POST
、PUT
和 PATCH
),则只会包含 Content-Length: 0
请求头。
如果您使用的是流式请求体(《ReadableStreamInterface》),它将默认使用 Transfer-Encoding: chunked
,或者您必须显式传入一个匹配的 Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->request('POST', $url, ['Content-Length' => '11'], $body);
requestStreaming()
requestStreaming(string $method, string $url, array $headers = [], string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法可用于发送任意HTTP请求并接收流式响应,而无需缓冲响应体。
发送HTTP请求的首选方式是使用上述 请求方法,例如使用 get()
方法发送HTTP GET
请求。默认情况下,这些方法会将整个响应体缓冲到内存中。这很容易上手,并且对于较小的响应来说效果还不错。
在某些情况下,使用流式方法可能更好,其中只需保留一小部分内存。您可以使用此方法发送任意HTTP请求并接收流式响应。它使用相同的HTTP消息API,但不缓冲内存中的响应体。它仅以接收到的数据小块的方式处理响应体,并通过 ReactPHP的Stream API 将这些数据转发。这适用于任意大小的(任意数量的)响应。
$browser->requestStreaming('GET', $url)->then(function (Psr\Http\Message\ResponseInterface $response) { $body = $response->getBody(); assert($body instanceof Psr\Http\Message\StreamInterface); assert($body instanceof React\Stream\ReadableStreamInterface); $body->on('data', function ($chunk) { echo $chunk; }); $body->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; }); $body->on('close', function () { echo '[DONE]' . PHP_EOL; }); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
有关更多详细信息、示例和可能的用例,请参阅 ReactPHP的 ReadableStreamInterface
和 流式响应。
此方法会自动添加匹配的 Content-Length
请求头,如果已知的 outgoing 请求体大小非空。对于空的请求体,如果请求方法通常期望请求体(仅适用于 POST
、PUT
和 PATCH
),则只会包含 Content-Length: 0
请求头。
如果您使用的是流式请求体(《ReadableStreamInterface》),它将默认使用 Transfer-Encoding: chunked
,或者您必须显式传入一个匹配的 Content-Length
请求头,如下所示
$body = new React\Stream\ThroughStream(); Loop::addTimer(1.0, function () use ($body) { $body->end("hello world"); }); $browser->requestStreaming('POST', $url, ['Content-Length' => '11'], $body);
withTimeout()
withTimeout(bool|number $timeout): Browser
方法可用于更改用于等待挂起请求的最大超时时间。
您可以传入要使用的秒数作为新的超时值
$browser = $browser->withTimeout(10.0);
您可以传入一个布尔值 false
来禁用任何超时。在这种情况下,请求可以永远挂起
$browser = $browser->withTimeout(false);
您可以传入一个布尔值 true
来重新启用默认超时处理。这将遵守PHP的 default_socket_timeout
设置(默认60秒)
$browser = $browser->withTimeout(true);
有关超时处理的更多详细信息,请参阅 超时。
请注意,《Browser》 是一个不可变对象,即此方法实际上返回一个 新 的 Browser
实例,并应用了给定的超时值。
withFollowRedirects()
withFollowRedirects(bool|int $followRedirects): Browser
方法可用于更改如何处理HTTP重定向。
您可以传入要跟随的最大重定向数
$browser = $browser->withFollowRedirects(5);
当超过重定向数时,请求将自动被拒绝。您可以传入一个 0
来拒绝遇到任何重定向
$browser = $browser->withFollowRedirects(0); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // only non-redirected responses will now end up here var_dump($response->getHeaders()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
您可以传入一个布尔值 false
来禁用跟随任何重定向。在这种情况下,请求将以重定向响应解决,而不是跟随 Location
响应头
$browser = $browser->withFollowRedirects(false); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // any redirects will now end up here var_dump($response->getHeaderLine('Location')); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
您可以传入一个布尔值 true
来重新启用默认重定向处理。默认情况下,最多跟随10个重定向
$browser = $browser->withFollowRedirects(true);
有关重定向处理的更多详细信息,请参阅 重定向。
请注意,Browser
是一个不可变对象,即此方法实际上返回一个新的 带有给定重定向设置的 Browser
实例。
withRejectErrorResponse()
可以使用 withRejectErrorResponse(bool $obeySuccessCode): Browser
方法来更改是否拒绝非成功 HTTP 响应状态码(4xx 和 5xx)。
您可以将 bool false
传入以禁用拒绝使用 4xx 或 5xx 响应状态码的传入响应。在这种情况下,请求将以表示错误条件的响应消息解决。
$browser = $browser->withRejectErrorResponse(false); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // any HTTP response will now end up here var_dump($response->getStatusCode(), $response->getReasonPhrase()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
您可以将 bool true
传入以重新启用默认状态码处理。默认情况下,会拒绝任何 4xx 或 5xx 范围内的响应状态码,并抛出 ResponseException
。
$browser = $browser->withRejectErrorResponse(true); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // any successful HTTP response will now end up here var_dump($response->getStatusCode(), $response->getReasonPhrase()); }, function (Exception $e) { if ($e instanceof React\Http\Message\ResponseException) { // any HTTP response error message will now end up here $response = $e->getResponse(); var_dump($response->getStatusCode(), $response->getReasonPhrase()); } else { echo 'Error: ' . $e->getMessage() . PHP_EOL; } });
请注意,Browser
是一个不可变对象,即此方法实际上返回一个 带有给定设置的 新的 Browser
实例。
withBase()
可以使用 withBase(string|null $baseUrl): Browser
方法来更改用于解析相对 URL 的基础 URL。
如果您配置了基础 URL,则对相对 URL 的任何请求都将首先相对于给定的绝对基础 URL 进行解析。这支持解析相对路径引用(如 ../
等)。这对于(RESTful)API 调用尤其有用,其中所有端点(URL)都位于一个公共基础 URL 下。
$browser = $browser->withBase('http://api.example.com/v3/'); // will request http://api.example.com/v3/users $browser->get('users')->then(…);
您可以将 null
作为基础 URL 传入以返回一个不使用基础 URL 的新实例。
$browser = $browser->withBase(null);
因此,使用相对 URL 请求不使用基础 URL 的浏览器将无法完成,并且会在不发送请求的情况下被拒绝。
如果给定的 $baseUrl
参数不是一个有效的 URL,则此方法将抛出 InvalidArgumentException
。
请注意,Browser
是一个不可变对象,即 withBase()
方法实际上返回一个 带有给定基础 URL 的 新的 Browser
实例。
withProtocolVersion()
可以使用 withProtocolVersion(string $protocolVersion): Browser
方法来更改所有后续请求将使用的 HTTP 协议版本。
所有上述 请求方法 默认为以 HTTP/1.1 发送请求。这是首选的 HTTP 协议版本,它还提供了与旧式 HTTP/1.0 服务器的良好向后兼容性。因此,很少需要显式更改此协议版本。
如果您想显式使用旧式 HTTP/1.0 协议版本,可以使用此方法。
$browser = $browser->withProtocolVersion('1.0'); $browser->get($url)->then(…);
请注意,Browser
是一个不可变对象,即此方法实际上返回一个 带有新协议版本 的新的 Browser
实例。
withResponseBuffer()
可以使用 withResponseBuffer(int $maximumSize): Browser
方法来更改响应体的最大缓冲区大小。
发送HTTP请求的首选方式是使用上述 请求方法,例如使用 get()
方法发送HTTP GET
请求。默认情况下,这些方法会将整个响应体缓冲到内存中。这很容易上手,并且对于较小的响应来说效果还不错。
默认情况下,响应体缓冲区将被限制在 16 MiB。如果响应体超过此最大大小,则请求将被拒绝。
您可以传入最大字节数以进行缓冲。
$browser = $browser->withResponseBuffer(1024 * 1024); $browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) { // response body will not exceed 1 MiB var_dump($response->getHeaders(), (string) $response->getBody()); }, function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
请注意,对于每个待处理的请求,响应体缓冲区必须保持在内存中,直到其传输完成,并且只有在待处理请求得到满足后才会被释放。因此,通常不建议增加此最大缓冲区大小以允许更大的响应体。相反,您可以使用 requestStreaming()
方法 来接收任意大小的响应,而无需缓冲。相应地,此最大缓冲区设置对流式响应没有影响。
请注意,Browser
是一个不可变对象,即此方法实际上返回一个 带有给定设置的 新的 Browser
实例。
withHeader()
可以使用 withHeader(string $header, string $value): Browser
方法为所有后续请求添加请求头。
$browser = $browser->withHeader('User-Agent', 'ACME'); $browser->get($url)->then(…);
请注意,新头将覆盖先前使用相同名称(不区分大小写)设置的所有头。除非为任何请求显式设置,否则后续请求将默认使用这些头。
withoutHeader()
可以使用 withoutHeader(string $header): Browser
方法删除通过 withHeader()
方法 设置的任何默认请求头。
$browser = $browser->withoutHeader('User-Agent'); $browser->get($url)->then(…);
请注意,此方法仅影响使用 withHeader(string $header, string $value): Browser
方法设置的头。
React\Http\Message
响应
可以使用 React\Http\Message\Response
类来表示出站服务器响应消息。
$response = new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/html' ], "<html>Hello world!</html>\n" );
此类实现了 PSR-7 ResponseInterface
,它又扩展了 PSR-7 MessageInterface
。
此外,此类还实现了 PSR-7 Message Util StatusCodeInterface
,这意味着大多数常见的HTTP状态码都可用作具有 STATUS_*
前缀的类常量。例如,200 OK
和 404 Not Found
状态码可以分别用作 Response::STATUS_OK
和 Response::STATUS_NOT_FOUND
。
内部实现基于一个基类,这被视为可能在未来发生变化的技术细节。
html()
可以使用静态的 html(string $html): Response
方法来创建HTML响应。
$html = <<<HTML <!doctype html> <html> <body>Hello wörld!</body> </html> HTML; $response = React\Http\Message\Response::html($html);
这是一个方便的快捷方法,返回与以下等效的值
$response = new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'text/html; charset=utf-8'
],
$html
);
此方法始终返回具有 200 OK
状态码以及为给定HTTP源字符串(以UTF-8(Unicode)编码)提供的适当 Content-Type
响应头部的响应。通常建议在给定的纯文本字符串末尾添加换行符。
如果您想使用不同的状态码或自定义HTTP响应头,您可以使用提供的PSR-7方法操作返回的响应对象,或直接使用 Response
构造函数实例化自定义HTTP响应对象。
$response = React\Http\Message\Response::html( "<h1>Error</h1>\n<p>Invalid user name given.</p>\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
json()
可以使用静态的 json(mixed $data): Response
方法来创建JSON响应。
$response = React\Http\Message\Response::json(['name' => 'Alice']);
这是一个方便的快捷方法,返回与以下等效的值
$response = new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'application/json'
],
json_encode(
['name' => 'Alice'],
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION
) . "\n"
);
此方法始终返回具有 200 OK
状态码和为给定结构化数据(作为JSON文本编码)提供的适当 Content-Type
响应头部的响应。
给定的结构化数据将被编码为JSON文本。数据中的任何 string
值都必须以UTF-8(Unicode)编码。如果编码失败,此方法将抛出 InvalidArgumentException
。
默认情况下,给定的结构化数据将使用上述标志进行编码。这包括美化打印和保留 float
值的零小数部分以简化调试。假设任何额外的数据开销通常由使用HTTP响应压缩来补偿。
如果您想使用不同的状态码或自定义HTTP响应头,您可以使用提供的PSR-7方法操作返回的响应对象,或直接使用 Response
构造函数实例化自定义HTTP响应对象。
$response = React\Http\Message\Response::json( ['error' => 'Invalid user name given'] )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
plaintext()
可以使用静态的 plaintext(string $text): Response
方法来创建纯文本响应。
$response = React\Http\Message\Response::plaintext("Hello wörld!\n");
这是一个方便的快捷方法,返回与以下等效的值
$response = new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'text/plain; charset=utf-8'
],
"Hello wörld!\n"
);
此方法始终返回具有 200 OK
状态码和为给定纯文本字符串(以UTF-8(Unicode)编码)提供的适当 Content-Type
响应头部的响应。通常建议在给定的纯文本字符串末尾添加换行符。
如果您想使用不同的状态码或自定义HTTP响应头,您可以使用提供的PSR-7方法操作返回的响应对象,或直接使用 Response
构造函数实例化自定义HTTP响应对象。
$response = React\Http\Message\Response::plaintext( "Error: Invalid user name given.\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
xml()
静态的 xml(string $xml): Response
方法可以用来创建一个 XML 响应。
$xml = <<<XML <?xml version="1.0" encoding="utf-8"?> <body> <greeting>Hello wörld!</greeting> </body> XML; $response = React\Http\Message\Response::xml($xml);
这是一个方便的快捷方法,返回与以下等效的值
$response = new React\Http\Message\Response(
React\Http\Message\Response::STATUS_OK,
[
'Content-Type' => 'application/xml'
],
$xml
);
此方法始终返回一个带有 200 OK
状态码和适当 Content-Type
响应头部的响应,对应于给定的 XML 源字符串。通常建议使用 UTF-8(Unicode)编码,并在 XML 声明中指定这一点,并在给定的 XML 源字符串末尾添加一个换行符。
如果您想使用不同的状态码或自定义HTTP响应头,您可以使用提供的PSR-7方法操作返回的响应对象,或直接使用 Response
构造函数实例化自定义HTTP响应对象。
$response = React\Http\Message\Response::xml( "<error><message>Invalid user name given.</message></error>\n" )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST);
请求
可以使用 React\Http\Message\Request
类来表示一个外出的 HTTP 请求消息。
此类实现了 PSR-7 RequestInterface
,它扩展了 PSR-7 MessageInterface
。
这主要用于内部表示 HTTP 客户端实现中的每个外出的 HTTP 请求消息。同样,您也可以与其他 HTTP 客户端实现一起使用此类,以及用于测试。
内部实现基于一个基类,这被视为可能在未来发生变化的技术细节。
ServerRequest
可以使用 React\Http\Message\ServerRequest
类来表示一个进入的服务器请求消息。
此类实现了 PSR-7 ServerRequestInterface
,它扩展了 PSR-7 RequestInterface
,而后者又扩展了 PSR-7 MessageInterface
。
这主要用于内部表示每个进入的请求消息。同样,您也可以在测试案例中使用此类来测试您的 Web 应用程序如何响应特定的 HTTP 请求。
内部实现基于一个基类,这被视为可能在未来发生变化的技术细节。
Uri
可以使用 React\Http\Message\Uri
类来表示 URI(或 URL)。
此类实现了 PSR-7 UriInterface
。
这主要用于内部表示我们的 HTTP 客户端和服务器实现中每个 HTTP 请求消息的 URI。同样,您也可以与其他 HTTP 实现一起使用此类,以及用于测试。
ResponseException
React\Http\Message\ResponseException
是一个 Exception
子类,如果远程服务器返回非成功状态码(除 2xx 或 3xx 之外),将用于拒绝请求承诺。您可以通过 withRejectErrorResponse()
方法 来控制此行为。
可以使用 getCode(): int
方法来返回 HTTP 响应状态码。
可以使用 getResponse(): ResponseInterface
方法来访问其底层响应对象。
React\Http\Middleware
StreamingRequestMiddleware
可以使用 React\Http\Middleware\StreamingRequestMiddleware
来处理带有流式请求体的进入请求(不进行缓冲)。
这允许您处理任何大小的请求,而无需在内存中缓冲请求体。相反,它将请求体表示为一个 ReadableStreamInterface
,该接口在接收到数据时发出数据块。
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), function (Psr\Http\Message\ServerRequestInterface $request) { $body = $request->getBody(); assert($body instanceof Psr\Http\Message\StreamInterface); assert($body instanceof React\Stream\ReadableStreamInterface); return new React\Promise\Promise(function ($resolve) use ($body) { $bytes = 0; $body->on('data', function ($chunk) use (&$bytes) { $bytes += \count($chunk); }); $body->on('close', function () use (&$bytes, $resolve) { $resolve(new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [], "Received $bytes bytes\n" )); }); }); } );
有关更多详细信息,请参阅 流式进入请求。
此外,此中间件可以与 LimitConcurrentRequestsMiddleware
和 RequestBodyBufferMiddleware
(见下文)一起使用,以显式配置一次可以处理的请求数量总和。
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request new React\Http\Middleware\RequestBodyParserMiddleware(), $handler );
内部,此类用作“标记”,以不在
HttpServer
中触发默认请求缓冲行为。它不实现任何自己的逻辑。
LimitConcurrentRequestsMiddleware
可以使用 React\Http\Middleware\LimitConcurrentRequestsMiddleware
来限制可以并发执行的后续处理器的数量。
如果调用此中间件,它将检查待处理处理程序的数量是否低于允许的限制,然后简单地调用下一个处理程序,并返回下一个处理程序返回的内容(或抛出异常)。
如果待处理处理程序的数量超过允许的限制,请求将被排队(并且其流式化请求体将被暂停),并将返回一个待定承诺。一旦待处理处理程序返回(或抛出异常),它将从队列中选取最老的请求并调用下一个处理程序(并且其流式化请求体将被恢复)。
以下示例展示了如何使用此中间件确保一次最多只能调用10个处理程序
$http = new React\Http\HttpServer( new React\Http\Middleware\LimitConcurrentRequestsMiddleware(10), $handler );
类似地,此中间件通常与下面的RequestBodyBufferMiddleware
(请见下文)结合使用,以限制一次可以缓存的请求数量
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request new React\Http\Middleware\RequestBodyParserMiddleware(), $handler );
更复杂的示例包括限制一次可以缓存的请求数量,然后确保实际请求处理程序依次处理一个请求,而不存在并发
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request new React\Http\Middleware\RequestBodyParserMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(1), // only execute 1 handler (no concurrency) $handler );
RequestBodyBufferMiddleware
内置的中间件之一是React\Http\Middleware\RequestBodyBufferMiddleware
,它可以用来在内存中缓冲整个请求体。如果请求处理程序需要完整的PSR-7兼容性,且不需要默认的流式化请求体处理,这非常有用。构造函数接受一个可选参数,即最大请求体大小。如果没有提供,它将使用PHP配置中的post_max_size
(默认8 MiB)。(注意,将使用匹配的SAPI的值,在大多数情况下是CLI配置。)
任何请求体超过此限制的入站请求都将被接受,但其请求体将被丢弃(空请求体)。这样做是为了避免在内存中保留一个过大(例如,考虑2 GB的文件上传)的入站请求。这允许下一个中间件处理程序仍然处理此请求,但它将看到一个空的请求体。这与PHP的默认行为类似,如果超过此限制,则不会解析请求体。然而,与PHP的默认行为不同的是,无法通过php://input
访问原始请求体。
RequestBodyBufferMiddleware
将缓冲已知大小(即带有Content-Length
头)的请求体以及未知大小(即带有Transfer-Encoding: chunked
头)的请求体。
所有请求都将在内存中缓冲,直到请求体结束,然后调用下一个中间件处理程序,传递完整的缓冲请求。同样,对于请求体为空(例如简单的GET
请求)以及已缓存的请求(例如由于另一个中间件),这会立即调用下一个中间件处理程序。
请注意,给定的缓冲区大小限制适用于每个请求单独。这意味着如果您允许2 MiB的限制,并且收到1000个并发请求,这些缓冲区总共可能分配高达2000 MiB。因此,强烈建议您与上述LimitConcurrentRequestsMiddleware
(请见上文)一起使用,以限制总并发请求数量。
用法
$http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB function (Psr\Http\Message\ServerRequestInterface $request) { // The body from $request->getBody() is now fully available without the need to stream it return new React\Http\Message\Response(React\Http\Message\Response::STATUS_OK); }, );
RequestBodyParserMiddleware
React\Http\Middleware\RequestBodyParserMiddleware
接收一个完全缓冲的请求体(通常来自RequestBodyBufferMiddleware
),并从传入的HTTP请求体中解析表单值和文件上传。
此中间件处理程序负责将HTTP请求中的值(使用Content-Type: application/x-www-form-urlencoded
或Content-Type: multipart/form-data
)应用于类似于PHP的默认超全局变量$_POST
和$_FILES
。您可以使用PSR-7定义的$request->getParsedBody()
和$request->getUploadedFiles()
方法,而不是依赖于这些超全局变量。
因此,每个文件上传都将表示为一个实现 PSR-7 UploadedFileInterface
的实例。由于其阻塞性质,moveTo()
方法不可用,并会抛出 RuntimeException
异常。您可以使用 $contents = (string)$file->getStream();
来访问文件内容并将其持久化到您喜欢的数据存储。
$handler = function (Psr\Http\Message\ServerRequestInterface $request) { // If any, parsed form fields are now available from $request->getParsedBody() $body = $request->getParsedBody(); $name = isset($body['name']) ? $body['name'] : 'unnamed'; $files = $request->getUploadedFiles(); $avatar = isset($files['avatar']) ? $files['avatar'] : null; if ($avatar instanceof Psr\Http\Message\UploadedFileInterface) { if ($avatar->getError() === UPLOAD_ERR_OK) { $uploaded = $avatar->getSize() . ' bytes'; } elseif ($avatar->getError() === UPLOAD_ERR_INI_SIZE) { $uploaded = 'file too large'; } else { $uploaded = 'with error'; } } else { $uploaded = 'nothing'; } return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, [ 'Content-Type' => 'text/plain' ], $name . ' uploaded ' . $uploaded ); }; $http = new React\Http\HttpServer( new React\Http\Middleware\StreamingRequestMiddleware(), new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers new React\Http\Middleware\RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB new React\Http\Middleware\RequestBodyParserMiddleware(), $handler );
有关更多详细信息,请参阅表单上传服务器示例。
默认情况下,此中间件遵循 upload_max_filesize
(默认 2M
) ini 设置。超过此限制的文件将使用 UPLOAD_ERR_INI_SIZE
错误被拒绝。您可以通过将最大文件大小(以字节为单位)作为构造函数的第一个参数显式传递来控制每个单独文件上传的最大文件大小,如下所示
new React\Http\Middleware\RequestBodyParserMiddleware(8 * 1024 * 1024); // 8 MiB limit per file
默认情况下,此中间件遵循 file_uploads
(默认 1
) 和 max_file_uploads
(默认 20
) ini 设置。这些设置控制单个请求中可以上传的文件数量。如果您在单个请求中上传更多文件,则额外的文件将被忽略,并且 getUploadedFiles()
方法返回一个截断的数组。请注意,提交时留空的上传字段不计入此限制。您可以通过将第二个参数显式传递给构造函数来控制每个请求的最大文件上传数,如下所示
new React\Http\Middleware\RequestBodyParserMiddleware(10 * 1024, 100); // 100 files with 10 KiB each
请注意,此中间件处理程序仅解析请求体中已缓冲的所有内容。请求体必须由上述示例中给出的先前中间件处理程序进行缓冲。此先前中间件处理程序还负责拒绝超出允许的消息大小(如大文件上传)的传入请求。上面使用的
RequestBodyBufferMiddleware
简单地丢弃过大的请求体,从而导致空的身体。如果您在没有缓冲的情况下使用此中间件,它将尝试解析一个空的(流式)身体,并可能因此假定一个空的数据结构。有关更多信息,请参阅RequestBodyBufferMiddleware
。
此中间件遵循 PHP 的
MAX_FILE_SIZE
隐藏字段。超过此限制的文件将使用UPLOAD_ERR_FORM_SIZE
错误被拒绝。
此中间件遵循
max_input_vars
(默认1000
) 和max_input_nesting_level
(默认64
) ini 设置。
请注意,此中间件忽略
enable_post_data_reading
(默认1
) ini 设置,因为它在这里没有意义,并且留给高级实现。如果您想遵循此设置,您必须检查其值并有效地避免完全使用此中间件。
安装
安装此库的推荐方法是 通过 Composer。 对 Composer 不熟悉?
一旦发布,此项目将遵循 SemVer。目前,这将安装最新的开发版本
composer require react/http:^3@dev
有关版本升级的详细信息,请参阅 CHANGELOG。
此项目旨在在任何平台上运行,因此不需要任何 PHP 扩展,并支持在 PHP 7.1 到当前 PHP 8+ 上运行。强烈建议使用此项目的最新支持的 PHP 版本。
测试
要运行测试套件,您首先需要克隆此仓库,然后通过Composer安装所有依赖项(请访问Composer网站)
composer install
要运行测试套件,请前往项目根目录并执行
vendor/bin/phpunit
测试套件还包含一些依赖稳定网络连接的功能集成测试。如果您不想运行这些测试,可以像这样简单地跳过
vendor/bin/phpunit --exclude-group internet
许可
MIT许可,请参阅许可文件。