symplely / hyper
一个简单的先进异步 PSR-7 和 PSR-18 HTTP 客户端,使用协程。
Requires
- php: >7.1
- ext-json: *
- symplely/coroutine: ^1.7.0
- symplely/http: ^1.1.0
- symplely/logger: ^1.0.9
Requires (Dev)
- phpunit/phpunit: ^6 | ^7
Provides
This package is auto-updated.
Last update: 2024-09-07 23:09:09 UTC
README
一个简单的先进 PSR-7 实现,以及使用协程的异步 PSR-18 HTTP 客户端。
目录
介绍/使用
本软件包基于 协程,使用 yield
和 generator
,它需要我们的其他仓库软件包 Coroutine。它的构建灵感来源于,并对 shuttle(一个 PSR-18 客户端)进行了改进。
关于 协程 有很多可说的,但为了快速了解,请查看这个 视频,如果您对概念或结构不熟悉。观看视频时,请注意,这是一个回调 vs 承诺 vs 生成器的概述,在其他语言中使用异步/等待结构。并且那里的 Promise
引用在这里指的是作为 Task
返回的 Integer
。
这个库和这里的整个 协程 概念都是基于 永远不要 允许用户/开发者 直接 访问 Task,这个类似 Promise
的对象。关于概念性用法的示例,请参阅 高级 Asyncio:解决现实世界生产问题。
从示例文件夹
/** * @see https://github.com/amphp/artax/blob/master/examples/6-parallel-requests.php */ include 'vendor/autoload.php'; use Async\Coroutine\Exceptions\Panicking; // An infinite loop example task, this will output on all task executions, pauses, stalls, or whatever. function lapse() { $i = 0; while(true) { $i++; print $i.'.lapse '; yield; } } // An initial function is required, this gives all other tasks an execution point to begin. function main() { // start an background job/task yield \await(lapse()); $uris = [ "https://github.com/", "https://google.com/", "https://stackoverflow.com/", 'http://creativecommons.org/' ]; try { $uriId = []; // Make an asynchronous HTTP requests // this `array` will collect `int`, represents an `http task id` // each `request()` starts an new background task, does not block or wait. // the `array` will be passed to `fetch();` for resolution. foreach ($uris as $uri) { $uriId[] = yield \request(\http_get($uri)); } // Executions here will pause and wait for each request to receive an response, // all previous tasks started will continue to run, so you will see output only // from the `lapse` task here until the foreach loop starts. // Doing `\fetchOptions(1);` before this statement will wait for only 1 response. $bodies = yield \fetch($uriId); foreach ($bodies as $id => $result) { $uri = \response_meta($result, 'uri'); // will pause execution of loop until full body is received, only `lapse` task will still output $body = yield \response_body($result); print \EOL."HTTP Task $id: ". $uri. " - " . \strlen($body) . " bytes" . \EOL.\EOL; } } catch (Panicking $error) { echo 'There was a problem: '.$error->getMessage(); } // display the collected stats logs \print_defaultLog(); yield \http_closeLog(); // if you don't the infinite loop will continue it's output. yield \shutdown(); } // Coroutine code needs to be bootstrapped. // The function called `MUST` have at least one `yield` statement. \coroutine_run(\main());
函数
在此处和 Core.php 文件中列出的函数是推荐使用此软件包的方式。正在使用函数式编程方法,实际使用的 OOP 类库正在不断变化,但这些调用函数将不会变化。
const SYMPLELY_USER_AGENT = 'Symplely Hyper PHP/' . \PHP_VERSION; // Content types for header data. const HTML_TYPE = 'text/html'; const OCTET_TYPE = 'application/octet-stream'; const XML_TYPE = 'application/xml'; const PLAIN_TYPE = 'text/plain'; const MULTI_TYPE = 'multipart/form-data'; const JSON_TYPE = 'application/json'; const FORM_TYPE = 'application/x-www-form-urlencoded'; /** * This function works similar to coroutine `await()` * * Takes an `request` instance or `yield`ed coroutine of an request. * Will immediately return an `int` HTTP task id, and continue to the next instruction. * Will resolve to an Response instance when `fetch()` * * - This function needs to be prefixed with `yield` */ yield \request(); /** * Run awaitable HTTP tasks in the requests set concurrently and block * until the condition specified by count. * * This function works similar to `gatherWait()`. * * Controls how the `wait/fetch` functions operates. * `await()` will behave like **Promise** functions `All`, `Some`, `Any` in JavaScript. * * - This function needs to be prefixed with `yield` */ yield \fetch_await($requests, $count, $exception, $clearAborted); /** * This function works similar to coroutine `gather()` * * Takes an array of request HTTP task id's. * Will pause current task and continue other tasks until * the supplied request HTTP task id's resolve to an response instance. * * - This function needs to be prefixed with `yield` */ yield \fetch(...$requests); /** * This function works similar to `cancel_task()` * * Will abort the supplied request HTTP task id and close stream. * * - This function needs to be prefixed with `yield` */ yield \request_abort($httpId); /** * This function is automatically called by the http_* functions. * * Creates and returns an `Hyper` instance for the global HTTP functions by. */ \http_instance($tag); /** * Clear & Close the global `Hyper`, and `Stream` Instances by. */ \http_clear($tag); /** * Close and Clear `ALL` global Hyper function, Stream instances. */ \http_nuke(); /** * Make a GET request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_get($tagUri, ...$authorizeHeaderOptions); /** * Make a PUT request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_put($tagUri, ...$authorizeHeaderOptions); /** * Make a POST request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_post($tagUri, ...$authorizeHeaderOptions); /** * Make a PATCH request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_patch($tagUri, ...$authorizeHeaderOptions); /** * Make a DELETE request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_delete($tagUri, ...$authorizeHeaderOptions); /** * Make a OPTIONS request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_options($tagUri, ...$authorizeHeaderOptions); /** * Make a HEAD request, will pause current task, and * continue other tasks until an response is received. * * - This function needs to be prefixed with `yield` */ yield \http_head($tagUri, ...$authorizeHeaderOptions); /** * This function is automatically called by the http_* functions. * * Set global functions response instance by. */ \response_set($response, $tag); /** * This function is automatically called by the response_* functions. * * Return current global functions response instance by. */ \response_instance($tag); /** * Clear and close global functions response/stream instance by. */ \response_clear($tag); /** * Close and Clear `ALL` global functions response instances. */ \response_nuke(); /** * Is response from an successful request? * Returns an `bool` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_ok($tag); /** * Returns response reason phrase `string` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_phrase($tag); /** * Returns response status code `int` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_code($tag); /** * Check if response has header key by. * Returns `bool` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_has($tag, $header); /** * Retrieve a response value for header key by. * Returns `string` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_header($tag, $header); /** * returns response FULL body. * * - This function needs to be prefixed with `yield` */ yield \response_body($tag); /** * Returns `string` of response metadata by key. */ \response_meta($tag, $key); /** * Check if response body been read completely by. * Returns `bool` or NULL, if not ready. * * This function can be used in an loop control statement, * which you will `yield` on `NULL`. */ \response_eof($tag); /** * returns response STREAM body. * * - This function needs to be prefixed with `yield` */ yield \response_stream($tag, $size); /** * returns response JSON body. * * - This function needs to be prefixed with `yield` */ yield \response_json($tag, $assoc); /** * returns response XML body. * * - This function needs to be prefixed with `yield` */ yield \response_xml($tag, $assoc);
安装
composer require symplely/hyper
使用/历史
发起请求:简单老式的方式,但有注意事项,需要以 yield 前缀
开始发起请求的最快和最简单的方法是使用 HTTP 方法名称
use Async\Request\Hyper; function main() { $http = new Hyper; $response = yield $http->get("https://www.google.com"); $response = yield $http->post("https://example.com/search", ["Form data"]));
此库内置了支持主要 HTTP 动词的方法:GET
、POST
、PUT
、PATCH
、DELETE
、HEAD
和 OPTIONS
。然而,您可以直接使用 request 方法发起任何 HTTP 动词请求,该方法返回一个 PSR-7 RequestInterface
。
$request = $http->request("connect", "https://api.example.com/v1/books"); $response = yield $http->sendRequest($request);
处理响应
Hyper 中的响应实现了 PSR-7 ResponseInterface
,因此是可流式传输的资源。
$response = $http->get("https://api.example.com/v1/books"); echo $response->getStatusCode(); // 200 echo $response->getReasonPhrase(); // OK // The body is return asynchronous in an non-blocking mode, // and as such needs to be prefixed with `yield` $body = yield $response->getBody()->getContents(); } // All coroutines/async/await needs to be enclosed/bootstrapped // in one `main` entrance routine function to properly execute. // The function `MUST` have at least one `yield` statement. \coroutine_run(\main());
处理失败的请求
默认情况下,如果请求失败,此库将抛出 RequestException
。这包括诸如主机名未找到、连接超时等问题。
HTTP 4xx 或 5xx 状态码的响应 不会 抛出异常,必须在您的业务逻辑中正确处理。
发起请求:PSR-7 的方式,但有注意事项,需要以 yield 前缀
如果您重视代码的可重用性和可移植性,通过以 PSR-7 的方式发起请求来为您的代码提供未来保障。请记住,PSR-7 规定了请求和响应消息必须是不可变的。
use Async\Request\Uri; use Async\Request\Request; use Async\Request\Hyper; function main() { // Build Request message. $request = new Request; $request = $request ->withMethod("get") ->withUri(Uri::create("https://www.google.com")) ->withHeader("Accept-Language", "en_US"); $http = new Hyper; // Send the Request. // Pauses current/task and send request, // will continue next to instruction once response is received, // other tasks/code continues to run. $response = yield $http->sendRequest($request); } // All coroutines/async/await needs to be enclosed/bootstrapped // in one `main` entrance routine function to properly execute. // The function `MUST` have at least one `yield` statement. \coroutine_run(\main());
选项
以下选项可以在每次请求中传递。
$http->request($method, $url, $body = null, array ...$authorizeHeaderOptions);
Authorization
一个数组,其中 key 为:auth_basic
、auth_bearer
、auth_digest
,而 value 为password
或token
。Headers
一个键值对数组,用于传递到每个请求中。Options
一个键值对数组,用于传递到每个请求中。
请求体
使用Body
类提交数据是件简单的事。这个类会自动转换数据,将其转换为BufferStream
,并在请求中设置默认的Content-Type
头。
将以下之一的CONSTANTS
传递给类构造函数:
Body::JSON
将关联数组转换为JSON,并将Content-Type
头设置为application/json
。Body::FORM
将关联数组转换为查询字符串,并将Content-Type
头设置为application/x-www-form-urlencoded
。Body::XML
不转换数据,并将Content-Type
头设置为application/xml
。Body::FILE
不转换数据,将检测并设置Content-Type
头。Body::MULTI
不转换数据,并将Content-Type
头设置为multipart/form-data
。
提交带有请求的JSON有效载荷
use Async\Request\Body; use Async\Request\Hyper; function main() { $book = [ "title" => "Breakfast Of Champions", "author" => "Kurt Vonnegut", ]; $http = new Hyper; yield $http->post("https://api.example.com/v1/books", [Body::JSON, $book]); // Or yield $http->post("https://api.example.com/v1/books", new Body(Body::JSON, $book)); // Or yield $http->post("https://api.example.com/v1/books", Body::create(Body::JSON, $book)); // Otherwise the default, will be submitted in FORM format of `application/x-www-form-urlencoded` yield $http->post("https://api.example.com/v1/books", $book); } // All coroutines/async/await needs to be enclosed/bootstrapped // in one `main` entrance routine function to properly execute. // The function `MUST` have at least one `yield` statement. \coroutine_run(\main());
贡献
欢迎贡献;我总是很高兴在Github上收到反馈或拉取请求 :) 为错误和新的功能创建Github Issues,并评论您感兴趣的内容。
许可证
MIT许可(MIT)。请参阅许可文件获取更多信息。