为菜鸟准备的HTTP头教程

原文链接:http://net.tutsplus.com/tutorials/other/http-headers-for-dummies/

作者:Burak Guzel

无论你是不是程序员,你看到它在网络上无所不在。这会儿你的浏览器地址栏就显示以”http://”开头的东西。甚至你的第一个hello world脚本就在你无意识下发送了HTTP头。本文中,我们会学习HTTP头的基础知识,以及我们如何在web应用中使用它们。

什么是HTTP头?

HTTP指“超文本传输协议”,整个万维网都是用此协议。它创立于1990年代初期。几乎所有你在浏览器中看到的内容都是通过HTTP传送到你的电脑。比如,但你打开本文页面,你的浏览器可能已经发送超过40个HTTP请求,并且为每一个请求接收了HTTP相应。

HTTP头是HTTP请求和相应的核心部分,它们携带关于客户端浏览器,被请求页面,服务器及其它信息。

示例

但你在地址栏输入一个URL,浏览器撒送一个HTTP请求,他看起来像这样子:

GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1
Host: net.tutsplus.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120
Pragma: no-cache
Cache-Control: no-cache

第一行是“请求行”,包含一些请求的基本信息。剩下的是HTTP头。

在这一请求之后,你的浏览器接收一个HTTP相应,看起来可能像这样子:

HTTP/1.x 200 OK
Transfer-Encoding: chunked
Date: Sat, 28 Nov 2009 04:36:25 GMT
Server: LiteSpeed
Connection: close
X-Powered-By: W3 Total Cache/0.8
Pragma: public
Expires: Sat, 28 Nov 2009 05:36:25 GMT
Etag: “pub1259380237;gz”
Cache-Control: max-age=3600, public
Content-Type: text/html; charset=UTF-8
Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT
X-Pingback: http://net.tutsplus.com/xmlrpc.php
Content-Encoding: gzip
Vary: Accept-Encoding, Cookie, User-Agent

<!– … rest of the html … –>
第一行是“状态行”,接下来是HTTP头,直至空白行。在此之后,“内容”开始(在此情况下是HTML输出)。

但你在浏览器中查看一个网页的源代码,你只会看到HTML部分而不是HTTP头,即使它们确实被一起传送,如你上面看到的一样。

这些HTTP请求也发送和接收其它东西,例如图像、CSS文件、JAVASCRTIP文件等等。因此我刚才说但你装载本文页面时你的浏览器已经发送了至少40个HTTP请求。

现在,让我们开始回顾更详细的结构。

如何查看HTTP头

我使用以下Firefox扩展区分析HTTP头:

Firebug

Live HTTP Headers

在PHP中:

  • getallheaders() 获取HTTP请求头。你也可以使用$_SERVER数组。
  • headers_list() 获取HTTP响应头。
    此外,在本文中,我们将看到一些PHP代码示例。

HTTP头第一行称为请求行,包含三个部分:

GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1

Host: net.tutsplus.com
而你仍然会从WEB服务器得到一个有效响应。

请求方法

最常用的三个请求方法是:GET, POST和HEAD,你可能已经从编写HTML表单中熟悉了前两个。

GET: 检索文档

只是用于检索HTML、图像、JavaScript、CSS等的主要方法。你浏览器中装载的不多数数据使用该方法请求。

比如,但载入一篇Nettuts+的文章,HTTP请求的第一行看起来像这样:

GET /tutorials/other/top-20-mysql-best-practices/ HTTP/1.1


一旦载入HTML,浏览器将开始发送图像的GET请求,可能看起来像这样:
GET /wp-content/themes/tuts_theme/images/header_bg_tall.png HTTP/1.1


WEB表单可被设置为使用GET方法,示例如下:
<form method=”GET” action=”foo.php”>

First Name: <input type=”text” name=”first_name”> <br />

Last Name: <input type=”text” name=”last_name”> <br />

<input type=”submit” name=”action” value=”Submit” />

</form>
当表单提交,HTTP请求会这样开始:
GET /foo.php?first_name=John&last_name=Doe&action=Submit HTTP/1.1


你会看到每个表单输入都被添加到查询字符串中。

POST: 向服务器发送数据

即使你可以使用GET和查询字符串向服务器发送数据,许多情况下,POST方法会更好。使用GET发送大量数据时不现实的,就有局限性。

POST请求通常由WEB表单发送。让我们修改上面的表单例子为POST方法:

<form method=”POST” action=”foo.php”>

First Name: <input type=”text” name=”first_name” /> <br />

Last Name: <input type=”text” name=”last_name” /> <br />

<input type=”submit” name=”action” value=”Submit” />

</form>
提交表单会创建如下HTTP请求:
POST /foo.php HTTP/1.1

Host: localhost

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8

Accept-Language: en-us,en;q=0.5

Accept-Encoding: gzip,deflate

Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7

Keep-Alive: 300

Connection: keep-alive

Referer: http://localhost/test.php

Content-Type: application/x-www-form-urlencoded

Content-Length: 43

first_name=John&last_name=Doe&action=Submit
这里要注意三个重要的事情:

  • 第一行的路径是很简单的/ foo.php,也没有查询字符串了。
  • Content-Type和Content-Lenght头被添加,提供有关被发送数据的信息。
  • 现在所有数据都在HTTP头之后发送,格式和查询字符串一样。
    POST方法请求也可以通过AJAX,以用程序,cURL,等等。所有文件上传表单都要求使用POST方法。

HEAD: 检索头信息

HEAD等同于GET,除了服务器不会在HTTP响应中返回内容。当你发送一个HEAD请求,则意味着你只对相应代码和HTTP头感兴趣,而不是文档本身。

当你发送一个HEAD请求,则意味着你只对相应代码和HTTP头感兴趣,而不是文档本身。

用该方法,浏览器可以检查文档是否被修改,为了缓存的目的。它也能检查文档是否存在。

例如,如果你的网站上有大量链接,你可以定期发送HEAD请求给所有链接已检查链接是否损坏。这会比使用GET工作快很多。

HTTP响应结构

发送HTTP请求之后,服务器用HTTP响应回应,除去内容,它看起来如下:

数据的第一部分是协议。在现代服务器中,通常又是使用HTTP/1.x或HTTP/1.1。

接下来的部分是状态代码接着简短的信息,代码200意味着我们的GET请求成功并且服务器会在HTTP头后返回被请求文档的内容。

我们都看过“404”页面,这个数据实际上来自于HTTP响应状态代码部分。如果GET请求一个服务器找不到的路径,则会返回404,而不是200。

响应的剩余部分和HTTP请求相似,这些值可以包含服务器软件、页面/文件最后修改时间、mime类型等信息。

HTTP状态代码

  • 200的用于成功的请求。
  • 300用于重定向。
  • 400用于有问题的请求。
  • 500用于服务器错误。
    200 OK

如前所述,这个状态代码在响应一个成功的请求中发送。

206 部分内容

如果应用程序只请求文件的范围,会返回206代码。

最常用于下载管理器,可以停止和恢复一个下载、或者把下载分割成片段。

404 未找到

当请求的页面或文件找不到,服务器会发送404响应代码。

401 未授权

密码保护的WEB页面会发送这个代码。如果你没有正确登录,你可能在浏览器中看到如下显示:

请注意这只用于HTTP密码保护的页面,那些弹出的登录想这个样子:

403 禁止

如果你不允许访问一个网页,此代码可能会发送到你的浏览器。这个常常发生在你尝试打开一个未包含index页面的目录的URL。如果服务器设置不允许显示目录内容,你会得到403错误。

比如,我在我本地服务器中创建了一个图片目录。在这个目录里,我建了个.htaccess文件,有这么一行:”Option -Indexes”。现在但我请求打开http://localhost/images/,我看到这个:

有其他的方式阻止访问,并发送403.比如,你可以通过一些htaccess指定的帮助用IP地址阻止。

order allow,deny

deny from 192.168.44.201

deny from 224.39.163.12

deny from 172.16.7.92

allow from all
302(或者307)临时移动 & 301 永久移动

这两个代码用来重定向浏览器。比如,当你使用URL缩短服务,如bit.ly,这正是他们转发人们点击他们链接的方式。

302和301都被浏览器类似处理,但他们对于搜索引擎爬虫有不同的意义。例如,如果你的网站正在维护,你可能用302重定向到其它位置。搜索引擎爬虫会继续在以后检查你的页面。但是如果你用301重定向,它会告诉爬虫你的网站已经永久移动到其它位置。为了给你一个更清晰的概念,http://www.nettuts.com重定向到http://net.tutsplus.com/,使用301代码而不是302。

500 服务器内部错误

这个代码通常在web脚本崩溃时看到。不想PHP,大多数CGI脚本不直接向浏览器输出错误。如果有任何致命错误,他们只是发送500状态代码。程序员需要搜索服务器错误日志去查找错误信息。

完整列表

你可以在这里找到HTTP状态代码及其解释的完整列表。

HTTP请求中的HTTP头

现在,我们会回顾一些在HTTP请求中发现的最普通的HTTP头。

在PHP中,几乎所有这些HTTP头都能在$_SERVER数组中找到。你也可以使用getallheaders()函数一次性检索所有的HTTP头。

Host

一个HTTP头被发送到一个特定的IP地址。但是既然大部分服务器有能力在一个IP下托管多个网站,他们必须知道浏览器查找哪个域名。

Host: net.tutsplus.com
基本上,这就是主机名,包括域名和子域名。

在PHP中,可以用$_SERVER[‘HTTP_HOST’] 或 $_SERVER[‘SERVER_NAME’] 得到。

User-Agent

User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko/20091102 Firefox/3.5.5 (.NET CLR 3.5.30729)
这个HTTP头可以携带如下几条信息:

  • 浏览器名称和版本。
  • 操作系统名称和版本。
  • 默认语言。
    因此,网站可以收集关于他们的浏览者的某些系统信息。比如,他们可以侦测浏览者是否使用手机浏览器并将他们重定向到他们网站的手机版本——在低分辨率下工作更好。

在PHP中,可以用$_SERVER[‘HTTP_USER_AGENT’] 得到。


if ( strstr($_SERVER[‘HTTP_USER_AGENT’],’MSIE 6’) ) {
echo “Please stop using IE6!”;
}

Accept-Language

Accept-Language: en-us,en;q=0.5
这个HTTP头显示用户设置的默认语言。如果网站有不同的语言版本,他可以用这个数据重定向浏览。

它可以携带多个语言,用逗号分隔。第一个是首选语言,其它每个语言可以带一个“q”值,这厮用户对该语言的偏好估计(最小0,最大1)。

在PHP中,可以用$_SERVER[“HTTP_ACCEPT_LANGUAGE”] 得到。


if (substr($_SERVER[‘HTTP_ACCEPT_LANGUAGE’], 0, 2) == ‘fr’) {
header(‘Location: http://french.mydomain.com‘);
}

Accept-Encoding

Accept-Encoding: gzip,deflate
大多数现代浏览器支持GZIP,并且会在HTTP头中发送这个。WEB服务器则会用压缩格式发送HTML输出。这最多将减少80%的大小,以节约带宽和时间。

在PHP中,可以用$_SERVER[“HTTP_ACCEPT_ENCODING”] 得到。无论如何,但你使用ob_gzhandler()回调函数,它会自动检查该值,所以你不需要自己干。


// enables output buffering
// and all output is compressed if the browser supports it
ob_start(‘ob_gzhandler’);

If-Modified-Since

如果一个web文档已经在你的浏览器中缓存,当你再次访问它,浏览器会发送下面HTTP头检查文档是否更新:

If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
如果在那个日期后没有更新,服务器会发送一个“304 未更新”响应代码,不带内容的,浏览器会从缓存中载入内容。

在PHP中,可以用$_SERVER[‘HTTP_IF_MODIFIED_SINCE’] 得到。


// assume $last_modify_time was the last the output was updated

// did the browser send If-Modified-Since header?
if(isset($_SERVER[‘HTTP_IF_MODIFIED_SINCE’])) {

// if the browser cache matches the modify time  
if ($last_modify_time == strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {  

    // send a 304 header, and no content  
    header("HTTP/1.1 304 Not Modified");  
    exit;  
}  

}

还有另一个叫做Etag的HTTP头,可用于确保缓存是最新的。稍后我们会谈到它。

Cookie

顾名思义,这个HTTP头为该域发送存储在你浏览器中的cookie。

Cookie: PHPSESSID=r2t5uvjq435r4q7ib3vtdjq120; foo=bar
这些键值对由分号分开,Cookie也可以包含会话ID。

在PHP中,独立的cookie可以用$_COOKIE数组访问。你可以用$_SESSION数组直接访问会话变量,如果你需要会话ID,你可以使用session_id()函数,而不是cookie。


echo $_COOKIE[‘foo’];
// output: bar
echo $_COOKIE[‘PHPSESSID’];
// output: r2t5uvjq435r4q7ib3vtdjq120
session_start();
echo session_id();
// output: r2t5uvjq435r4q7ib3vtdjq120

Referer

顾名思义,这个HTTP头包含引用网址。

比如,我访问Nettuts+主页,并点击一个文章链接,下面这个HTTP头会发送到我的浏览器:

Referer: http://net.tutsplus.com/
在PHP中,可以用$_SERVER[‘HTTP_REFERER’] 获得:


if (isset($_SERVER[‘HTTP_REFERER’])) {

$url_info = parse_url($_SERVER['HTTP_REFERER']);  

// is the surfer coming from Google?  
if ($url_info['host'] == 'www.google.com') {  

    parse_str($url_info['query'], $vars);  

    echo "You searched on Google for this keyword: ". $vars['q'];  

}  

}
// if the referring url was:
// http://www.google.com/search?source=ig&hl=en&rlz=&=&q=http+headers&aq=f&oq=&aqi=g-p1g9
// the output will be:
// You searched on Google for this keyword: http headers

你可能注意到了单词“referrer” 被误写为“referer”。不幸的是,这进入了正式的HTTP规范并被留用。

Authorization

当网页请求授权,浏览器会打开一个登录窗口。但你在窗口中输入用户名和密码,浏览器会发送另一个HTTP请求,这次它会包含这个HTTP头:

Authorization: Basic bXl1c2VyOm15cGFzcw==
在这个HTTP头中的数据是base64编码的,比如

base64_decode(‘bXl1c2VyOm15cGFzcw==’)会返回’myuser:mypass’

在PHP中,这些值可以用$_SERVER[‘PHP_AUTH_USER’] 和 $_SERVER[‘PHP_AUTH_PW’] 获得。

更多关于此的细节我们会在讨论WWW-Authenticate头时讨论。

HTTP响应中的HTTP头

现在,让我们看看HTTP响应中最常见的HTTP头。

在PHP中,你可以用header()函数设置响应头。PHP会自动发送特定的HTTP头,如加载内容和设置Cookie等。你可以用headers_list()函数查看已发送或将发送的HTTP头。你可以用headers_sent()函数检查头是否已经发送。

Cache-Control

w3.org的定义:“Cache-Control通用HTTP头用于指定所有请求/响应链中的缓存机制的指令”。这些“缓存机制”包括你的ISP可能使用的网关、代理。

比如:

Cache-Control: max-age=3600, public
“public”表示响应可以被任意缓存。“max-age”表示缓存有效时间为多少秒。允许你的网站被缓存可以减少服务器负载和带宽,提高浏览器载入时间。

也可以使用“no-cache”指令禁用缓存。

Cache-Control: no-cache
更多细节请参考w3.org

Content-Type

这个HTTP头表明文档的MIME类型。浏览器用以决定如何解释内容。比如,HTML页面(或者PHP脚本的HTML输出)可能如下面一般返回:

Content-Type: text/html; charset=UTF-8
“text”是文档类型,“html”是子类型。这个HTTP头也包含更多信息,比如编码。

对于gif图像,可能会发送这个:

Content-Type: image/gif
浏览器会决定使用该MIME类型的外部程序或浏览器扩展。比如,下面这个会打开Adobe Reader:
Content-Type: application/pdf
但直接载入时,apache通常会检测文档的MIME类型并发送相应的HTTP头。大多数浏览器也有一定的容错性和MIME自动检测能力,以免HTTP头错误或者缺失。

你可以在这里找到通用MIME类型的列表。

在PHP中,你可以使用finfo_file()函数检测文件的MIME类型。

Content-Disposition

这个HTTP头指示浏览器打开一个文件下载框,而非常识解析内容,如:

Content-Disposition: attachment; filename=”download.zip”
会导致浏览器这样工作:

请注意,相应的Content-Type也需要一起发送:

Content-Type: application/zip

Content-Disposition: attachment; filename=”download.zip”
Content-Length

当内容将被传送给浏览器的时候,服务器会用这个HTTP头标示大小(字节数)。

Content-Length: 89123
这对文件下载特别有用,浏览器可以因此判断下载的进度。

比如,这是我写的一个虚拟脚本,用于模拟一个慢下载。


// it’s a zip file
header(‘Content-Type: application/zip’);
// 1 million bytes (about 1megabyte)
header(‘Content-Length: 1000000’);
// load a download dialogue, and save it as download.zip
header(‘Content-Disposition: attachment; filename=”download.zip”‘);

// 1000 times 1000 bytes of data
for ($i = 0; $i < 1000; $i++) {
echo str_repeat(“.”,1000);

// sleep to slow down the download  
usleep(50000);  

}

结果会是:

现在,我注释掉Content-Length头。


// it’s a zip file
header(‘Content-Type: application/zip’);
// the browser won’t know the size
// header(‘Content-Length: 1000000’);
// load a download dialogue, and save it as download.zip
header(‘Content-Disposition: attachment; filename=”download.zip”‘);

// 1000 times 1000 bytes of data
for ($i = 0; $i < 1000; $i++) {
echo str_repeat(“.”,1000);

// sleep to slow down the download  
usleep(50000);  

}

现在的结果是:

浏览器只能告诉你已经下载多少字节,但是它不知道总数,所以进度条不会显示进度。

Etag

这是另一个用于缓存目的的HTTP头,看起来如下:

Etag: “pub1259380237;gz”
WEB服务器可能为每个文档发送这个HTTP头。其值可根据最后修改日期、文件大小、甚至文件的校验和值设定。浏览器会在缓存文档时保存该值。当浏览器下次请求同一个文件时,会在HTTP请求中发送下面这个HTTP头。
If-None-Match: “pub1259380237;gz”
如果文档的Etag值相符,服务器会发送304代码而不是200,并且没有内容,浏览器将从缓存中装载内容。

Last-Modified

顾名思义,这个HTTP以GMT格式表明民文档的最后修改日期。

Last-Modified: Sat, 28 Nov 2009 03:50:37 GMT
$modify_time = filemtime($file);

header(“Last-Modified: “ . gmdate(“D, d M Y H:i:s”, $modify_time) . “ GMT”);
它提供浏览器缓存文档的另一种方式。浏览器可以在HTTP请求中发送:
If-Modified-Since: Sat, 28 Nov 2009 06:38:19 GMT
我们已经在”If-Modified-Since”一节中对此讨论过。

Location

这个HTTP头用于重定向。如果响应代码是301或302,服务器必须也发送这个HTTP头。比如,但你访问http://www.nettuts.com,你的浏览器可能会收到:

HTTP/1.x 301 Moved Permanently

Location: http://net.tutsplus.com/


在PHP中,你可以如下重定向浏览:
header(‘Location: http://net.tutsplus.com/‘);
默认地,会发送302响应代码,如果你想发送301:
header(‘Location: http://net.tutsplus.com/‘, true, 301);
Set-Cookie

当网站想设置或者更新浏览器cookie,它会使用这个HTTP头。

Set-Cookie: skin=noskin; path=/; domain=.amazon.com; expires=Sun, 29-Nov-2009 21:42:28 GMT

Set-Cookie: session-id=120-7333518-8165026; path=/; domain=.amazon.com; expires=Sat Feb 27 08:00:00 2010 GMT
每个cookie都被独立HTTP头发送。请注意通过JavaScript设置的cookie不通过HTTP头。

在PHP中,你可以使用setcookie()函数设置cookie,PHP会发送相应的HTTP头。

setcookie(“TestCookie”, “foobar”);
会导致发送以下HTTP头:
Set-Cookie: TestCookie=foobar
如果截止日期没有指定,cookie会在浏览器关闭时删除。

WWW-Authenticate

网站可能用这个HTTP头通过HTTP认证用户。当浏览器看到这个HTTP头,它会打开一个登录对话窗口:

WWW-Authenticate: Basic realm=”Restricted Area”
如下所示:

在PHP指南中有个部分,有代码示范如何在PHP中实现:


if (!isset($_SERVER[‘PHP_AUTH_USER’])) {
header(‘WWW-Authenticate: Basic realm=”My Realm”‘);
header(‘HTTP/1.0 401 Unauthorized’);
echo ‘Text to send if user hits Cancel button’;
exit;
} else {
echo “

Hello {$_SERVER[‘PHP_AUTH_USER’]}.
“;
echo “

You entered {$_SERVER[‘PHP_AUTH_PW’]} as your password.
“;
}

Content-Encoding

这个HTTP头通常在内容被压缩时设置:

Content-Encoding: gzip
在PHP中,如果你使用ob_gzhandler()回调函数,它会自动设置。

结论

感谢阅读。我希望这篇文章是学习HTTP头的一个好的开端。请在下面留下你的评论和问题,我会尽量回复。