本文适合寻找PHP HTTP客户端库,或者对于Guzzle的使用和实现原理比较感兴趣的同学阅读,需要具备一定的PHP基础知识。
Guzzle是一个PHP的HTTP客户端,用来轻而易举地发送请求,并集成到我们的WEB服务上。
背景
在PHP后台开发过程中,经常会遇到模块间需要通过HTTP通信的情形。PHP语言本身只提供了socket
操作的接口,并未提供HTTP相关操作的接口。许多现有的实现采用curl扩展充当 HTTP Client
与 HTTP Server
通信,但仍需自己封装curl的接口。有鉴于此,本文介绍一款流行的 PHP HTTP Client客户端—Guzzle的用法,深入分析其底层实现原理。
Guzzle
基础
需求
- PHP 5.5.0+
- 使用PHP的流,
allow_url_fopen
必须在php.ini中启用。 - 要使用cURL,你必须已经有版本
cURL >= 7.19.4
,并且编译了OpenSSL
与zlib
安装
你可以使用composer.phar客户端将Guzzle作为依赖添加到项目:
1 | php composer.phar require guzzlehttp/guzzle:~6.0 |
或者,你可以编辑项目中已存在的composer.json文件,添加Guzzle作为依赖:
1 | { |
安装完毕后,你需要引入Composer的自动加载文件:
1 | require 'vendor/autoload.php'; |
测试
1 | git clone https://github.com/guzzle/guzzle.git |
Guzzle is unit tested with PHPUnit. Run the tests using the Makefile:
1 | make test |
Guzzle
用法
例如使用Guzzle访问 http://www.baidu.com
的代码:
1 |
|
接口封装是不是十分简单?只需要关心请求方法,目标url和请求的选项即可快速上手。同时,Guzzle
还支持异步请求方式:
1 |
|
基于异步请求,Guzzle还实现了并发请求,关于Guzzle的具体使用方法可以参考其 Guzzle中文文档
Guzzle
实现原理
1.client构造
GuzzleHttp\Client
类构造函数声明为:
1 | public function __construct(array $config = []) |
$config
配置使得用户可以根据需要配置一切可以配置的选项,包括allowredirects
、auth
、connecttimeout
、proxy
等。除此之外,还可以自定义请求的处理函数handler
,方便应用程序扩展,handler
接口规范为:
1 | function handler($request, array $options); |
处理成功时,接口返回 Psr\Http\Message\ResponseInterface
;
失败时返回 GuzzleHttp\Exception\RequestException
异常。
默认情形下,GuzzleHttp\HandlerStack::create
会创建请求处理函数
1 | public static function create(callable $handler = null) |
create
函数以堆栈的形式创建了一系列的处理函数,包括 http异常、重定向、cookie和prepare_body。处理函数返回的函数闭包为:
1 | return function (callable $handler) { |
函数入参为handler
,返回一个新的handler
,这样可以将所有的处理函数链接在一起,最终生成一个符合handler
接口规范的函数.
choose_handler
函数选择stack
中的起始handler
,选择策略为:
扩展自带
curl_multi_exec
和curl_exec
函数则根据$options
中的synchronous
选项决定,empty(synchronous)
为false
则使用CurlHandler
,否则使用CurlMultiHandler
扩展只有
curl_exec
函数则使用CurlHandler
扩展只有
curl_multi_exec
函数则使用CurlMultiHandler
最后,如果 php.ini 中开启了allow_url_fopen
,则根据$options
中的stream
选项决定,empty(stream)
为false
则使用StreamHandler
2.client调用request方法
request方法实现为:
1 | public function request($method, $uri = '', array $options = []) |
由此可见,request事实上是采用了requestAsync异步方法+wait来完成的,也就是异步转同步。
requestAsync
requestAsync
将请求信息包装成 Psr7\Request
对象,然后调用
1 | transfer(RequestInterface $request, array $options) |
transfer
函数最终返回Promise\promise_for($handler($request, $options))
; 其中$handler
即为构造函数中所设置的stack
,stack
中存放一系列的请求处理函数。 HandlerStack
的处理函数为:
1 | public function __invoke(RequestInterface $request, array $options) |
resolve
方法解析整个stack
,返回一个包装后的handler
,包装策略为按照出栈顺序包装,也就是
1 | foreach (array_reverse($this->stack) as $fn) { |
典型的中间件模型,所有的处理函数串接在一起了。请求经由http_errors
、allow_redirects
等处理之后到达Curl
,执行真正的网络交互。
对于同步的
handler
如CurlHandler
,在此处会执行curl_exec
发起请求,最终返回的是FulfilledPromise对象
或RejectedPromise对象
,代表请求已经处理完毕。对于异步的
handler
比如CurlMultiHandler
,在此处并不会执行curl_multi_exec
,而是返回一个promise
对象,里面注册了需要等待执行的curl_multi_exec
。
wait
请求发送完毕,进入promise
的wait
操作,最终会执行promise
的$waitFn
函数。
对于
CurlMultiHandler
,$waitFn
即执行curl_multi_exec
进行网络交互,然后调用resolve
方法将response对象
传递到then
方法的$onFulfilled
函数。对于
CurlHandler
,直接利用resolve
将response
对象传递到$onFulfilled
函数。
这样,异步的then
方法设置的回调就可以接收到response
了。 then方法最终返回response,这个对象又可以作为返回值返回,这样同步的wait就可以通过返回值来获取response对象了。
总结
本文重点介绍了 Guzzle
同步和异步请求的实现原理,除此之外,Guzzle还提供了并行请求,请求pool等实现,读者可以在此基础上继续深入。