mpyw/co

此包已被废弃,不再维护。未建议替代包。

基于资源和生成器的异步cURL执行器

维护者

详细信息

github.com/mpyw/co

源代码

问题

v1.5.4 2016-08-28 14:02 UTC

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&amp;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 是否在顶层抛出或捕获 CURLExceptionRuntimeException
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请求包不能进行管道传输。您可以从第二个 yieldCo::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,该异常由用户抛出。

异常

  • 在异常不安全上下文中抛出 CURLExceptionRuntimeException

Co::async()

在调用 Co::wait() 的同时执行 cURL 请求,不等待 解析后的值。
选项继承自 Co::wait()

此方法主要预期用于 ...

  • 当您对响应不感兴趣时。
  • CURLOPT_WRITEFUNCTIONCURLOPT_HEADERFUNCTION 回调中。
static Co::async(mixed $value, mixed $throw = null) : null

参数

  • (mixed) $value
    任何需要并行解决的值。
  • (mixed) $throw
    当传递 truefalse 时,覆盖 Co::wait() 选项中的 throw

返回值

(null)

异常

  • CURLExceptionRuntimeException 可能在异常不安全上下文中抛出。
    请注意,除非您在 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