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); } } });