概述
HTTP协议全称为超文本传输协议(HyperText Transfer Protocol),它使得网络上的Web浏览器、服务器和相关的Web应用程序之间的通信成为可能。HTTP是现代全球因特网中使用的公共语言。
上图是表示了Web客户端和服务器通信的大致过程。客户端对HTTP发送一个获取资源的请求,服务器端返回该资源给客户端,并且附加一些额外信息,这有利于客户端采取合理的方式解析资源。在这一过程中,有几个问题需要考虑:
- 寻址:客户端是如何知道资源在哪个服务器的哪个存储区间的?
- 协议:客户端和服务器端是如何相互识别对方的信息的?
- 数据:数据是放在什么地方以什么方式传送的?
- 连接:客户端和服务器端之间是如何进行数据传输的?
以下内容分别对这几个问题进行探讨。
资源
这一部分回答第一个问题。
HTTP被称为因特网的多媒体信使。它可以传输图片、HTML页面、文本文件、MPEG电影、WAV音频文件等各种各样的资源。这些资源存储在服务器中,它们被某个唯一的记号标记,从而被全世界各地的客户端访问而不会发生错乱。这种唯一的标记方式就是URI。
URI
URI的全称为统一资源标识符(Uniform Resource Identifier),也就是说用来为网络资源标识某个唯一性的东西。标识的方式可以采取多种多样,URI主要采取两种,URL和URN,前者通过地址标识,后者通过名字标识。
URL
在现在的网络环境中,基本上可以说URL就是URI。URL全称为统一资源定位符。它可以明确说明如何从一个精确、固定的位置获取资源。
URL语法
大多数URL方案的URL语法都建立在如下9部分构成的通用格式上:
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
其中最终的3个部分为scheme(方案),host(主机),path(路径)。下表对各个组件进行了总结。
组件 | 描述 | 默认值 |
---|---|---|
方案 | 访问服务器以获取资源时要使用哪种协议 | 无 |
用户 | 某些方案访问资源时需要的用户名 | 匿名 |
密码 | 用户名后面要包含的密码,中间又冒号(:)分隔 | 无 |
主机 | 资源宿主服务器的主机名或点分IP地址 | 无 |
端口 | 资源宿主服务器正在监听的端口号。很多方案都有默认端口号(HTTP的默认端口号为80) | 每个方案特有 |
路径 | 服务器上资源的本地名,有一个斜杠(/)将其与前面的URL组件分隔开来。路径组件的语法是与服务器和方案有关的。 | 无 |
参数 | 某些方案会用这个组件来指定输入参数。参数为名/值对。URL中可以包含多个参数字段,它们相互之间以及与路径的奇遇部分之间用(;)分隔。 | 无 |
查询 | 某些方案会用这个组件传递参数以激活应用程序(比如数据库、公告板、搜索引擎以及其他因特网网关)。查询组件的内容没有通用格式。用字符“?”将其与URL的其余部分分隔开来。 | 无 |
片段 | 一小片或一部分资源的名字。引用对象时,不会将frag字段传送给服务器;这个字段是在客户端内部使用的。通过“#”将其与URL的其余部分分隔开来。 | 无 |
其中要特别说明的是参数和片段部分。
参数
参数为应用程序提供了访问资源所需的所有附加信息。HTTP URL的路径组件可以分成若干路径段。每段都可以有自己的参数。比如:
http://www.joes-hardware.com//hammers;sale=false/index.html;graphics=true
这个例子有两个路径段,hamaers和index.html。hammers路径段有参数sale,其值为false。index.html段有参数graphics,其值为true。
片段
片段是对资源级的再一次划分,使得我们在浏览如大型文本文档时可以分章节浏览。HTTP服务器通常处理整个对象,客户端不能将片段传送给服务器。浏览器从服务器获得了整个资源之后,会根据片段来显示你感兴趣的那部分资源。比如有下面URL:
http://www.joes-hardware.com/tools.html#drills
浏览器浏览该网页的过程如下图所示:
绝对URL和相对URL
上面的例子都是绝对URL,它们指定了一个完整的URL,根据这个URL就可以找到地址。相对URL只给出一个相对路径,然后根据再和基础URL的推导进行拼接组成。
例如,有资源名为./hammers.html,其基础URL为:
http://www.joes-hardware.com/tools.html
那么可以用这个URL作为基础,推导出其方案和主机名,然后拼接为一个新的绝对URL
http://www.joes-hardware.com/hammers.html
URN
URN是统一资源名(uniform resource name)的缩写,它使用资源名而不是位置来标记,因此无论资源移到何处,都能追踪到。只是现在还没怎么投入使用,或许将来发展的方向。因此,基本上我们所提到的URI也就等于URL,URL也就是URI。
报文
该部分回答了第二和第三个问题。
URL帮我们标识了某个资源的位置,使我们的通信有了目的地。然而就像打电话一样,双方必须基于都能理解的语言,否则便鸡同鸭讲了。HTTP协议的做法与此类似,它定义了一系列双方都能理解的格式,使得客户端和服务器的通信成为可能。这些格式体现在报文中。可以这样理解报文,它包括了双方传递的内容,以及对此内容的语法说明。比如一个说英语的和说中文的不能进行通信,但如果双方都有一部实时翻译的字典就可能了。HTTP的报文于此类似:
报文内容
报文只有请求报文和响应报文两种。请求报文从客户端流向服务器,响应报文从服务器流向客户端。两种报文都由三部分组成:对报文进行描述的起始行(start line)、包含属性的首部(header)块,以及可选的、包含数据的主体(body)部分。
而请求报文与响应报文之间又有所不同,下图是它们的结构对比:
注:由上图可知,起始行的各组件之间以空格分离。首部字段最后以一个空行与主体部分分隔。即使在没有主体部分的情况下,也需要空行。
下面分别对报文的各组件进行阐述。
起始行
请求报文和响应报文的起始行有所不同,下面先介绍二者不同之处,由于二者都拥有HTTP版本号,因此放在统一说明。
请求行
请求报文的起始行被称为请求行。请求行包含请求方法、请求URL以及HTTP版本号。这三个部分之间以空格分隔。
方法(method)
方法用来告知服务器要做些什么。我们比较通用的就是GET和POST方法了。下表描述了现在常用的7种方法。
方法 | 描述 | 是否包含主体 |
---|---|---|
GET | 从服务器获取一份文档 | 否 |
HEAD | 只从服务器获取文档的首部 | 否 |
POST | 向服务器发送需要处理的数据 | 是 |
PUT | 将请求的主体部分存储在服务器上 | 是 |
TRACE | 对可能经过代理服务器传送到服务器上去的报文进行追踪 | 否 |
OPTIONS | 决定可以在服务器上执行哪些方法 | 否 |
DELETE | 从服务器上删除一份文档 | 否 |
注:POST和PUT容易造成混淆,POST是用来向服务器发送数据,比如发送账号和密码到服务器。而PUT则是用于向服务器上的资源中存储数据。
请求URL
请求URL可以是绝对的URL,也可以是相对的URL(注:这样得在首部字段的Host字段指定主机名,之后详细解释)。
响应行
响应报文的起始行叫做响应行。它包含了HTTP版本号、状态码和原因语句。
状态码
状态码用来告诉客户端,发生了什么事情。客户端向服务器端发送请求时,由可能成功,也有可能失败,还有可能资源被移动了。状态码就是用来告诉客户端程序,发生了什么事情。
下表列出了状态码的分类:
整体范围 | 已定义范围 | 分类 |
---|---|---|
100 ~ 199 | 100 ~ 101 | 信息提示 |
200 ~ 299 | 200 ~ 206 | 成功 |
300 ~ 399 | 300 ~ 305 | 重定向 |
400 ~ 499 | 400 ~ 415 | 客户端错误 |
500 ~ 599 | 500 ~ 505 | 服务器错误 |
常见的状态码比如200表示成功、401表示未授权需要输入账号和密码、404表示没有找到URL对应的资源。
另外,也可以自定义扩展状态码,虽然HTTP没有定义,但当我们收到一个515的状态码时,我们可以立马发现这是服务器错误了,然后将之作为服务器错误的普通成员进行处理。
原因语句
原因语句紧随状态码的后面,状态码供程序解析,原因语句供人们阅读。比如200的原因语句是OK,404的原因语句是Not Found。这也告诉我们不要死记硬背状态码,记住大概范围,碰到的时候读原因语句和查阅相关资料即可。
版本号
HTTP版本号是请求行和响应行共同拥有的。旨在告诉对方,我的协议版本是什么,以便了解对方发的能力和报文格式。在与HTTP1.1的应用程序进行通信的HTTP1.2应用程序应该知道,它不能使用任何新的1.2特性,因为使用老版本协议的应用程序很可能无法实现这些特性。
首部字段
HTTP首部字段向请求报文和响应报文中添加了一些附加信息,从而可以使双方更好的理解对方的意图。首部以名/值对的形式出现,各首部字段之间以换行(CRLF,换行与回车)分隔。不考虑自定义扩展的话,首部字段大概可以分为如下四类:
首部类别 | 描述 |
---|---|
通用首部 | 既可以出现在请求报文中,也可以出现在响应报文中 |
请求首部 | 提供更多有关请求的信息 |
响应首部 | 提供更多有关响应的信息 |
实体首部 | 描述主体的长度和内容,或者资源自身 |
首部的字段是繁多的,想要完全即很难也无必要,记住首部的分类的框架可能更有益处。下面的思维图展示了首部分类。之后详细论述了各首部:
通用首部
首部 | 描述 |
---|---|
Connection | 允许客户端和服务器指定与请求/响应连接有关的选项 |
Date | 提供日期和时间标志,说明报文是什么时候创建的 |
MIME-Version | 给出了发送端使用的MIME版本 |
Trailer | 如果报文采用了分块传输编码(chunked transfer encoding)方式,就可以用这个首部列出位于报文拖挂(trailer)部分的首部集合。 |
Transfer-Encoding | 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式 |
Update | 给出了发送端可能想要“升级”使用的新版本或协议 |
Via | 显示了报文经过的中间节点(代理、网关) |
通用首部在请求报文和响应报文中都能使用。通用首部又可分为通用的信息性首部和通用缓存首部。
下表列出了通用的信息性首部:
首部 | 描述 |
---|---|
Connection | 允许客户端和服务器指定与请求/响应连接有关的选项 |
Date | 提供日期和时间标志,说明报文是什么时候创建的 |
MIME-Version | 给出了发送端使用的MIME版本 |
Trailer | 如果报文采用了分块传输编码(chunked transfer encoding)方式,就可以用这个首部列出位于报文拖挂(trailer)部分的首部集合。 |
Transfer-Encoding | 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式 |
Update | 给出了发送端可能想要“升级”使用的新版本或协议 |
Via | 显示了报文经过的中间节点(代理、网关) |
下表列出了通用的缓存首部:
首部 | 描述 |
---|---|
Cache-Control | 用于随报文传送缓存指示 |
Progma | 另一种随报文传送指示的方式,但并不专用于缓存 |
请求首部
请求首部是旨在请求报文中有意义的部分,服务器可以根据请求首部给出的客户端信息,试着为客户端提供更好的响应。比如,服务器端可以根据客户端是PC端还是移动端返回不同格式的HTML文档,从而使用户获得更好的阅读体验。请求首部大概可以分为如下四类,请求的通用性首部、Accept首部、条件请求首部和安全请求首部。由于条目众多,下面各表只列出了主要的首部,如有兴趣请查阅《HTTP权威指南》第72页。
下表为请求的信息性首部
首部 | 描述 |
---|---|
Host | 给出了接收请求的服务器的主机名和端口号 |
Referer | 提供了包含当前请求的URI的文档的URL |
下表为Accept首部
首部 | 描述 |
---|---|
Accept | 告诉服务器能够发送哪些媒体类型 |
Accept-Charset | 告诉服务器能够发送哪些字符集 |
Accept-Encoding | 告诉服务器能够发送哪些编码方式 |
Accept-Language | 告诉服务器能够发送哪些语言 |
下表为条件请求首部
首部 | 描述 |
---|---|
Expect | 允许客户端列出某请求所要求的服务器行为 |
If-Match | 如果实体标记与文档当前的实体标记相匹配,就获取这份文档 |
If-Modified-Since | 除非在某个指定的日期之后资源被修改过,否则就限制这个请求 |
If-None-Match | 如果提供的实体标记与当前的文档的实体标记不相符,就获取文档 |
If-Range | 允许对文档的某个范围进行条件请求 |
If-Unmodified-Since | 除非在某个指定日期之后资源没有被修改过,否则就限制这个请求 |
Range | 如果服务器支持范围请求,就请求资源的指定范围 |
下表为安全请求首部
首部 | 描述 |
---|---|
Authorization | 包含了客户端提供给服务器,以便对其自身进行认证的数据 |
Cookie | 客户端用它向服务器传送一个令牌 |
Cookie | 用来说明请求端支持的cookie版本。 |
响应首部
响应首部分为三部分,信息性首部、协商首部和安全响应首部。
下表为常用的响应的信息性首部:
首部 | 描述 |
---|---|
Retry-After | 如果资源不可用的话,在此日期或时间重试 |
Server | 服务器应用程序软件的名称和版本 |
下表为协商首部:
首部 | 描述 |
---|---|
Accept-Ranges | 对此资源来说,服务器可接受的范围类型 |
Vary | 服务器查看的其他首部的列表,可能会使响应发生变化;也就是说,这是一个首部列表,服务器会根据这些首部的内容挑选出最合适的资源版本发送给客户端 |
安全响应首部:
首部 | 描述 |
---|---|
Proxy-Authenticate | 来自代理的对客户端的质询列表 |
Set-Cookie | 不是真正的安全首部,但隐含有安全功能;可以在客户端设置一个令牌,以便服务器对客户端进行标识。 |
Set-Cookie2 | 与Set-Cookie类似 |
WWW-Authenticate | 来自服务器的对客户端的质询列表 |
实体首部
实体首部列出了大量关于传输主体(body)的信息。可分为三部分信息性首部、内容首部、缓存首部。
下表为实体的信息性首部:
首部 | 描述 |
---|---|
Allow | 列出了可以对此实体执行的请求方法 |
Location | 告知客户端实体实际上位于何处;用于将接收端定向到资源的(可能是新的)位置(URL)上去。 |
下表为内容首部:
首部 | 描述 |
---|---|
Content-Encoding | 对主体执行的任意编码方式 |
Content-Language | 理解主体时最适宜使用的自然语言 |
Content-Length | 主体的长度或尺寸 |
Content-Location | 资源实际所处的位置 |
Content-Range | 在整个资源中此实体表示的字节范围 |
Content-Type | 这个主体的对象类型 |
下表为实体缓存首部:
首部 | 描述 |
---|---|
ETag | 与此实体相关的实体标记 |
Expires | 实体不再有效,要从原始的源端再次获取此实体的日期和时间 |
Last-Modified | 这个实体最后一次呗修改的日期和时间 |
主体
主体放在HTTP报文的第三个部分。它是HTTP报文的负荷,是HTTP要传输的内容。
TCP连接
到目前为止,我们已经知道如何标识服务器端的资源(URL),也知道了,服务器和客户端是如何识别对方的信息(报文)。然而,虽然目的地和消息都有了,如果没有运输工具将消息运往目的地,那也是枉然。
HTTP报文是利用TCP协议来运输的。HTTP协议栈的如下图所示:
从图中可以看出,HTTP是位于TCP之上的协议。
分段传送
TCP协议是用来传输数据的,它首先会把HTTP报文中的数据划分为一块一块的小数据块,然后为各小数据块添加上端口号、数据排序等信息。TCP将数据交给IP端的时候,IP端为数据块添加源和目的地的IP地址、长度等信息。因此,TCP和IP层共同决定了连接的正确运行。TCP连接主要由下面四个值确定:
<源IP地址、源端口号、目的IP地址、目的端口号>
下图表示了,客户端发送一条请求到服务器接收的简单过程。服务器的响应与此类似,只是方向相反而已。
优化
通常情况下,HTTP的每一次请求和响应都会进行一次TCP/IP连接。通常有三种方式可以优化TCP/IP连接。
- 并行连接:通过多条TCP连接发起并发的HTTP请求。
- 持久连接:重用TCP连接,以消除连接及关闭时延。
- 管道化连接:通过共享的TCP连接发起并发的HTTP请求。
总结
本篇文章可以看做是对《HTTP权威指南》前四章的笔记。总得来说该部分详细解释了HTTP的基础。是一本值得阅读的书籍。