curl
使用 cURL 获取响应头更加困难,因为它们不会立即可用,这与我们使用内置文件函数时不同。
相反,我们必须手动从请求中提取标头。
这可以在执行请求后使用 curl_getinfo 函数完成:
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $headers = substr($response, 0, $header_size); $body = substr($response, $header_size);
然后将标头作为字符串存储在 $headers 变量中。
为了从字符串创建关联数组,我们可以遍历字符串中的每一行,并在执行时将其内容保存到数组中:
//Define the $response_headers array for later use $response_headers = []; //Get the first line (The Status Code) $line = strtok($headers, "\r\n"); $status_code = trim($line); //Parse the string, saving it into an array instead while (($line = strtok("\r\n")) !== false) { if(false !== ($matches = explode(':', $line, 2))) { $response_headers["{$matches[0]}"] = trim($matches[1]); } }
当我们使用内置 PHP 文件函数执行 HTTP 请求时,响应标头将自动在特殊变量 $http_response_header 中可用;这在使用内置文件函数时非常有用,但在使用 cURL 库时不起作用。
在本教程中,我们将学习如何解析请求标头,无论我们使用的是 cURL 还是 file_get_contents 等文件函数:
有点奇怪的是,标头存储为索引数组,而不是更用户友好的关联数组;但这只是一个小小的不便,因为我们可以轻松地自行转换为关联数组。
为了解析响应头并创建一个关联数组,我们可以将这个解决方案用于内置文件函数:
$response_headers = []; $status_message = array_shift($http_response_header); foreach ($http_response_header as $value) { if(false !== ($matches = explode(':', $value, 2))) { $response_headers["{$matches[0]}"] = trim($matches[1]); } }
使用 cURL 库时的这个:
//Define the $response_headers array for later use $response_headers = []; //Get the first line (The Status Code) $line = strtok($headers, "\r\n"); $status_code = trim($line); //Parse the string, saving it into an array instead while (($line = strtok("\r\n")) !== false) { if(false !== ($matches = explode(':', $line, 2))) { $response_headers["{$matches[0]}"] = trim($matches[1]); } }
这样做可以轻松检查给定的标头是否存在,只需在数组键上使用 isset 即可:
if (isset($response_headers["content-type"])) { echo '<p>The "content-type" header was found, and the content is:</p>'; echo $response_headers["content-type"]; exit(); }
文件函数
如前所述,要使用 PHP 的内置文件函数获取响应标头,我们可以遍历 $http_response_header 变量;在我们执行 HTTP 请求后,PHP 将自动将响应标头作为此变量中的索引数组提供给我们。
用于使用文件函数发出 HTTP 请求的函数通常包括 file_get_contents、stream_context_create 和 stream_get_contents——如何使用它们在其他教程中介绍。
$http_response_header 数组中的第一个元素始终是 HTTP 状态代码——即使在读取原始标头时,状态代码也始终排在最前面。
将状态代码存储在单独的变量中可能很有用:
$status_message = array_shift($http_response_header);
array_shift 函数在这里有两个目的:
- 它返回数组中的第一个元素。
它从数组中删除第一个元素。
所有数字数组键也将相应更新,因此不会有任何丢失的键。
HTTP 标头由 key: value 对组成,但我们不能仅通过冒号 (:) 将它们转换为数组,因为标头值也可能包含冒号。
所以,我们可以做的,而不是求助于正则表达式,就是用字符串中的第一个冒号分割字符串,因为它总是在键名之后。
虽然使用正则表达式更容易,但使用 stripos 和 substr 的组合或者爆炸要快 10% 到 68%——这在实践中并不重要——
但无论如何,我认为我们应该坚持最快的方式。
以下所有方法实际上都非常易于使用,因此应该使用哪种方法最有效。
解决方案1:
使用explode函数比使用preg_match快68%:
$headers = array(); $status_message = array_shift($http_response_header); foreach ($http_response_header as $value) { if(false !== ($matches = explode(':', $value, 2))) { $headers["{$matches[0]}"] = trim($matches[1]); } }
解决方案2:
这是如何使用 stripos 和 substr ,它比使用正则表达式快 10% 左右:
$headers = array(); $status_message = array_shift($http_response_header); foreach ($http_response_header as $value) { $pos = stripos($value, ':'); $key = substr($value, 0, $pos); $value = substr($value, $pos+1); $headers["$key"] = trim($value); } print_r($headers);
解决方案3:
如果我们出于某种原因更喜欢使用正则表达式,请随意使用;对于大多数网站来说,速度差异是微不足道的——但请记住,我们获得的并发用户越多,即使是很小的优化,我们也会受益更多。
下面是如何使用 preg_match 来做同样的事情:
$headers = array(); $status_message = array_shift($http_response_header); foreach ($http_response_header as $value) { if (preg_match('/^([^:]+):([^\n]+)/', $value, $matches)) { $headers["{$matches[1]}"] = trim($matches[2]); } } print_r($headers);
当然我们也可以在使用正则表达式的时候去掉trim函数;我只是把它留在那里以获得公平的基准结果。
如何执行基准测试
每个测试都是通过解析标头 100 万次来执行的,如下所示:
$start_time = microtime(true); $repeat = 0; $status_message = array_shift($http_response_header); while ($repeat < 1000000) { $headers = array(); $status_message = array_shift($http_response_header); foreach ($http_response_header as $value) { if(false !== ($matches = explode(':', $value, 2))) { $headers["{$matches[0]}"] = trim($matches[1]); } } ++$repeat; } $end_time = microtime(true); echo $end_time - $start_time . "\n\n"; var_dump($headers);exit();