clue/buzz-react

该包已被弃用,不再维护。作者建议使用 react/http 包。

简单的异步PSR-7 HTTP客户端,用于并发处理任意数量的HTTP请求,基于ReactPHP构建。

v2.9.0 2020-07-03 10:43 UTC

README

此包已迁移到 react/http,仅存在以支持向下兼容。

$ composer require react/http

如果您之前使用过此包,升级不应超过几分钟。所有类都已从最新的 v2.9.0 版本直接合并,没有其他重大更改,因此您可以简单地更新代码以使用更新的命名空间,如下所示

// old
$browser = new Clue\React\Buzz\Browser($loop);
$browser->get($url);

// new
$browser = new React\Http\Browser($loop);
$browser->get($url);

有关更多详细信息,请参阅 react/http

以下文档适用于此包的最后一个版本。进一步的开发将在更新的 react/http 中进行,因此强烈建议您尽快升级。

遗留的 clue/reactphp-buzz 构建状态

简单的异步PSR-7 HTTP客户端,用于并发处理任意数量的HTTP请求,基于 ReactPHP

此库深受伟大的 kriswallsmith/Buzz 项目的启发。然而,它不阻塞在每个请求上,而是依赖于 ReactPHP的EventLoop 来并行处理多个请求。这允许您同时与多个HTTP服务器交互(获取URL,与RESTful API通信,跟踪重定向等)。与底层的 react/http-client 不同,此项目旨在提供一个高级API,易于使用,以并发处理多个HTTP请求,而不必处理大多数底层细节。

  • 异步执行HTTP请求 - 并行向任意数量的HTTP服务器发送任意数量的HTTP请求,并在结果到来时立即处理它们的响应。基于Promise的设计提供了一种处理超出范围响应的合理的接口。
  • 标准接口 - 通过实现 PSR-7 (http-message) 接口,ReactPHP的标准 promises流接口,允许轻松集成现有的高级组件。
  • 轻量级,SOLID设计 - 提供了一个薄薄的抽象,它足够合适并且不会妨碍您。建立在经过良好测试的组件和已确立的概念之上,而不是重新发明轮子。
  • 良好的测试覆盖率 - 包含自动测试套件,并在现实世界中进行定期测试。

目录

支持我们

我们在开发、维护和更新我们的优秀开源项目上投入了大量的时间。您可以通过成为GitHub上的赞助商来帮助我们保持我们工作的这种高质量。赞助商将获得许多回报,有关详细信息,请参阅我们的赞助页面

让我们共同努力将这些项目提升到下一个水平!🚀

快速入门示例

安装后,您可以使用以下代码访问HTTP网络服务器并发送一些简单的HTTP GET请求

$loop = React\EventLoop\Factory::create();
$client = new Clue\React\Buzz\Browser($loop);

$client->get('http://www.google.com/')->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

$loop->run();

另请参阅示例

用法

请求方法

最重要的是,该项目提供了一个Browser对象,该对象提供了几个类似于HTTP协议方法的函数

$browser->get($url, array $headers = array());
$browser->head($url, array $headers = array());
$browser->post($url, array $headers = array(), string|ReadableStreamInterface $contents = '');
$browser->delete($url, array $headers = array(), string|ReadableStreamInterface $contents = '');
$browser->put($url, array $headers = array(), string|ReadableStreamInterface $contents = '');
$browser->patch($url, array $headers = array(), string|ReadableStreamInterface $contents = '');

这些函数中的每一个都需要一个$url和一些可选参数来发送HTTP请求。每个方法名都与相应的HTTP请求方法相匹配,例如get()方法发送HTTP GET请求。

您可以可选地传递一个包含附加$headers的关联数组,这些headers将随此HTTP请求一起发送。此外,每个方法都会自动添加一个匹配的Content-Length请求头,如果提供了出站请求体且其大小已知且非空。对于空请求体,如果请求方法通常期望请求体(仅适用于POSTPUTPATCH HTTP请求方法),则将仅包含Content-Length: 0请求头。

如果您正在使用流式请求体,它将默认使用Transfer-Encoding: chunked,除非您明确传递一个匹配的Content-Length请求头。有关更多详细信息,请参阅流式请求

默认情况下,上述所有方法都使用HTTP/1.1协议版本发送请求。如果您想显式使用旧的HTTP/1.0协议版本,可以使用withProtocolVersion()方法。如果您想使用任何其他或自定义的HTTP请求方法,可以使用request()方法。

上述每种方法都支持异步操作,并且要么以ResponseInterface方式fulfills,要么以rejects方式返回一个Exception。请参阅以下章节关于promises的更多细节。

promises

发送请求是异步的(非阻塞),因此您实际上可以并行发送多个请求。浏览器会对每个请求响应一个ResponseInterface消息,顺序没有保证。发送请求使用基于Promise的接口,这使得在HTTP请求完成时(即成功满足或因错误而被拒绝)很容易做出反应。

$browser->get($url)->then(
    function (Psr\Http\Message\ResponseInterface $response) {
        var_dump('Response received', $response);
    },
    function (Exception $error) {
        var_dump('There was an error', $error->getMessage());
    }
);

如果您觉得这很奇怪,也可以使用更传统的阻塞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请求和等待成功响应的超时,否则将取消挂起的请求,并使用Exception拒绝其值。

请注意,此超时值涵盖了创建底层传输连接、发送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());
});

类似地,您可以使用布尔值 false 来完全不应用超时,或者使用布尔值 true 来恢复默认处理。有关详细信息,请参阅 withTimeout()

如果您使用的是 流式响应体,则接收响应体流所需的时间将不计入超时。这允许您更长时间地保持此传入流打开,例如在下载非常大的流或通过长期连接传输数据时。

如果您使用的是 流式请求体,则发送请求体流所需的时间将不计入超时。这允许您更长时间地保持此传出流打开,例如在上传非常大的流时。

请注意,此超时处理适用于高级HTTP层。例如,socket和DNS等低层可能也会应用(不同的)超时值。特别是,底层socket连接使用相同的 default_socket_timeout 设置来建立底层传输连接。要控制此连接超时行为,您可以像这样 注入自定义的 Connector

$browser = new Clue\React\Buzz\Browser(
    $loop,
    new React\Socket\Connector(
        $loop,
        array(
            'timeout' => 5
        )
    )
);

身份验证

此库支持使用 Authorization: Basic … 请求头进行 HTTP基本身份验证,或者允许您显式设置 Authorization 请求头。

默认情况下,此库不包含出站的 Authorization 请求头。如果服务器需要身份验证,它可能会返回 401(未授权)状态码,默认情况下将拒绝请求(也请参阅下文的 withRejectErrorResponse() 方法)。

为了传递身份验证详情,您可以将用户名和密码作为请求URL的一部分传递,例如这样

$promise = $browser->get('https://user:pass@example.com/api');

请注意,身份验证详情中的特殊字符必须进行百分号编码,也请参阅 rawurlencode()。此示例将自动使用出站的 Authorization: Basic … 请求头传递base64编码的身份验证详情。如果您与之通信的HTTP端点需要任何其他身份验证方案,您也可以显式传递此头。这在使用(RESTful)HTTP API时很常见,这些API使用OAuth访问令牌或JSON Web Tokens (JWT)。

$token = 'abc123';

$promise = $browser->get(
    'https://example.com/api',
    array(
        'Authorization' => 'Bearer ' . $token
    )
);

在跟随重定向时,默认情况下不会将 Authorization 请求头发送到任何远程主机。当跟随包含身份验证详情的 Location 响应头时的重定向,这些详情将被发送到后续请求。有关详细信息,请参阅下文的 重定向

重定向

默认情况下,此库会跟随任何重定向并遵守远程服务器通过 Location 响应头返回的 3xx(重定向)状态码。Promise将使用重定向链中的最后一个响应来履行。

$browser->get($url, $headers)->then(function (Psr\Http\Message\ResponseInterface $response) {
    // the final response will end up here
    var_dump($response->getHeaders());
});

任何重定向请求都将遵循原始请求的语义,并将包括与原始请求相同的请求头,除了以下列出的那些。如果原始请求包含请求体,则此请求体将永远不会传递给重定向请求。因此,每个重定向请求都将删除任何 Content-LengthContent-Type 请求头。

如果原始请求使用了带有 Authorization 请求头的 HTTP 认证,那么只有当重定向的 URL 使用相同的域名时,这个请求头才会作为重定向请求的一部分传递。换句话说,由于可能存在的隐私/安全顾虑,Authorization 请求头不会被转发到其他外国主机。当跟随重定向时,如果 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());
});

有关更多详细信息,请参阅 withFollowRedirects()

阻止

如上所述,这个库默认提供了一个强大的、异步的 API。

然而,如果您想将其集成到传统的、阻塞的环境中,您应该考虑同时使用 clue/reactphp-block

生成的阻塞代码可能看起来像这样:

use Clue\React\Block;

$loop = React\EventLoop\Factory::create();
$browser = new Clue\React\Buzz\Browser($loop);

$promise = $browser->get('http://example.com/');

try {
    $response = Block\await($promise, $loop);
    // response successfully received
} catch (Exception $e) {
    // an error occured while performing the request
}

同样,您还可以并发处理多个请求,并等待一个 Response 对象数组。

$promises = array(
    $browser->get('http://example.com/'),
    $browser->get('http://www.example.org/'),
);

$responses = Block\awaitAll($promises, $loop);

有关更多详细信息,请参阅 clue/reactphp-block

请记住上述关于在内存中缓冲整个响应消息的说明。作为替代方案,您还可以查看以下章节中的 流式 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());
    });
}

因此,通常建议对发送端的并发性进行限制,以合理的值。通常使用一个相对较小的限制,因为同时做超过十几件事情可能会轻易压倒接收方。您可以使用 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());
    });
}

超过并发限制的附加请求将自动入队,直到其中一个挂起的请求完成。这很好地与现有的基于 Promise 的 API 集成。有关更多详细信息,请参阅 clue/reactphp-mq

这种方法对于处理数千个挂起的请求来说是合理的。如果您正在处理非常大的输入列表(例如 CSV 或 NDJSON 文件中的数百万行),您可能需要考虑使用流式方法。有关详细信息,请参阅 clue/reactphp-flux

流式响应

上述所有示例都假设您想要将整个响应体存储在内存中。这很容易入门,并且对于较小的响应来说表现良好。

然而,在以下几种情况下,通常使用流式处理方法会更好,这种方法只需要在内存中保留小块数据。

  • 如果您正在处理大量的并发请求(100+)或
  • 如果您想在数据块发生时处理它们(而不必等待完整的响应体)或
  • 如果您预计响应体很大(例如,1 MiB或更多,当下载二进制文件时)或
  • 如果您不确定响应体的尺寸(当访问任意远程HTTP端点且响应体尺寸事先未知时,最好是谨慎行事)。

您可以使用requestStreaming()方法发送任意HTTP请求并接收流式响应。它使用相同的HTTP消息API,但不会在内存中缓冲响应体。它只在小块数据接收时处理响应体,并通过ReactPHP的Stream API转发这些数据。这对于任意大小的响应都是有效的。

这意味着它使用正常的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 $error) {
        echo 'Error: ' . $error->getMessage() . PHP_EOL;
    });

    $body->on('close', function () {
        echo '[DONE]' . 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中,那么使用解析为流对象的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;
});

有关更多详细信息,请参阅requestStreaming()方法。

遗留信息:v2.9.0之前的遗留版本使用了遗留的streaming选项。此选项已弃用,但行为上仍然保持一致。

流式请求

除了流式传输响应体外,您还可以流式传输请求体。如果您想发送大的POST请求(上传文件等)或同时处理许多传出流,这可能很有用。您只需将实现ReactPHP的ReadableStreamInterface的实例传递给请求方法即可,如下所示

$browser->post($url, array(), $stream)->then(function (Psr\Http\Message\ResponseInterface $response) {
    echo 'Successfully sent.';
});

如果您使用流式请求体(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(
    'http://127.0.0.1:8080',
    new React\Socket\Connector($loop)
);

$connector = new React\Socket\Connector($loop, array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new Clue\React\Buzz\Browser($loop, $connector);

请参阅HTTP CONNECT代理示例

SOCKS代理

您还可以通过添加对clue/reactphp-socks的依赖来通过SOCKS代理服务器建立您的出站连接。

SOCKS代理协议族(SOCKS5、SOCKS4和SOCKS4a)通常用于通过中间代理(“代理”)隧道HTTP(S)流量,以隐藏原始地址(匿名性)或绕过地址封锁(地理封锁)。虽然许多(公共)SOCKS代理服务器通常仅限制为HTTP(S)端口80443,但技术上这可以用于隧道任何基于TCP/IP的协议。

$proxy = new Clue\React\Socks\Client(
    'socks://127.0.0.1:1080',
    new React\Socket\Connector($loop)
);

$connector = new React\Socket\Connector($loop, array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new Clue\React\Buzz\Browser($loop, $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('me@localhost:22', $loop);

$connector = new React\Socket\Connector($loop, array(
    'tcp' => $proxy,
    'dns' => false
));

$browser = new Clue\React\Buzz\Browser($loop, $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($loop)
);

$browser = new Browser($loop, $connector);

$client->get('http://localhost/info')->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders(), (string)$response->getBody());
});

请参阅Unix Domain Sockets (UDS) 示例

API

浏览器

Clue\React\Buzz\Browser负责向您的HTTP服务器发送HTTP请求,并跟踪待处理的传入HTTP响应。它还将所有内容注册到主EventLoop

$loop = React\EventLoop\Factory::create();

$browser = new Clue\React\Buzz\Browser($loop);

如果您需要自定义连接器设置(DNS解析、TLS参数、超时、代理服务器等),您可以显式传递一个自定义的 ConnectorInterface 实例。

$connector = new React\Socket\Connector($loop, 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 Clue\React\Buzz\Browser($loop, $connector);

get()

可以使用 get(string|UriInterface $url, array $headers = array()): PromiseInterface<ResponseInterface> 方法发送 HTTP GET 请求。

$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump((string)$response->getBody());
});

另请参阅 示例 01

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

post()

可以使用 post(string|UriInterface $url, array $headers = array(), string|ReadableStreamInterface $contents = ''): 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()));
});

另请参阅 示例 04

此方法也常用于提交 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);

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

head()

可以使用 head(string|UriInterface $url, array $headers = array()): PromiseInterface<ResponseInterface> 方法发送 HTTP HEAD 请求。

$browser->head($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump($response->getHeaders());
});

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

patch()

可以使用 patch(string|UriInterface $url, array $headers = array(), string|ReadableStreamInterface $contents = ''): 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()));
});

如果出站请求体是 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);

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

put()

可以使用 put(string|UriInterface $url, array $headers = array()): 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());
});

另请参阅 示例 05

如果出站请求体是 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);

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

delete()

可以使用 delete(string|UriInterface $url, array $headers = array()): PromiseInterface<ResponseInterface> 方法发送 HTTP DELETE 请求。

$browser->delete($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
    var_dump((string)$response->getBody());
});

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

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());
});

如果已知的出站请求体大小非空,则此方法将自动添加匹配的 Content-Length 请求头。对于空请求体,如果请求方法通常期望请求体(仅适用于 POSTPUTPATCH),则将仅包含 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);

请注意,此方法从v2.9.0版本开始可用,并在解析前始终缓冲响应体。它不尊重已弃用的 streaming 选项。如果您想流式传输响应体,请使用 requestStreaming() 方法。

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 $error) {
        echo 'Error: ' . $error->getMessage() . PHP_EOL;
    });

    $body->on('close', function () {
        echo '[DONE]' . PHP_EOL;
    });
});

有关更多详细信息、示例和可能的用例,请参阅 ReadableStreamInterface流式响应

如果已知的出站请求体大小非空,则此方法将自动添加匹配的 Content-Length 请求头。对于空请求体,如果请求方法通常期望请求体(仅适用于 POSTPUTPATCH),则将仅包含 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);

请注意,此方法从v2.9.0版本开始可用,并且始终在不缓冲响应体的情况下解析响应。它不尊重已弃用的 streaming 选项。如果您想缓冲响应体,请使用 request() 方法。

submit()

自v2.9.0版本以来已弃用,请改用 post()

可以使用已弃用的 submit(string|UriInterface $url, array $fields, array $headers = array(), string $method = 'POST'): PromiseInterface<ResponseInterface> 方法提交与表单提交类似的一组字段值(application/x-www-form-urlencoded)。

// deprecated: see post() instead
$browser->submit($url, array('user' => 'test', 'password' => 'secret'));

出于向后兼容性原因,此方法接受 $url 作为 string 值或作为 UriInterface。建议显式将实现 UriInterface 的任何对象转换为 string

send()

自v2.9.0版本以来已弃用,请改用 request()

可以使用已弃用的 send(RequestInterface $request): PromiseInterface<ResponseInterface> 方法发送实现 RequestInterface(PSR-7)的任意实例。

发送 HTTP 请求的首选方法是使用上述 请求方法,例如使用 get() 方法发送 HTTP GET 请求。

作为替代,如果您想使用自定义 HTTP 请求方法,可以使用此方法

$request = new Request('OPTIONS', $url);

// deprecated: see request() instead
$browser->send($request)->then(…);

如果已知的出站请求体大小非空,则此方法将自动添加匹配的 Content-Length 请求头。对于空请求体,如果请求方法通常期望请求体(仅适用于 POSTPUTPATCH),则将仅包含 Content-Length: 0 请求头。

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()

使用withTimeout(bool|int $$followRedirects): Browser方法可以更改HTTP重定向的跟随方式。

您可以传递要跟随的最大重定向数

$new = $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());
});

您可以传递一个布尔值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'));
});

您可以传递一个布尔值true来重新启用默认的重定向处理。默认情况下,最多跟随10个重定向

$browser = $browser->withFollowRedirects(true);

有关重定向处理的更多详细信息,请参阅重定向

请注意,Browser是一个不可变对象,即此方法实际上返回了一个应用了给定重定向设置的Browser实例。

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());
});

您可以传递一个布尔值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 Clue\React\Buzz\Message\ResponseException) {
        // any HTTP response error message will now end up here
        $response = $e->getResponse();
        var_dump($response->getStatusCode(), $response->getReasonPhrase());
    } else {
        var_dump($e->getMessage());
    }
});

请注意,Browser是一个不可变对象,即此方法实际上返回了一个应用了给定设置的Browser实例。

withBase()

使用withBase(string|null|UriInterface $baseUrl): Browser方法可以更改用于解析相对URL的基本URL。

如果您配置了基本URL,任何请求相对URL的请求都将首先使用此绝对基本URL进行处理。请注意,这仅预置基本URL,并不会解析任何相对路径引用(如../等)。这对于所有端点(URL)都位于公共基本URL下的(RESTful)API调用非常有用。

$browser = $browser->withBase('http://api.example.com/v3');

// will request http://api.example.com/v3/example
$browser->get('/example')->then(…);

您可以通过传递一个null基本URL来返回一个不使用基本URL的新实例

$browser = $browser->withBase(null);

因此,任何使用相对URL请求不使用基本URL的浏览器的请求都无法完成,并且将在不发送请求的情况下被拒绝。

如果给定的$baseUrl参数不是一个有效的URL,此方法将抛出一个InvalidArgumentException

请注意,Browser是一个不可变对象,即withBase()方法实际上返回了一个应用了给定基本URL的Browser实例。

出于BC的原因,此方法接受$baseUrl作为字符串值或UriInterface。建议显式地将实现UriInterface的对象转换为字符串。

更新日志:从v2.9.0版本开始,此方法接受一个null值以重置基本URL。早期版本必须使用已弃用的withoutBase()方法来重置基本URL。

withProtocolVersion()

withProtocolVersion(string $protocolVersion): Browser方法可用于更改所有后续请求将使用的HTTP协议版本。

上述所有请求方法默认以HTTP/1.1发送请求。这是首选的HTTP协议版本,它还提供了与旧版HTTP/1.0服务器的良好向后兼容性。因此,通常无需显式更改此协议版本。

如果您想显式使用旧版HTTP/1.0协议版本,可以使用此方法

$newBrowser = $browser->withProtocolVersion('1.0');

$newBrowser->get($url)->then(…);

请注意,Browser是一个不可变对象,即此方法实际上返回一个应用了新协议版本的Browser实例。

withResponseBuffer()

withRespomseBuffer(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());
});

请注意,响应体缓冲区必须为每个挂起的请求保留在内存中,直到其传输完成,它将在挂起的请求得到满足后才会被释放。因此,通常不推荐增加此最大缓冲区大小以允许更大的响应体。相反,您可以使用requestStreaming()方法接收任意大小的响应,而不进行缓冲。因此,此最大缓冲区设置对流式响应没有影响。

请注意,Browser是一个不可变对象,即此方法实际上返回了一个应用了给定设置的Browser实例。

withOptions()

自v2.9.0版本起已弃用,请参阅withTimeout()withFollowRedirects()withRejectErrorResponse()

已弃用的withOptions(array $options): Browser方法可用于更改要使用的选项

Browser类公开了用于处理HTTP事务的几个选项。这些选项类似于PHP的一些HTTP上下文选项,可以通过以下API(及其默认值)进行控制

// deprecated
$newBrowser = $browser->withOptions(array(
    'timeout' => null, // see withTimeout() instead
    'followRedirects' => true, // see withFollowRedirects() instead
    'maxRedirects' => 10, // see withFollowRedirects() instead
    'obeySuccessCode' => true, // see withRejectErrorResponse() instead
    'streaming' => false, // deprecated, see requestStreaming() instead
));

有关更多详细信息,请参阅超时重定向流式响应

请注意,Browser是一个不可变对象,即此方法实际上返回一个应用了选项的Browser实例。

withoutBase()

自v2.9.0版本起已弃用,请参阅withBase()

已弃用的 withoutBase(): Browser 方法可以用来移除基本 URL。

// deprecated: see withBase() instead
$newBrowser = $browser->withoutBase();

注意,Browser 是一个不可变对象,也就是说,withoutBase() 方法实际上返回了一个没有应用任何基本 URL 的新 Browser 实例。

另请参阅 withBase()

ResponseInterface

Psr\Http\Message\ResponseInterface 表示从 Browser 接收到的响应。

这是一个在 PSR-7: HTTP 消息接口 中定义的标准接口,请参阅其 ResponseInterface 定义,该定义又扩展了 MessageInterface 定义

RequestInterface

Psr\Http\Message\RequestInterface 表示将通过 Browser 发送的出站请求。

这是一个在 PSR-7: HTTP 消息接口 中定义的标准接口,请参阅其 RequestInterface 定义,该定义又扩展了 MessageInterface 定义

UriInterface

Psr\Http\Message\UriInterface 表示一个绝对或相对 URI(即 URL)。

这是一个在 PSR-7: HTTP 消息接口 中定义的标准接口,请参阅其 UriInterface 定义

出于向后兼容性原因,请求方法接受 URL 作为 string 值或作为 UriInterface。建议显式地将实现 UriInterface 的任何对象转换为 string

ResponseException

ResponseException 是一个 Exception 子类,将在远程服务器返回非成功状态码(不是 2xx 或 3xx)时拒绝请求承诺。您可以通过 withRejectErrorResponse() 方法 控制此行为。

可以使用 getCode(): int 方法来返回 HTTP 响应状态码。

可以使用 getResponse(): ResponseInterface 方法来访问其底层的 ResponseInterface 对象。

安装

推荐通过 Composer 安装此库。对 Composer 陌生?新用户?

该项目遵循 SemVer。这将安装最新支持的版本。

$ composer require clue/buzz-react:^2.9

有关版本升级的详细信息,请参阅 CHANGELOG

本项目的目标是能够在任何平台上运行,因此不需要任何PHP扩展,并且支持在旧版PHP 5.3到当前PHP 7+和HHVM上运行。强烈推荐使用PHP 7+。

测试

要运行测试套件,首先需要克隆此仓库,然后通过Composer安装所有依赖项(Composer)

$ composer install

要运行测试套件,请进入项目根目录并运行

$ php vendor/bin/phpunit

测试套件还包含一些功能集成测试,这些测试会向在线服务http://httpbin.org发送测试HTTP请求,因此依赖于稳定的网络连接。如果您不想运行这些测试,可以像这样简单地跳过

$ php vendor/bin/phpunit --exclude-group online

许可证

本项目采用宽松的MIT许可证发布。

你知道吗?我提供定制开发服务,并为发行版赞助和贡献发放发票。如需详细信息,请联系我(@clue)。