symplely/hyper

一个简单的先进异步 PSR-7 和 PSR-18 HTTP 客户端,使用协程。

1.0.0 2020-03-09 14:36 UTC

This package is auto-updated.

Last update: 2024-09-07 23:09:09 UTC


README

Build StatusBuild statuscodecovCodacy BadgeMaintainability

一个简单的先进 PSR-7 实现,以及使用协程的异步 PSR-18 HTTP 客户端。

目录

介绍/使用

本软件包基于 协程,使用 yieldgenerator,它需要我们的其他仓库软件包 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 动词的方法:GETPOSTPUTPATCHDELETEHEADOPTIONS。然而,您可以直接使用 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_basicauth_bearerauth_digest,而 valuepasswordtoken
  • 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)。请参阅许可文件获取更多信息。