mpyw / co
基于资源和生成器的异步cURL执行器
Requires
- php: >=5.5.0
- ext-reflection: *
- lib-curl: >=7.20.0
- react/promise: ^2.4
Requires (Dev)
- php: >=7.0.0
- codeception/aspect-mock: ^2.0
- codeception/codeception: ^2.2
- codeception/specify: *
- mpyw/privator: ^2.0
- satooshi/php-coveralls: ^1.0
This package is auto-updated.
Last update: 2023-12-11 13:14:38 UTC
README
基于资源和生成器的异步cURL执行器
PHP | ❓ | 功能限制 |
---|---|---|
7.0~ | 😄 | 完全支持 |
5.5~5.6 | 😧 | 生成器并不那么酷 |
~5.4 | 💥 | 不兼容 |
function curl_init_with(string $url, array $options = []) { $ch = curl_init(); $options = array_replace([ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, ], $options); curl_setopt_array($ch, $options); return $ch; } function get_xpath_async(string $url) : \Generator { $dom = new \DOMDocument; @$dom->loadHTML(yield curl_init_with($url)); return new \DOMXPath($dom); } var_dump(Co::wait([ 'Delay 5 secs' => function () { echo "[Delay] I start to have a pseudo-sleep in this coroutine for about 5 secs\n"; for ($i = 0; $i < 5; ++$i) { yield Co::DELAY => 1; if ($i < 4) { printf("[Delay] %s\n", str_repeat('.', $i + 1)); } } echo "[Delay] Done!\n"; }, "google.com HTML" => curl_init_with("https://google.com"), "Content-Length of github.com" => function () { echo "[GitHub] I start to request for github.com to calculate Content-Length\n"; $content = yield curl_init_with("https://github.com"); echo "[GitHub] Done! Now I calculate length of contents\n"; return strlen($content); }, "Save mpyw's Gravatar Image URL to local" => function () { echo "[Gravatar] I start to request for github.com to get Gravatar URL\n"; $src = (yield get_xpath_async('https://github.com/mpyw')) ->evaluate('string(//img[contains(@class,"avatar")]/@src)'); echo "[Gravatar] Done! Now I download its data\n"; yield curl_init_with($src, [CURLOPT_FILE => fopen('/tmp/mpyw.png', 'wb')]); echo "[Gravatar] Done! Saved as /tmp/mpyw.png\n"; } ]));
请求尽可能并行执行 😄
请注意,只有 1 个进程 和 1 个线程。
[Delay] I start to have a pseudo-sleep in this coroutine for about 5 secs
[GitHub] I start to request for github.com to calculate Content-Length
[Gravatar] I start to request for github.com to get Gravatar URL
[Delay] .
[Delay] ..
[GitHub] Done! Now I calculate length of contents
[Gravatar] Done! Now I download its data
[Delay] ...
[Gravatar] Done! Saved as /tmp/mpyw.png
[Delay] ....
[Delay] Done!
array(4) {
["Delay 5 secs"]=>
NULL
["google.com HTML"]=>
string(262) "<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="https://www.google.co.jp/?gfe_rd=cr&ei=XXXXXX">here</A>.
</BODY></HTML>
"
["Content-Length of github.com"]=>
int(25534)
["Save mpyw's Gravatar Image URL to local"]=>
NULL
}
目录
安装
通过Composer安装。
composer require mpyw/co:^1.5
并在您的脚本中要求Composer自动加载器。
require __DIR__ . '/vendor/autoload.php'; use mpyw\Co\Co; use mpyw\Co\CURLException;
API
Co::wait()
等待所有cURL请求完成。
这些选项将覆盖静态默认值。
static Co::wait(mixed $value, array $options = []) : mixed
参数
(mixed)
$value
任何需要并行解决的值。(array<string, mixed>)
$options
选项的关联数组。
键 | 默认值 | 描述 |
---|---|---|
throw |
true |
是否在顶层抛出或捕获 CURLException 或 RuntimeException 。 |
pipeline |
false |
是否使用HTTP/1.1管道。 最多 5 次请求相同的目的地捆绑到单个TCP连接。 |
multiplex |
true |
是否使用HTTP/2多路复用。 所有 目的地的请求捆绑到单个TCP连接。 |
autoschedule |
false |
是否使用由 CURLMOPT_MAX_TOTAL_CONNECTIONS 定义的自动调度。 |
interval |
0.002 |
curl_multi_select() 超时秒数。 0 表示实时观察。 |
concurrency |
6 |
并发TCP连接的限制。 0 表示无限。此值最多为 10 。 |
Throwable
不扩展自RuntimeException
,例如Error
Exception
LogicException
都不会被捕获。如果您需要捕获它们,您必须在函数中编写自己的 try-catch 块。- 只有当TCP连接已建立并验证使用长连接会话时,才能使用HTTP/1.1管道。这意味着 第一个HTTP/1.1请求包不能进行管道传输。您可以从第二个
yield
在Co::wait()
调用中使用它。 - 要使用HTTP/2多路复用,您必须使用libcurl 7.43.0+ 和
--with-nghttp2
编译PHP。 - 要使用
autoschedule
,需要PHP 7.0.7或更高版本。
当 autoschedule
禁用时
curl_multi_add_handle()
调用可以延迟。- 使用
pipeline
/multiplex
进行concurrency
控制无法正确驱动。在这些情况下,您应该设置更高的concurrency
。
当 autoschedule
启用时
curl_multi_add_handle()
总是立即调用。CURLINFO_TOTAL_TIME
无法正确计算。 "Total Time" 包含等待其他请求完成的时间。
CURLIFNO_*_TIME
时序图的详细信息请参阅本页底部。
返回值
(混合类型)
已解析的值;在异常安全上下文中,它可能包含...
CURLException
,该异常已内部抛出。RuntimeException
,该异常由用户抛出。
异常
- 在异常不安全上下文中抛出
CURLException
或RuntimeException
。
Co::async()
在调用 Co::wait()
的同时执行 cURL 请求,不等待 解析后的值。
选项继承自 Co::wait()
。
此方法主要预期用于 ...
- 当您对响应不感兴趣时。
- 在
CURLOPT_WRITEFUNCTION
或CURLOPT_HEADERFUNCTION
回调中。
static Co::async(mixed $value, mixed $throw = null) : null
参数
(mixed)
$value
任何需要并行解决的值。(mixed)
$throw
当传递true
或false
时,覆盖Co::wait()
选项中的throw
。
返回值
(null)
异常
CURLException
或RuntimeException
可能在异常不安全上下文中抛出。
请注意,除非您在Co::wait()
调用之外捕获,否则您**无法**捕获顶级异常。
Co::isRunning()
返回 Co::wait()
是否正在运行。
通过此检查,您可以安全地调用 Co::wait()
或 Co::async()
。
static Co::isRunning() : bool
Co::any()
Co::race()
Co::all()
返回一个解析为特定值的生成器。
static Co::any(array $value) : \Generator<mixed> static Co::race(array $value) : \Generator<mixed> static Co::all(array $value) : \Generator<mixed>
家族 | 返回值 | 异常 |
---|---|---|
Co::any() |
首次成功 | AllFailedException |
Co::race() |
首次成功 | 首次失败 |
- 作业无法取消。
即使Co::any()
或Co::race()
已解析,仍会保留不完整的作业。 Co::all(...)
只是(function () { return yield ...; })()
的包装器。
它应仅与Co::race()
或Co::any()
一起使用。
Co::wait(function () { $group1 = Co::all([$ch1, $ch2, $ch3]); $group2 = Co::all([$ch4, $ch5, $ch6]); $group1or2 = Co::any([$group1, $group2]); var_dump(yield $group1or2); });
Co::setDefaultOptions()
Co::getDefaultOptions()
覆盖/获取静态默认设置。
static Co::setDefaultOptions(array $options) : null static Co::getDefaultOptions() : array
规则
解析时的转换
所有产生的/返回的值都按照以下规则解析。
产生的值也重新发送到生成器。
规则将递归应用。
之前 | 之后 |
---|---|
cURL 资源 | curl_multi_getconent() 结果或 CURLException |
数组 | 数组(包含已解析的子项)或 RuntimeException |
生成器闭包 生成器 |
返回值(所有产生完成后)或 RuntimeException |
"生成器闭包"意味着包含 yield
关键字的闭包。
异常安全或异常不安全优先级
生成器中的上下文
默认为异常不安全上下文。
以下yield
语句指定异常安全上下文。
$results = yield Co::SAFE => [$ch1, $ch2];
这等价于
$results = yield [ function () use ($ch1) { try { return yield $ch1; } catch (\RuntimeException $e) { return $e; } }, function () use ($ch2) { try { return yield $ch2; } catch (\RuntimeException $e) { return $e; } }, ];
在Co::wait()
上的上下文
默认为异常不安全上下文。
以下设置指定异常安全上下文。
$result = Co::wait([$ch1, $ch2], ['throw' => false]);
这等价于
$results = Co::wait([ function () use ($ch1) { try { return yield $ch1; } catch (\RuntimeException $e) { return $e; } }, function () use ($ch2) { try { return yield $ch2; } catch (\RuntimeException $e) { return $e; } }, ]);
在Co::async()
上的上下文
上下文是从Co::wait()
中继承的。
以下设置覆盖父上下文为异常安全。
Co::async($value, false);
以下设置覆盖父上下文为异常不安全。
Co::async($value, true);
每个协程的伪睡眠
以下yield
语句延迟协程处理
yield Co::DELAY => $seconds yield Co::SLEEP => $seconds # Alias
与PHP7.0+或PHP5.5~5.6生成器的比较
return
语句
PHP 7.0+
yield $foo; yield $bar; return $baz;
PHP 5.5~5.6
yield $foo; yield $bar; yield Co::RETURN_WITH => $baz;
尽管提供了实验性别名Co::RETURN_
Co::RET
Co::RTN
,
Co::RETURN_WITH
在可读性方面被推荐。
带赋值的yield
语句
PHP 7.0+
$a = yield $foo; echo yield $bar;
PHP 5.5~5.6
$a = (yield $foo); echo (yield $bar);
finally
语句
请注意,return
会触发finally
,而yield Co::RETURN_WITH =>
则不会。
try { return '...'; } finally { // Reachable }
try { yield Co::RETURN_WITH => '...'; } finally { // Unreachable }
附录
时间图
注意,当禁用autoschedule
时,S等于Q。
基本
ID | 当 |
---|---|
Q | 在调用curl_multi_add_handle() 后立即调用curl_multi_exec() |
S | 实际开始处理 |
DNS | DNS解析完成 |
TCP | TCP连接建立 |
TLS | TLS/SSL会话建立 |
HS | 所有HTTP请求头已发送 |
BS | 整个HTTP请求体已发送 |
HR | 所有HTTP响应头已接收 |
BR | 整个HTTP响应体已接收 |
常数 | 时间 |
---|---|
CURLINFO_NAMELOOKUP_TIME | DNS - S |
CURLINFO_CONNECT_TIME | TCP - S |
CURLINFO_APPCONNECT_TIME | TLS - S |
CURLINFO_PRETRANSFER_TIME | HS - S |
CURLINFO_STARTTRANSFER_TIME | HR - S |
CURLINFO_TOTAL_TIME | BR - Q |
通过CURLOPT_FOLLOWLOCATION
进行重定向
ID | 当 |
---|---|
Q | 在调用curl_multi_add_handle() 后立即调用curl_multi_exec() |
S | 实际开始处理 |
DNS(1) | DNS解析完成 |
TCP(1) | TCP连接建立 |
TLS(1) | TLS/SSL会话建立 |
HS(1) | 所有HTTP请求头已发送 |
BS(1) | 整个HTTP请求体已发送 |
HR(1) | 所有HTTP响应头已接收 |
DNS(2) | DNS解析完成 |
TCP(2) | TCP连接建立 |
TLS(2) | TLS/SSL会话建立 |
HS(2) | 所有HTTP请求头已发送 |
BS(2) | 整个HTTP请求体已发送 |
HR(2) | 所有HTTP响应头已接收 |
BR(2) | 整个HTTP响应体已接收 |
常数 | 时间 |
---|---|
CURLINFO_REDIRECT_TIME | HR(1) - Q |
CURLINFO_NAMELOOKUP_TIME | DNS(2) - HR(1) |
CURLINFO_CONNECT_TIME | TCP(2) - HR(1) |
CURLINFO_APPCONNECT_TIME | TLS(2) - HR(1) |
CURLINFO_PRETRANSFER_TIME | HS(2) - HR(1) |
CURLINFO_STARTTRANSFER_TIME | HR(2) - HR(1) |
CURLINFO_TOTAL_TIME | BR(2) - Q |