前言

之前工作中有个需求,判断CDN上某个文件是否存在。很显然,用一个HTTP的HEAD请求就可以搞定。

在本地启动一个nginx服务,服务目录里放一个几百兆的视频文件。用curl -I http://localhost/test/test.mp4命令访问一下,同时打开wireshark抓包看看。如下图所示,nginx只响应了一个http的头信息。

get_headers

php中有个函数get_headers,怪我太年轻啊,一看这名字就以为是通过HEAD方法获取url的头信息。我们写代码实测一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<?php
$rs = get_headers('http://localhost/test/test.mp4');
print_r($rs);

Array
(
    [0] => HTTP/1.1 200 OK
    [1] => Server: nginx/1.19.2
    [2] => Date: Sat, 15 Aug 2020 14:31:51 GMT
    [3] => Content-Type: video/mp4
    [4] => Content-Length: 368455569
    [5] => Last-Modified: Fri, 12 Jun 2020 16:26:54 GMT
    [6] => Connection: close
    [7] => ETag: "5ee3acce-15f62f91"
    [8] => Accept-Ranges: bytes
)

看起来没有问题。但是一抓包就露馅了。

get_headers发出去的并不是HEAD请求,而是GET请求,而且更流氓的是,当对方返回数据内容时,php粗暴地发送RST断开tcp连接。真是坑爹货啊。

改进

get_headers到底能不能用head方法呢?其实也可以,从php7.1开始,get_headers多接受了一个context的参数,我们可以通过这个参数设置http请求方法。代码如下:

1
2
3
4
<?php
$context = stream_context_create(['http' => ['method' => 'HEAD']]);
$rs      = get_headers('http://localhost/test/test.mp4', 0, $context);
print_r($rs);

图就不贴了,效果和上边curl命令一样。

CURL

php中还有一个重要的网络请求工具,那就是curl扩展,如果想用这个扩展完成这个功能,又该怎么写呢?其实也很简单。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php
$ch = curl_init('http://localhost/test/test.mp4');
curl_setopt($ch, CURLOPT_NOBODY, true); // 设置不接受响应体,curl就会以HEAD方法发送请求
curl_setopt($ch, CURLOPT_HEADER, true); // 请求结果中显示头信息
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 请求结果作为返回值而非直接打印
$rs = curl_exec($ch);
echo $rs;


HTTP/1.1 200 OK
Server: nginx/1.19.2
Date: Sat, 15 Aug 2020 14:50:42 GMT
Content-Type: video/mp4
Content-Length: 368455569
Last-Modified: Fri, 12 Jun 2020 16:26:54 GMT
Connection: keep-alive
ETag: "5ee3acce-15f62f91"
Accept-Ranges: bytes

抓包可以发现的确是用了HEAD方法。

总结

日常工作中看似简单的函数也可能有坑,好在如果是网络方面的函数我们可以通过抓包的方式观察这个函数到底是在干什么。wireshark真香!