Asynchronous requests in Guzzle

Use Guzzle to make asynchronous requests

Guzzle is a PHP HTTP client. It can not only initiate HTTP requests synchronously, but also asynchronously.

$client = new Client();
$request = new Request('GET', 'http://www.baidu.com');
$promise = $client->sendAsync($request)->then(function ($response) {
    echo $response->getBody();
});
// todo something
echo 1;
$promise->wait();

Several ways for PHP to initiate HTTP requests

curl

Using the libcurl library allows you to connect and communicate with various servers using various types of protocols.

stream

Get and send remote files by stream. This function requires ini to configure allow_url_fopen=on. More about the flow of php An overview and use of PHP Stream

In guzzle, you can use either of them or the user-defined http handler

function choose_handler()
{
    $handler = null;
    if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
        $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
    } elseif (function_exists('curl_exec')) {
        $handler = new CurlHandler();
    } elseif (function_exists('curl_multi_exec')) {
        $handler = new CurlMultiHandler();
    }

    if (ini_get('allow_url_fopen')) {
        $handler = $handler
            ? Proxy::wrapStreaming($handler, new StreamHandler())
            : new StreamHandler();
    } elseif (!$handler) {
        throw new \RuntimeException('GuzzleHttp requires cURL, the '
            . 'allow_url_fopen ini setting, or a custom HTTP handler.');
    }

    return $handler;
}

As you can see, guzzle will give priority to curl, and then choose to use stream. Proxy::wrapStreaming($handler, new StreamHandler()) is a stream wrapper.

    public static function wrapStreaming(
        callable $default,
        callable $streaming
    ) {
        return function (RequestInterface $request, array $options) use ($default, $streaming) {
            return empty($options['stream'])
                ? $default($request, $options)
                : $streaming($request, $options);
        };
    }

What is a URI? Composition of URIs

URI, Uniform Resource Identifier, Uniform Resource Identifier.
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]

Assembly requested

Guzzle initiates requests in two phases. The first phase is responsible for assembling the URIs that need to be requested into various internally defined classes.

  • Client class: This is the caller who initiates the client. All subsequent calls need to be implemented based on this responsible class. It is responsible for providing a handler, which is a handle to the http request initiated by the client. In this case, Guzzle implements the imperceptibility of curl and stream calls. At the same time, developers can also customize the request protocol.
// Select a method handle of the protocol that initiates the Http request according to the current state of the system
function choose_handler()
{
    $handler = null;
    if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
        $handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
    } elseif (function_exists('curl_exec')) {
        $handler = new CurlHandler();
    } elseif (function_exists('curl_multi_exec')) {
        $handler = new CurlMultiHandler();
    }

    if (ini_get('allow_url_fopen')) {
        $handler = $handler
            ? Proxy::wrapStreaming($handler, new StreamHandler())
            : new StreamHandler();
    } elseif (!$handler) {
        throw new \RuntimeException('GuzzleHttp requires cURL, the '
            . 'allow_url_fopen ini setting, or a custom HTTP handler.');
    }

    return $handler;
}
  • Request class: responsible for defining a uri
  • Promise class: this class is responsible for carrying the results of various preparatory work before the class request is launched, including two callbacks (request success callback and request failure callback). At the same time, the queue, delay and other processing in the request initiation are also in this class.

The most important method in the assembly stage is the private function transfer(RequestInterface $request, array $options). It is responsible for combining the uri passed in by the user through various methods with various properties of the client class, and then using these properties to generate a new class Promise class.

Request initiation

After the Client's various properties are assembled, you can use the Promise class to initiate http requests, mainly through a wait() method.

Synchronous call and asynchronous call

In the internal call of the synchronization method, the synchronization method is to start the wait() call immediately after the internal assembly of a Promise.

    public function send(RequestInterface $request, array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->sendAsync($request, $options)->wait();
    }

Implementation of wait

The implementation logic of the wait() method is also very simple. Call the wait() method recursively until the result property is not a PromiseInterface implementation class or the state is not pending, and then output the result layer by layer. Let's talk about the pending state of this state, which is the initialization state of the PromiseInterface implementation class. It means that the implementation class has not been completed yet, so we need to continue waiting.

    public function wait($unwrap = true)
    {
        $this->waitIfPending();

        $inner = $this->result instanceof PromiseInterface
            ? $this->result->wait($unwrap)
            : $this->result;

        if ($unwrap) {
            if ($this->result instanceof PromiseInterface
                || $this->state === self::FULFILLED
            ) {
                return $inner;
            } else {
                // It's rejected so "unwrap" and throw an exception.
                throw exception_for($inner);
            }
        }
    }

waitIfPending(): executes if the promise class is still in the pending state. It is mainly to implement waitFn method of implementation class. After the implementation of the outermost promise, execute queue() - > run() ` ` this method circulates internally to execute the methods in the queue until the queue is empty. At this point, Guzzle can execute multiple request s and various methods assembled.

    private function waitIfPending()
    {
        if ($this->state !== self::PENDING) {
            return;
        } elseif ($this->waitFn) {
            $this->invokeWaitFn();
        } elseif ($this->waitList) {
            $this->invokeWaitList();
        } else {
            // If there's not wait function, then reject the promise.
            $this->reject('Cannot wait on a promise that has '
                . 'no internal wait function. You must provide a wait '
                . 'function when constructing the promise to be able to '
                . 'wait on a promise.');
        }

        queue()->run();

        if ($this->state === self::PENDING) {
            $this->reject('Invoking the wait callback did not resolve the promise');
        }
    }

    public function run()
    {
        /** @var callable $task */
        while ($task = array_shift($this->queue)) {
            $task();
        }
    }

What is waitFn

Go back to the transfer() function mentioned earlier.

$handler = $options['handler'];
// Returns a promise class with a waitFn attribute
return Promise\promise_for($handler($request, $options));

Let's see what $handler is here? It is a HandleStack class, which is the method handle and instantiation class of the protocol that we choose to initiate Http request when we are in new Client. The calls after < br / > are HandleStack - >__ invoke,RedirectMiddleware->__ invoke,PrepareBodyMiddleware->__ invoke. Execute $fn($request, $options); methods, after the previous layer by layer processing, at this time, $fn is the Proxy wrapper method inside HandleStack. No matter which protocol is used, an instance of the Proxy with waitFn will be instantiated in their respective implementation.

        // The implementation of curl
                $promise = new Promise(
            [$this, 'execute'],
            function () use ($id) {
                return $this->cancel($id);
            }
        );

So we can wait until waitFn method is the request initiation method of each protocol's implementation class. The then() method encapsulates the promise itself with another layer of promise, and packages the original waitFn and then() callback methods into the waitFnList attribute.

queue() is the time to join the team

When the request is completed, call processMessages(), promise - > resolve(), set(), fulfilled promise - > then(), and insert the request result into the queue.

        $queue->add(static function () use ($p, $value, $onFulfilled) {
            if ($p->getState() === self::PENDING) {
                try {
                    $p->resolve($onFulfilled($value));
                } catch (\Throwable $e) {
                    $p->reject($e);
                } catch (\Exception $e) {
                    $p->reject($e);
                }
            }
        });

Keywords: PHP curl Attribute Fragment

Added by estan on Thu, 11 Jun 2020 08:41:35 +0300