anonymhk / http
基于 ReactPHP 的事件驱动、流式 HTTP 客户端和服务器实现
Requires
- php: >=5.3.0
- evenement/evenement: ^3.0 || ^2.0 || ^1.0
- fig/http-message-util: ^1.1
- guzzlehttp/psr7: >=2.6
- psr/http-message: ^2.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.5 || ^5.7 || ^4.8.35
- react/async: ^4 || ^3 || ^2
- react/promise-stream: ^1.4
- react/promise-timer: ^1.9
This package is auto-updated.
Last update: 2024-09-27 17:00:39 UTC
README
基于 ReactPHP 的事件驱动、流式 HTTP 客户端和服务器实现。
这个 HTTP 库提供了基于 ReactPHP 的 Socket
和 EventLoop
组件的可重用实现。其客户端组件允许您并发发送任意数量的异步 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 = array()); $browser->head($url, array $headers = array()); $browser->post($url, array $headers = array(), string|ReadableStreamInterface $body = ''); $browser->delete($url, array $headers = array(), string|ReadableStreamInterface $body = ''); $browser->put($url, array $headers = array(), string|ReadableStreamInterface $body = ''); $browser->patch($url, array $headers = array(), 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 的内容。
承诺
发送请求是异步(非阻塞)的,因此您可以实际并行发送多个请求。浏览器将对每个请求响应一个 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 将使用异常拒绝其值并清理任何底层资源。
$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( array( 'timeout' => 5 ) ) );
身份验证
此库支持使用Authorization: Basic …
请求头或允许您设置显式的Authorization
请求头来实现HTTP基本认证。
默认情况下,此库不包括出站的Authorization
请求头。如果服务器需要认证,它可能返回401
(未授权)状态码,默认情况下将拒绝请求(也请参阅下面的withRejectErrorResponse()
方法)。
为了传递认证详情,您可以将用户名和密码作为请求URL的一部分传递,如下所示
$promise = $browser->get('https://user:pass@example.com/api');
请注意,在认证细节中的特殊字符需要进行百分号编码,请参阅rawurlencode()
。此示例将自动通过出站Authorization: Basic …
请求头传递base64编码的认证细节。如果您要交互的HTTP端点需要任何其他认证方案,也可以显式传递此头。这在使用(RESTful)HTTP API并使用OAuth访问令牌或JSON Web Tokens (JWT)时很常见。
$token = 'abc123'; $promise = $browser->get( 'https://example.com/api', array( '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 }
同样,您也可以并发处理多个请求并等待一个响应对象数组
use function React\Async\await; use function React\Promise\all; $promises = array( $browser->get('http://example.com/'), $browser->get('http://www.example.org/'), ); $responses = await(all($promises));
这是由于PHP 8.1+中可用的纤维以及我们的兼容性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 = array()); $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 中,那么使用解决为 Stream 对象的 Promise 对象通常不太方便。考虑使用 react/promise-stream。生成的流代码可能看起来像这样
use React\Promise\Stream; function download(Browser $browser, string $url): React\Stream\ReadableStreamInterface { return Stream\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, array(), $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, array('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(array( '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(array( 'tcp' => $proxy, 'dns' => false )); $browser = new React\Http\Browser($connector);
请参阅 SOCKS 代理示例。
SSH 代理
您还可以通过添加对 clue/reactphp-ssh-proxy 的依赖项,通过 SSH 服务器建立您的传出连接。
Secure Shell (SSH) 是一种安全的网络协议,最常用于访问远程服务器的登录 shell。其架构允许它通过单个连接使用多个安全通道。除此之外,还可以用来创建“SSH 隧道”,通常用于通过中间代理(“代理”)隧道 HTTP(S) 流量,以隐藏原始地址(匿名性)或绕过地址阻止(地理封锁)。这可以用于隧道任何基于 TCP/IP 的协议(HTTP、SMTP、IMAP 等),允许您访问外部无法访问的本地服务(防火墙后的数据库),因此也可以用于纯 HTTP 和 TLS 加密的 HTTPS。
$proxy = new Clue\React\SshProxy\SshSocksConnector('alice@example.com'); $connector = new React\Socket\Connector(array( 'tcp' => $proxy, 'dns' => false )); $browser = new React\Http\Browser($connector);
请参阅 SSH 代理示例。
Unix 域套接字
默认情况下,此库支持 http://
和 https://
URL 方案的纯文本 TCP/IP 传输和安全的 TLS 连接。此库还支持在显式配置时使用 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('https:///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
值以使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
为了开始监听任何传入的连接,需要通过listen()
方法将HttpServer
附加到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
。如果一个请求超过这个限制,它的请求体会被忽略,并且它将被像没有请求体一样的请求处理。下面是显式配置以覆盖此设置的示例。
默认情况下,此类将尝试避免消耗超过您 memory_limit
的一半来缓冲多个并发 HTTP 请求。因此,在上述默认设置 128M
的最大值下,它将尝试消耗不超过 64M
来缓冲多个并发 HTTP 请求。结果,它将限制并发性到 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 请求体和并发设置。有关详细信息,请参阅下文的 流式传入请求。
变更日志 v1.5.0:此类已被重命名为
HttpServer
,从之前的Server
类以避免任何歧义。之前的名称已被弃用,不应再使用。
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 端口 8080
的 HTTP 请求。作为替代,通常使用反向代理,并通过使用监听地址 127.0.0.1:8080
仅在 localhost(环回)接口上监听此 HTTP 服务器。这样,您可以在默认 HTTP 端口 80
上托管您的应用程序,并且仅将特定请求路由到该 HTTP 服务器。
同样,通常建议使用反向代理设置,在默认 HTTPS 端口 443
上接受安全的 HTTPS 请求(TLS 终止),并且仅将纯文本请求路由到该 HTTP 服务器。作为替代,您还可以通过将此附加到 React\Socket\SocketServer
并使用安全的 TLS 监听地址、证书文件和可选的 passphrase
来接受此 HTTP 服务器上的安全 HTTPS 请求,如下所示
$http = new React\Http\HttpServer($handler); $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array( 'tls' => array( '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']
变量。此方法返回按照消息边界定义的请求体的完整尺寸,以字节数表示。如果请求消息不包含请求体(例如简单的GET
请求),则此值可能为0
。此方法操作于缓冲的请求体,即请求体的大小始终已知,即使在请求未指定Content-Length
请求头或使用HTTP/1.1请求的Transfer-Encoding: chunked
时也是如此。
注意:
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
。ReactPHP的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, $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 提供您对单个 URI 组件的访问。请注意,根据给定的 request-target
,某些 URI 组件可能存在或不存,例如,对于 asterisk-form
或 authority-form
中的请求,getPath(): string
方法将返回空字符串。其 getHost(): string
方法将返回由有效请求 URI 确定的主机,如果 HTTP/1.0 客户端未指定(即没有 Host
头部),则默认为本地套接字地址。其 getScheme(): string
方法将返回 http
或 https
,具体取决于请求是否通过安全 TLS 连接到目标主机。
如果此 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》(Bad Request)HTTP错误响应并关闭连接。除此之外,它还会触发一个《error》事件,可用于记录类似这样的用途
$http->on('error', function (Exception $e) { echo 'Error: ' . $e->getMessage() . PHP_EOL; });
请注意,如果您的请求处理函数没有返回有效的响应对象,服务器也会触发一个《error》事件。有关更多详细信息,请参阅无效响应。
服务器响应
传递给《HttpServer》构造函数的回调函数负责处理请求并返回一个响应,该响应将发送到客户端。
此函数必须返回一个实现《PSR-7 ResponseInterface
》的对象或一个《ReactPHP Promise》,该Promise解析为《PSR-7 ResponseInterface
》对象。
该项目提供了一个实现《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秒后创建响应。这个示例表明,如果响应需要时间来创建,您需要Promise。当请求体结束时,《ReactPHP Promise》将在《Response》对象上解析。如果客户端在Promise挂起时关闭连接,Promise将被自动取消。在这种情况下,可以使用Promise取消处理程序来清理分配的任何待处理资源(如果适用)。如果Promise在客户端关闭后解析,它将被简单地忽略。
流式输出响应
本项目中的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, array( '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库范围的自定义协议逻辑。如果您想处理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, array( '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, array( 'Server' => 'PHP/3' ) ); });
如果您根本不希望发送此 Server
响应头(例如,当您不想暴露底层服务器软件时),您可以使用空字符串值像这样
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, array( 'Server' => '' ) ); });
如果未提供,将自动添加带有当前系统日期和时间的 Date
响应头。您可以像这样添加自定义的 Date
响应头
$http = new React\Http\HttpServer(function (ServerRequestInterface $request) { return new React\Http\Message\Response( React\Http\Message\Response::STATUS_OK, array( '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, array( '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
请求处理器(递归地)使用与上述相同的逻辑调用链中的下一个请求处理器,并按上述方式返回(或抛出)。- 在调用
$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,因此未能充分利用承诺(promises)来实现延迟响应。简而言之,PSR-15 强制要求返回PSR-7 ResponseInterface
,我们也接受PromiseInterface<ResponseInterface>
。因此,我们建议使用外部PSR-15 中间件适配器,它通过动态修改这些返回值,使得使用大多数 PSR-15 中间件无需任何修改即可与该包兼容。
除此之外,您还可以使用上述中间件定义来创建自定义中间件。第三方中间件的非详尽列表可以在中间件维基页面找到。如果您构建或知道自定义中间件,请确保让全世界都知道,并随时将其添加到此列表中。
API
浏览器
React\Http\Browser
负责向您的 HTTP 服务器发送 HTTP 请求,并跟踪待处理的传入 HTTP 响应。
$browser = new React\Http\Browser();
此类接受两个可选参数,用于更高级的使用。
// constructor signature as of v1.5.0 $browser = new React\Http\Browser(?ConnectorInterface $connector = null, ?LoopInterface $loop = null); // legacy constructor signature before v1.5.0 $browser = new React\Http\Browser(?LoopInterface $loop = null, ?ConnectorInterface $connector = null);
如果您需要自定义连接器设置(DNS 解析、TLS 参数、超时、代理服务器等),您可以显式传递一个自定义的 ConnectorInterface
实例。
$connector = new React\Socket\Connector(array( 'dns' => '127.0.0.1', 'tcp' => array( 'bindto' => '192.168.10.1:0' ), 'tls' => array( 'verify_peer' => false, 'verify_peer_name' => false ) )); $browser = new React\Http\Browser($connector);
此类接受一个可选的LoopInterface|null $loop
参数,可以用于将事件循环实例传递给此对象。您可以使用null
值以使用默认循环。除非您确信要显式使用给定的事件循环实例,否则不应提供此值。
请注意,浏览器类是最终的,不应扩展,它可能在未来的版本中标记为最终。
get()
可以使用 get(string $url, array $headers = array()): PromiseInterface<ResponseInterface>
方法发送 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 = array(), string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法发送 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, array('Content-Length' => '11'), $body);
head()
可以使用 head(string $url, array $headers = array()): PromiseInterface<ResponseInterface>
方法发送 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 = array(), string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法发送 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, array('Content-Length' => '11'), $body);
put()
可以使用 put(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法发送 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, array('Content-Length' => '11'), $body);
delete()
可以使用 delete(string $url, array $headers = array(), string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法发送 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 = array(), string|ReadableStreamInterface $body = ''): PromiseInterface<ResponseInterface>
方法发送任意 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; });
如果已知道 outgoing 请求体的尺寸且非空,此方法将自动添加匹配的Content-Length
请求头。对于空的请求体,如果请求方法通常期望请求体(仅适用于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, array('Content-Length' => '11'), $body);
requestStreaming()
可以使用requestStreaming(string $method, string $url, array $headers = array(), 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
和流式响应。
如果已知道 outgoing 请求体的尺寸且非空,此方法将自动添加匹配的Content-Length
请求头。对于空的请求体,如果请求方法通常期望请求体(仅适用于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, array('Content-Length' => '11'), $body);
withTimeout()
可以使用withTimeout(bool|number $timeout): Browser
方法更改等待挂起请求的最大超时时间。
您可以传递要使用的秒数作为新的超时值
$browser = $browser->withTimeout(10.0);
您可以传递一个bool false
以禁用任何超时。在这种情况下,请求可以无限期地挂起
$browser = $browser->withTimeout(false);
您可以传递一个bool true
以重新启用默认超时处理。这将尊重PHP的default_socket_timeout
设置(默认60s)
$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; });
您可以传递一个bool 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; });
您可以传递一个bool true
以重新启用默认重定向处理。默认情况下,最多遵循10个重定向
$browser = $browser->withFollowRedirects(true);
另请参阅重定向以获取关于重定向处理的更多详细信息。
请注意,浏览器
是一个不可变对象,即此方法实际上返回了一个具有给定重定向设置应用的新浏览器
实例。
withRejectErrorResponse()
可以使用withRejectErrorResponse(bool $obeySuccessCode): Browser
方法来更改是否拒绝非成功的HTTP响应状态代码(4xx和5xx)。
您可以通过传递布尔值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; });
您可以通过传递布尔值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; } });
请注意,浏览器
是一个不可变对象,即此方法实际上返回了一个具有给定设置应用的新浏览器
实例。
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
。
请注意,浏览器
是一个不可变对象,即withBase()
方法实际上返回了一个具有给定基础URL应用的新浏览器
实例。
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(…);
请注意,浏览器
是一个不可变对象,即此方法实际上返回了一个具有新协议版本应用的新浏览器
实例。
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()
方法接收任意大小的响应,而不进行缓冲。因此,此最大缓冲区大小设置对流式响应没有影响。
请注意,浏览器
是一个不可变对象,即此方法实际上返回了一个具有给定设置应用的新浏览器
实例。
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, array( '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
状态码的响应,以及为给定的UTF-8(Unicode)编码的HTTP源字符串设置适当的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
状态码的响应,以及为给定的结构化数据设置适当的Content-Type
响应头,该数据编码为JSON文本。
给定的结构化数据将被编码为JSON文本。数据中的任何string
值都必须以UTF-8(Unicode)编码。如果编码失败,此方法将抛出InvalidArgumentException
。
默认情况下,给定的结构化数据将以上面显示的标志进行编码。这包括格式化输出(PHP 5.4+)和保留浮点数的零分数(PHP 5.6.6+),以便于调试。假设任何额外的数据开销通常可以通过使用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
的响应,并带有适当的Content-Type
响应头,用于给定以UTF-8(Unicode)编码的纯文本字符串。通常建议在给定的纯文本字符串后添加一个尾随换行符。
如果您想使用不同的状态码或自定义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客户端实现一起使用,以及进行测试。
内部实现基于现有的出去请求消息,并仅添加对流的支持。此基类被视为实现细节,可能在将来发生变化。
ServerRequest
可以使用React\Http\Message\ServerRequest
类来表示一个进入的服务器请求消息。
此类实现了PSR-7 ServerRequestInterface
,该接口扩展了PSR-7 RequestInterface
,而后者又扩展了PSR-7 MessageInterface
。
这主要用于内部表示每个进入的请求消息。同样,您也可以在测试用例中使用此类来测试您的Web应用程序对某些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请求体中解析表单值和文件上传。
此中间件处理程序负责将使用Content-Type: application/x-www-form-urlencoded
或Content-Type: multipart/form-data
的HTTP请求中的值应用于类似于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, array( '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新手吗?了解Composer?
本项目遵循SemVer。这将安装最新支持的版本。
composer require react/http:^1.9
有关版本升级的详细信息,请参阅变更日志。
本项目旨在在任何平台上运行,因此不需要任何PHP扩展,并支持在旧版PHP 5.3至当前PHP 8+和HHVM上运行。强烈建议使用此项目支持的最新PHP版本。
测试
要运行测试套件,首先需要克隆此仓库,然后通过Composer安装所有依赖项。
composer install
要运行测试套件,请转到项目根目录并运行
vendor/bin/phpunit
测试套件还包含一些依赖稳定网络连接的功能集成测试。如果您不想运行这些测试,可以像这样简单地跳过
vendor/bin/phpunit --exclude-group internet
许可
MIT许可,请参阅许可文件。