Guzzle中的异步请求使用Guzzle发起异步请求Guzzle是一个PHPHTTP客户端,它既可以同步发起http请求,也可以异步发起。$客户=新客户();$request=newRequest('GET','http://www.baidu.com');$promise=$client->sendAsync($request)->then(function($response){echo$response->getBody();});//todosomethingecho1;$promise->wait();PHP发起HTTP请求的几种方式curl使用了libcurl库,允许您使用它与各种服务器的各种类型的协议进行连接和通信。stream以流的方式获取和发送远程文件,该功能需要ini配置allow_url_fopen=on。关于php流的更多信息,请参考PHP流(Stream)的概述和详解。在guzzle中,您可以使用这两个或用户定义的http处理程序函数choose_handler(){$handler=null;if(function_exists('curl_multi_exec')&&function_exists('curl_exec')){$handler=Proxy::wrapSync(newCurlMultiHandler(),newCurlHandler());}elseif(function_exists('curl_exec')){$handler=newCurlHandler();}elseif(function_exists('curl_multi_exec')){$handler=newCurlMultiHandler();}if(ini_get('allow_url_fopen')){$handler=$handler?Proxy::wrapStreaming($handler,newStreamHandler()):newStreamHandler();}elseif(!$handler){thrownew\RuntimeException('GuzzleHttp需要cURL、'.'allow_url_fopenini设置或自定义HTTP处理程序。');}return$handler;}可以看出,guzzle会先使用curl,然后选择使用stream,Proxy::wrapStreaming($handler,newStreamHandler())是streamwrapper。publicstaticfunctionwrapStreaming(callable$default,callable$streaming){returnfunction(RequestInterface$request,array$options)use($default,$streaming){returnempty($options['stream'])?$default($request,$options):$streaming($request,$options);};}什么是URI?URI的组成URI,UniformResourceIdentifier,统一资源标识符。scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]RequestassemblyGuzzle发起一个请求大致分为两个阶段,第一阶段负责请求URIs被组装成各种内部定义的类。客户端类:这是启动客户端的调用者。后续的所有调用都需要基于这个负责的类来实现。它负责提供一个handler,这个handler是客户端发起http请求的句柄。Guzzle实现了curl和streamcalls而没有Perception在这里实现,开发者也可以自定义请求协议。//根据系统当前状态,为发起Http请求的协议选择一个方法句柄functionchoose_handler(){$handler=null;if(function_exists('curl_multi_exec')&&function_exists('curl_exec')){$handler=Proxy::wrapSync(newCurlMultiHandler(),newCurlHandler());}elseif(function_exists('curl_exec')){$handler=newCurlHandler();}elseif(function_exists('curl_multi_exec')){$handler=newCurlMultiHandler();}if(ini_get('allow_url_fopen')){$handler=$handler?Proxy::wrapStreaming($handler,newStreamHandler()):newStreamHandler();}elseif(!$handler){thrownew\RuntimeException('GuzzleHttp需要cURL、'.'allow_url_fopenini设置或自定义HTTP处理程序。');}return$handler;}请求类:负责定义一个uriPromise类:该类负责承载请求发起前的各种准备工作完成后的结果还包括两个回调(请求成功回调,请求失败回调),以及队列和请求发起的延迟处理也属于此类。组装阶段最重要的方法是私有方法privatefunctiontransfer(RequestInterface$request,array$options),负责将用户通过各种方法传入的uri和客户端类的各种属性进行组合,然后使用这些属性生成一个类似于NewPromise的类。请求的发起客户端的各种属性组装完成后,就可以使用得到的Promise类来发起http请求了,主要是通过一个wait()方法。同步调用和异步调用都是在同步方法内部调用的。同步方法在内部组装一个Promise后立即启动wait()调用。publicfunctionsend(RequestInterface$request,array$options=[]){$options[RequestOptions::SYNCHRONOUS]=true;返回$this->sendAsync($request,$options)->wait();}wait实现wait()方法的实现逻辑也很简单。递归调用wait()方法,直到result属性不是PromiseInterface实现类或者状态不是pending,然后逐层输出结果。这里先说说这个状态的pending状态。这是一个PromiseInterface实现类的初始化状态,也就是说这个实现类还没有完成,需要继续等待。publicfunctionwait($unwrap=true){$this->waitIfPending();$inner=$this->resultinstanceofPromiseInterface?$this->result->wait($unwrap):$this->result;if($unwrap){if($this->resultinstanceofPromiseInterface||$this->state===self::FULFILLED){return$inner;}else{//它被拒绝了,所以“展开”并抛出异常。抛出exception_for($inner);}}}waitIfPending():如果promise类仍处于挂起状态则执行。主要是执行实现类的waitFn方法。执行完最外层的promise后执行queue()->run()``该方法循环执行队列中的方法,直到队列为空。此时,Guzzle可以执行组装的多个请求和各种方法。privatefunctionwaitIfPending(){if($this->state!==self::PENDING){返回;}elseif($this->waitFn){$this->invokeWaitFn();}elseif($this->waitList){$this->invokeWaitList();}else{//如果没有等待函数,则拒绝承诺。$this->reject('不能等待具有'.'没有内部等待功能的承诺。您必须在构建承诺时提供等待'.'功能才能'.'等待承诺。');}队列()->运行();if($this->state===self::PENDING){$this->reject('调用等待回调没有解决承诺');}}publicfunctionrun(){/**@varcallable$task*/while($task=array_shift($this->queue)){$task();}}waitFn是什么返回到前面提到的transfer()函数。$handler=$options['handler'];//返回一个promise类,它有一个属性waitFnreturnPromise\promise_for($handler($request,$options));这里我们看看$handler是什么?它是一个HandleStack类,是我们在newClient时选择发起Http请求的协议的方法句柄和实例化类。
下面的调用分别是HandleStack->__invoke、RedirectMiddleware->__invoke、PrepareBodyMiddleware->__invoke。执行$fn($request,$options);方法。经过前面的层层处理,此时的$fn就是HandleStack内部的Proxy封装方法。无论使用哪种协议,都会在各自的实现中实例化一个带有waitFn的waitFn。Promise的一个实例。//curl的实现$promise=newPromise([$this,'execute'],function()use($id){return$this->cancel($id);});由此可知,waitFn方法是各自协议的实现类的请求发起方法。then()方法会将promise本身再封装一层promise,将原来的waitFn和then()回调方法封装到waitFnList属性中。queue()是加入队列的时间。当请求执行时,依次调用processMessages()、promise->resolve()、settle()、FulfilledPromise->then()将请求结果插入队列。$queue->add(staticfunction()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);}}});
