PHP Conference Japan 2024

get_headers

(PHP 5, PHP 7, PHP 8)

get_headers擷取伺服器為回應 HTTP 請求所傳送的所有標頭

描述

get_headers(字串 $url, 布林值 $associative = false, ?資源 $context = null): 陣列|false

get_headers() 會傳回一個陣列,其中包含伺服器為回應 HTTP 請求所傳送的標頭。

參數

url

目標 URL。

associative

如果選擇性的 associative 參數設定為 true,get_headers() 會剖析回應並設定陣列的索引鍵。

context

使用 stream_context_create() 建立的有效上下文資源,或 null 以使用預設上下文。

傳回值

傳回帶有標頭的索引或關聯陣列,失敗時傳回 false

變更記錄

版本 描述
8.0.0 associative 已從 int 變更為 布林值
7.1.0 新增了 context 參數。

範例

範例 #1 get_headers() 範例

<?php
$url
= 'http://www.example.com';

print_r(get_headers($url));

print_r(get_headers($url, true));
?>

上面的範例會輸出類似下列的內容

Array
(
    [0] => HTTP/1.1 200 OK
    [1] => Date: Sat, 29 May 2004 12:28:13 GMT
    [2] => Server: Apache/1.3.27 (Unix)  (Red-Hat/Linux)
    [3] => Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
    [4] => ETag: "3f80f-1b6-3e1cb03b"
    [5] => Accept-Ranges: bytes
    [6] => Content-Length: 438
    [7] => Connection: close
    [8] => Content-Type: text/html
)

Array
(
    [0] => HTTP/1.1 200 OK
    [Date] => Sat, 29 May 2004 12:28:14 GMT
    [Server] => Apache/1.3.27 (Unix)  (Red-Hat/Linux)
    [Last-Modified] => Wed, 08 Jan 2003 23:11:55 GMT
    [ETag] => "3f80f-1b6-3e1cb03b"
    [Accept-Ranges] => bytes
    [Content-Length] => 438
    [Connection] => close
    [Content-Type] => text/html
)

範例 #2 使用 HEAD 的 get_headers() 範例

<?php
// 預設情況下,get_headers 會使用 GET 請求來擷取標頭。如果您想要
// 改為傳送 HEAD 請求,可以使用串流上下文來執行:
$context = stream_context_create(
[
'http' => array(
'method' => 'HEAD'
)
]
);
$headers = get_headers('http://example.com', false, $context);
?>

另請參閱

新增註解

使用者貢獻的註解 18 則註解

nick at innovaweb dot co dot uk
14 年前
似乎有些人只想知道 3 位數的 HTTP 回應程式碼 - 這是一個快速而粗糙的解決方案

<?php
function get_http_response_code($theURL) {
$headers = get_headers($theURL);
return
substr($headers[0], 9, 3);
}
?>

是不是很簡單?回傳包含您要檢查回應程式碼的 URL 的函式,瞧!自訂重新導向,取代被封鎖的 is_file() 或 flie_exists() 函式 (就像我伺服器上似乎有的那樣),因此使用這種便宜的替代方案。但是,嘿,它有效!

Pudding
sey at sey dot prometheus-designs dot net
19 年前
aeontech at gmail dot com 更新的 get_headers 函式在 $format = 1 時,日期格式不正確。

取代
<?
else {
$headers[strtolower($h2[0])] = trim($h2[1]);
}
?>

使用
<?
else {
$foo = implode( ':', $h2 );
$foo = preg_replace( '/[a-zA-Z- ]*: /', '', $foo );
$headers[strtolower($h2[0])] = trim( $foo );
}
mcilva
6 年前
如何檢查 URL 是否指向有效的影片

<?php
function isVideo($url){
$url = get_headers($url,1);
if(
is_array($url['Content-Type'])){ //在某些回應中,Content-type 是一個陣列
$video = strpos($url['Content-Type'][1],'video');
}else{
$video = strpos($url['Content-Type'],'video');
}
if(
$video !== false)
return
true;

return
false;
}

?>
cees at cornelisdigitaal dot nl
9 年前
@Jim Greene

如果 URL 不存在,它會傳回不完整的標頭,導致子字串預設為亂碼。

亂碼的整數值永遠為 0。因此,您的低於 400 並不總是表示它存在!
Jim Greene
11 年前
我知道您不應該參考其他註解,但衷心感謝 Nick at Innovaweb 的評論,我基於他的想法新增了這部分

如果您使用該函式,它會傳回一個字串,如果您只檢查傳回 404 或 200 等等的檔案,那就太棒了。如果您將字串值轉換為整數,就可以對其執行數學比較。

例如

<?php
function get_http_response_code($theURL) {
$headers = get_headers($theURL);
return
substr($headers[0], 9, 3);
}

if(
intval(get_http_response_code('filename.jpg')) < 400){
// 檔案存在,萬歲!
}
?>

經驗法則是,如果回應小於 400,則該檔案存在,即使它未傳回 200。
php at hm2k dot org
14 年前
<?php
/**
* 擷取伺服器回應 HTTP 請求時發送的所有真實標頭,不包含重定向。
*
* @link https://php.dev.org.tw/function.get_headers
* @link http://bugs.php.net/bug.php?id=50719
*/

function get_real_headers($url,$format=0,$follow_redirect=0) {
if (!
$follow_redirect) {
// 設定新的預設選項
$opts = array('http' =>
array(
'max_redirects'=>1,'ignore_errors'=>1)
);
stream_context_get_default($opts);
}
// 取得標頭
$headers=get_headers($url,$format);
// 還原預設選項
if (isset($opts)) {
$opts = array('http' =>
array(
'max_redirects'=>20,'ignore_errors'=>0)
);
stream_context_get_default($opts);
}
// 回傳
return $headers;
}
?>
Kubo2
11 年前
如果你不想要在 get_headers() 函式失敗時顯示警告,你可以在它前面簡單地加上 @ 符號。

<?php

// 在失敗時,警告會被隱藏並回傳 false
$withoutWarning = @get_headers("http://www.some-domain.com");

// 在失敗時,會顯示警告並且也會回傳 false
$withWarning = get_headers("http://www.some-domain.com");

// bool(false)
var_dump($withoutWarning);
// bool(false)
var_dump($withWarning);
?>
Weboide
14 年前
請注意,get_headers **會追蹤重定向**(HTTP 重定向)。如果 $format=0,新的標頭將會附加到陣列中。如果 $format=1,每個多餘的標頭將會是一個包含多個值的陣列,每個重定向對應一個值。

例如

<?php
$url
= 'http://google.com';
var_dump(get_headers($url,0));
/*array(18) {
[0]=> string(30) "HTTP/1.0 301 Moved Permanently"
[1]=> string(32) "Location: http://www.google.com/"
[2]=> string(38) "Content-Type: text/html; charset=UTF-8"
[3]=> string(35) "Date: Sun, 26 Sep 2010 00:59:50 GMT"
[4]=> string(38) "Expires: Tue, 26 Oct 2010 00:59:50 GMT"
[5]=> string(38) "Cache-Control: public, max-age=2592000"
....
string(15) "HTTP/1.0 200 OK"
[10]=> string(35) "Date: Sun, 26 Sep 2010 00:59:51 GMT"
[11]=> string(11) "Expires: -1"
[12]=> string(33) "Cache-Control: private, max-age=0"
.....
}*/

/*===========================*/

var_dump(get_headers($url,1));
/*array(11) {
[0]=>
string(30) "HTTP/1.0 301 Moved Permanently"
["Location"]=> string(22) "http://www.google.com/"
["Content-Type"]=> array(2) {
[0]=> string(24) "text/html; charset=UTF-8"
[1]=> string(29) "text/html; charset=ISO-8859-1"
}
["Date"]=> array(2) {
[0]=> string(29) "Sun, 26 Sep 2010 01:03:39 GMT"
[1]=> string(29) "Sun, 26 Sep 2010 01:03:39 GMT"
}
["Expires"]=> array(2) {
[0]=> string(29) "Tue, 26 Oct 2010 01:03:39 GMT"
[1]=> string(2) "-1"
}
["Cache-Control"]=> array(2) {
[0]=> string(23) "public, max-age=2592000"
[1]=> string(18) "private, max-age=0"
}
.....
}*/
?>
info at marc-gutt dot de
16 年前
應該與原始的 get_headers() 相同

<?php
if (!function_exists('get_headers')) {
function
get_headers($url, $format=0) {
$headers = array();
$url = parse_url($url);
$host = isset($url['host']) ? $url['host'] : '';
$port = isset($url['port']) ? $url['port'] : 80;
$path = (isset($url['path']) ? $url['path'] : '/') . (isset($url['query']) ? '?' . $url['query'] : '');
$fp = fsockopen($host, $port, $errno, $errstr, 3);
if (
$fp)
{
$hdr = "GET $path HTTP/1.1\r\n";
$hdr .= "Host: $host \r\n";
$hdr .= "Connection: Close\r\n\r\n";
fwrite($fp, $hdr);
while (!
feof($fp) && $line = trim(fgets($fp, 1024)))
{
if (
$line == "\r\n") break;
list(
$key, $val) = explode(': ', $line, 2);
if (
$format)
if (
$val) $headers[$key] = $val;
else
$headers[] = $key;
else
$headers[] = $line;
}
fclose($fp);
return
$headers;
}
return
false;
}
}
?>
bunny at bunny dot hu
8 年前
如果 URL 被重定向,而且新的目標也被重定向,我們會在陣列中取得 Location。我們也會在數字索引的值中取得 HTTP 碼。

這裡有一個標頭的**部分**(不是全部),顯示使用這個重定向鏈(id=4 是最終頁面)時的樣子。
/test.php?id=1 -> /test.php?id=2 -> /test.php?id=3 -> /test.php?id=4

array
(
[0] => HTTP/1.1 302 Moved Temporarily

[Location] => Array
(
[0] => /test.php?id=2
[1] => /test.php?id=3
[2] => /test.php?id=4
)

[1] => HTTP/1.1 302 Moved Temporarily
[2] => HTTP/1.1 302 Moved Temporarily
[3] => HTTP/1.1 200 OK
)

在一般情況下,我們只需要最終頁面的資訊,所以這裡有一小段程式碼可以取得它

$result = array();
$header = get_headers($url, 1);
foreach ($header as $key=>$value) {
if (is_array($value)) {
$value = end($value);
}
$result[$key] = $value;
}
sidnash56 at gmail dot com
8 年前
為了檢查 URL 的有效性,這個方法對我來說運作良好

function url_valid(&$url) {
$file_headers = @get_headers($url);
if ($file_headers === false) return false; // 當找不到伺服器時
foreach($file_headers as $header) { // 解析所有標頭
// 當 301/302 重定向導向 200 時,修正 $url
if(preg_match("/^Location: (http.+)$/",$header,$m)) $url=$m[1];
// 取得最後一個 $header $code,以防重定向
if(preg_match("/^HTTP.+\s(\d\d\d)\s/",$header,$m)) $code=$m[1];
} // End foreach...
if($code==200) return true; // $code 200 == 一切正常
else return false; // 其他情況都失敗了,所以這一定是不良連結
} // End function url_exists
pegasus at vaultwiki dot org
9 年前
請注意,get_headers 不應該用於從使用者輸入收集的 URL。串流環境中的 timeout 選項只會影響串流中資料之間的閒置時間。它不會影響連線時間或請求的整體時間。

(不幸的是,timeout 選項的文件中沒有提到這一點,但已經在其他地方的許多程式碼討論中討論過,而且我已經做了自己的測試來確認這些討論的結論。)

因此,使用者很容易提供一個行為像 Slowloris 攻擊的 URL,讓您的 get_headers 函式只提供一個標頭,而且頻率足以避開串流逾時。

如果您要發布程式碼,即使是 `default_socket_timeout` 也無法保證能解決此問題,因為在許多較舊版本的 PHP 中,HTTPS 協定存在問題:https://bugs.php.net/bug.php?id=41631

由於 `get_headers` 接受使用者輸入,攻擊者可以輕易地使您的所有 PHP 子程序都處於忙碌狀態。

相反地,請使用 cURL 函數來獲取使用者提供的 URL 的標頭,並手動解析這些標頭,因為 `CURLOPT_TIMEOUT` 適用於整個請求。
stuart at sixletterwords dot com
19 年前
嘿,我幾週前遇到了這個問題,並在一個用於記錄公司擁有的網域名稱資訊的應用程式中使用了這個函數,發現它返回的狀態大多是錯誤的(對於明顯在線的網站,返回 400 錯誤請求或空值)。然後我深入研究,發現問題是它無法獲取具有重新導向的網站的正確資訊。但這不是全部問題,因為我伺服器上的所有內容都返回了錯誤的狀態。我在 php.net 上搜索其他資訊,發現 fsockopen 的範例效果更好,只需要一些調整。

這是我從中整理出來的函數以及一個小的變更。

<?php
if(!function_exists('get_headers')) {
function
get_headers($url,$format=0,$httpn=0){
$fp = fsockopen($url, 80, $errno, $errstr, 30);
if (
$fp) {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $url\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!
feof($fp)) {
$var.=fgets($fp, 1280);
}

$var=explode("<",$var);
$var=$var[0];
$var=explode("\n",$var);
fclose($fp);
return
$var;
}
}
}
?>

這會返回標頭的陣列(唯一的問題是,如果網站沒有正確的 HTML,它也會拉取一些內容)。

希望這對其他人有幫助。
Anonymous
18 年前
我注意到了。
如果您發送 'HEAD' 請求而不是 'GET' 請求,某些伺服器只會返回錯誤的回覆標頭。'GET' 請求標頭總是接收到最實際的 HTTP 標頭,而不是 'HEAD' 請求標頭。但是,如果您不介意使用快速但有風險的方法,那麼 'HEAD' 請求對您來說更好。

順便說一下... 這是具有其他資訊(例如使用者、密碼和來源)的 get header ...
<?php
function get_headers_x($url,$format=0, $user='', $pass='', $referer='') {
if (!empty(
$user)) {
$authentification = base64_encode($user.':'.$pass);
$authline = "Authorization: Basic $authentification\r\n";
}

if (!empty(
$referer)) {
$refererline = "Referer: $referer\r\n";
}

$url_info=parse_url($url);
$port = isset($url_info['port']) ? $url_info['port'] : 80;
$fp=fsockopen($url_info['host'], $port, $errno, $errstr, 30);
if(
$fp) {
$head = "GET ".@$url_info['path']."?".@$url_info['query']." HTTP/1.0\r\n";
if (!empty(
$url_info['port'])) {
$head .= "Host: ".@$url_info['host'].":".$url_info['port']."\r\n";
} else {
$head .= "Host: ".@$url_info['host']."\r\n";
}
$head .= "Connection: Close\r\n";
$head .= "Accept: */*\r\n";
$head .= $refererline;
$head .= $authline;
$head .= "\r\n";

fputs($fp, $head);
while(!
feof($fp) or ($eoheader==true)) {
if(
$header=fgets($fp, 1024)) {
if (
$header == "\r\n") {
$eoheader = true;
break;
} else {
$header = trim($header);
}

if(
$format == 1) {
$key = array_shift(explode(':',$header));
if(
$key == $header) {
$headers[] = $header;
} else {
$headers[$key]=substr($header,strlen($key)+2);
}
unset(
$key);
} else {
$headers[] = $header;
}
}
}
return
$headers;

} else {
return
false;
}
}
?>

此致。
Donovan
drfickle2 at yahoo dot com
19 年前
aeontech,以下變更增加了對 SSL 連線的支援。感謝您的程式碼!

if (isset($url_info['scheme']) && $url_info['scheme'] == 'https') {
$port = 443;
$fp=fsockopen('ssl://'.$url_info['host'], $port, $errno, $errstr, 30);
} else {
$port = isset($url_info['port']) ? $url_info['port'] : 80;
$fp=fsockopen($url_info['host'], $port, $errno, $errstr, 30);
}
Backslider
12 年前
應該注意的是,此函數(和其他函數)在失敗時,不是返回 "false",而是返回一個巨大的警告,如果您沒有關閉錯誤報告/警告,這會使您的腳本停止執行。

這太瘋狂了!任何像獲取 URL 的函數,如果 URL 因為格式不正確以外的任何原因失敗,都應該只返回 false,而不會出現警告。
php dot sirlancelot at spamgourmet dot com
16 年前
我試圖盡可能地複製原生行為,以便用於沒有 `get_headers()` 函數的系統。這是程式碼:
<?php
if (!function_exists('get_headers')) {
function
get_headers($Url, $Format= 0, $Depth= 0) {
if (
$Depth > 5) return;
$Parts = parse_url($Url);
if (!
array_key_exists('path', $Parts)) $Parts['path'] = '/';
if (!
array_key_exists('port', $Parts)) $Parts['port'] = 80;
if (!
array_key_exists('scheme', $Parts)) $Parts['scheme'] = 'http';

$Return = array();
$fp = fsockopen($Parts['host'], $Parts['port'], $errno, $errstr, 30);
if (
$fp) {
$Out = 'GET '.$Parts['path'].(isset($Parts['query']) ? '?'.@$Parts['query'] : '')." HTTP/1.1\r\n".
'Host: '.$Parts['host'].($Parts['port'] != 80 ? ':'.$Parts['port'] : '')."\r\n".
'Connection: Close'."\r\n";
fwrite($fp, $Out."\r\n");
$Redirect = false; $RedirectUrl = '';
while (!
feof($fp) && $InLine = fgets($fp, 1280)) {
if (
$InLine == "\r\n") break;
$InLine = rtrim($InLine);

list(
$Key, $Value) = explode(': ', $InLine, 2);
if (
$Key == $InLine) {
if (
$Format == 1)
$Return[$Depth] = $InLine;
else
$Return[] = $InLine;

if (
strpos($InLine, 'Moved') > 0) $Redirect = true;
} else {
if (
$Key == 'Location') $RedirectUrl = $Value;
if (
$Format == 1)
$Return[$Key] = $Value;
else
$Return[] = $Key.': '.$Value;
}
}
fclose($fp);
if (
$Redirect && !empty($RedirectUrl)) {
$NewParts = parse_url($RedirectUrl);
if (!
array_key_exists('host', $NewParts)) $RedirectUrl = $Parts['host'].$RedirectUrl;
if (!
array_key_exists('scheme', $NewParts)) $RedirectUrl = $Parts['scheme'].'://'.$RedirectUrl;
$RedirectHeaders = get_headers($RedirectUrl, $Format, $Depth+1);
if (
$RedirectHeaders) $Return = array_merge_recursive($Return, $RedirectHeaders);
}
return
$Return;
}
return
false;
}}
?>
該函式將處理最多五次的重新導向。
請享用!
rgawenda at gmail dot com
2 個月前
有些筆記使用 substr 來取得 HTTP/1.X 後第一個 ([0]) 標頭中的回應「代碼」(取決於 PHP 版本),但 HTTP/2 存在已將近十年,而 HTTP/3 現在已被廣泛支援,因此根據規範,一個更好、更具未來性的解析方法來提取該代碼是:

<?php
$headers
= get_headers($url, true);
$response_code = explode(" ", $headers[0])[1];
?>

此外,如果您沒有使用 stream_context_create 封鎖重新導向,請考慮到 $headers[0] 是所請求 URL 的回應代碼,而下一個已解析且追蹤的重新導向則在 [1],因此您也應該先查詢正確的(最後一個)回應標頭。

<?php
$response_header
= $headers[max(array_filter(array_keys($headers), 'is_int'))];
?>
To Top