HTTP协议详解(一)

HTTP:Web的基础

1.1 HTTP概述


1.1.1 概述

http仔细给每种要通过Web传输的对象打上名为MIME类型的数据格式标签,当web浏览器从服务器取回一个对象时,通过MIME来决定如何处理对象。常见的MIME格式如下:

  • HTML 格式的文本文档由 text/html 类型来标记。
  • 普通的 ASCII 文本文档由 text/plain 类型来标记。
  • JPEG 格式的图片为 image/jpeg 类型。
  • GIF 格式的图片为 image/gif 类型。
  • Apple 的 QuickTime 电影为 video/quicktime 类型。
  • 微软的 PowerPoint 演示文件为 application/vnd.ms-powerpoint类型。

每个http请求都有一个方法,这个方法告诉服务器执行什么动作。有如下五种:

  • GET 从服务器向客户端发送命名资源。
  • PUT 将来自客户端的数据存储到一个命名的服务器资源中。
  • DELETE 删除服务器中的命名资源。
  • POST 将客户端的数据发送到一个服务器中提交数据。
  • HEAD 服务器返回资源的HTTP首部。

通过终端连接web服务器的命令:

192:github huangchengdu$ telnet www.joes-hardware.com 80
Trying 128.121.66.211...
Connected to joes-hardware.com.
Escape character is '^]'.
GET /tools.html HTTP/1.1
HOST:www.joes-hardware.com

HTTP/1.1 200 OK
Date: Sat, 08 Oct 2016 22:09:46 GMT
Server: Apache/2.2.22 (Unix) DAV/2 FrontPage/5.0.2.2635 mod_ssl/2.2.22 OpenSSL/1.0.1h
Last-Modified: Fri, 12 Jul 2002 07:50:17 GMT
ETag: "146deb7-1b1-3a58f649c4040"
Accept-Ranges: bytes
Content-Length: 433
Content-Type: text/html

<HTML>
<HEAD><TITLE>Joe's Tools</TITLE></HEAD>
<BODY>
<H1>Tools Page</H1>
<H2>Hammers</H2>
<P>Joe's Hardware Online has the largest selection of 
<A HREF="./hammers.html">hammers</A> on the earth.</P>
<H2><A NAME=drills></A>Drills</H2>
<P>Joe's Hardware has a complete line of cordless and corded drills,
as well as the latest in plutonium-powered atomic drills, for those
big around the house jobs.</P> ...
</BODY>
</HTML>

1.2 URL与资源


大多数 URL 方案的 URL 语法都建立在这个由 9 部分构成的通用格式上:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

几乎没有哪个 URL 中包含了所有这些组件。URL 最重要的 3 个部分是方案(scheme)、 主机(host)和路径(path)。表 2-1 对各种组件进行了总结。

img

URL的设计者们认识到有时人们可能会希望URL包含除通用的安全字母表之外的二进制数据或字符。因此,需要 有一种转义机制,能够将不安全的字符编码为安全字符,再进行传输。为了避开安全字符集表示法带来的限制,人们设计了一种编码机制,用来在URL中表示各种不安全的字符。这种编码机制就是通过一种“转义”表示法来表示不安全字符的,这种转义表示法包含一个百分号(%),后面跟着两个表示字符ASCII码的十六进制数。

1.3 HTTP报文


HTTP报文是简单的格式化数据块。每条报文都包含一条来自客户端的请求,或者一条来自服务器的响应。它们由三个部分组成:对报文 进行描述的起始行(start line)、包含属性的首部(header)块,以及可选的、包含 数据的主体(body)部分。

起始行和首部就是由行分隔的ASCII文本。每行都以一个由两个字符组成的行终止序列作为结束,其中包括一个回车符(ASCII 码 13)和一个换行符(ASCII 码 10)。 这个行终止序列可以写做 CRLF。需要指出的是,尽管HTTP规范中说明应该用CRLF来表示行终止,但稳健的应用程序也应该接受单个换行符作为行的终止。有些老的,或不完整的HTTP应用程序并不总是既发送回车符,又发送换行符。实体的主体或报文的主体(或者就称为主体)是一个可选的数据块。与起始行和首部不同的是,主体中可以包含文本或二进制数据,也可以为空。

1.3.1 状态码

100~199信息状态码,200~299成功状态吗,300~399重定向状态吗,400~499客户端错误状态码,500~599服务器错误状态码。

如果客户端在向服务器发送一个实体,并且愿意在发送实体之前等待100 Continue响应,那么,客户端就要发送一个携带了值为100Continue的Expect请求首部。如果客户端没有发送实体,就不应该发送100 Continue Expect首部,因为这样会使服务器误以为客户端要发送一个实体。从很多方面来看,100Continue都是一种优化。客户端应用程序只有在避免向服务器发送一个服务器无法处理或使用的大实体时,才应该使用 100 Continue。

200-299之间的状态码,表示客户端发起的请求是成功的。

img

300-399之间的状态码表示重定向,重定向状态码要么告知客户端使用替代位置来访问他们所感兴趣的资源,要么就提 供一个替代的响应而不是资源的内容。如果资源已被移动,可发送一个重定向状态 码和一个可选的 Location 首部来告知客户端资源已被移走,以及现在可以在哪里 找到它。

img img

400-499表示客户端发送了一个服务器无法处理的请求。

img img

500-599状态码表示发送了一个有效的请求,但是服务器发生错误。

img

1.3.2 首部

首部分为五种类型,通用首部,客户端和服务端都使用的首部,提供与报文相关的最基本的信息,如Date。请求首部是请求报文特有的,如Accept。响应首部,比如Server。实体首部,用于表明实体相关的信息,Content-Type表示实体主体的类型。扩展首部,开发者自己创建。

通用首部提供与豹纹相关的最基本信息,常用通用首部如下:

  • connection : 允许客户端和服务器指定与请求/响应链接有关的选项。
  • Date : 提供日期和时间标志,说明豹纹是什么时候创建的。
  • MIME-Version : 给出了发送端使用的MIME版本。
  • Transfer-Encoding : 告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
  • Update : 给出了发送端可能想要”升级”使用的新版本或者协议。
  • Via : 显示了报文经过的中间节点。
  • Cache-Control : 用于随报文传送缓存指使。
  • Pragma : 另一种随报文传送指示的方式,但并不专用于缓存。

请求首部是只在请求报文中有意义的首部。常用请求首部如下:

  • Host : 该出了接收请求的服务器的主机名和端口号。
  • Referer : 提供了包含当前请求URI的文档的URL。
  • User-Agent : 发起请求的应用程序名称告知服务器。
  • Accept : 告诉服务器能够发送那些媒体类型。
  • Accept-Charset : 告诉服务器能够发送那些字符集。
  • Accept-Encoding : 告诉服务器能够发送那些编码方式。
  • Accept-Language : 告诉服务器能够发送那些语言。
  • Expect : 允许客户端列出某请求所要求的服务器行为。
  • If-Match : 如果实体标记与文档当前的实体标记相匹配,就获取这份文档。
  • If-Modified-Since : 除非在某个指定的日期之后资源被修改过,否则就限制这个请求。
  • If-None-Match : 如果提供的实体标记与当前文档的实体标记不符合,就获取文档。
  • If-Range : 运行对文档的某个范围进行条件请求。
  • If-Unmodified-Since : 除非某个指定日期之后资源没有被修改过,否则就限制这个请求。
  • Range : 如果服务器支持范围请求,就请求资源的指定范围。
  • Authorization : 包含了客户端提供给服务器,以便对齐进行身份认证的数据。
  • Cookie : 客户端用它向服务器传送一个令牌,它并不是真正的安全首部,但确实隐含了安全功能。
  • Proxy-Authorization : 与Authorization首部相同,但这个首部是在与代理进行认证时使用的。
  • Proxy-Connection : 与Connection首部相同,但这个首部是在与代理建立连接时使用的。

响应报文有自己的响应首部集。响应首部为客户端提供了一些额外信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令。这些首部有助于客户 端处理响应,并在将来发起更好的请求。

  • Age : 响应持续时间。
  • Public : 服务器为其资源支持的请求方法列表。
  • Server : 服务器应用程序软件的名称和版本。
  • Accept-Ranges :对此资源来说,服务器可接受的范围类型。
  • Proxy-Authenticate : 来自代理的对客户端的质询列表。
  • Set-Cookie : 不是真正的安全首部,但隐含有安全功能;可以在客户端设置一个令牌,以便服务器对客户端进行标识。
  • WWW-Authenticate : 来自服务器的对客户端的质询列表。

实体首部可以用来描述HTTP报文的负荷。由于请求和响应报文中都可能包含实体部分,所以在这两种类型的报文中都可能出现这些首部。实体首部提供了有关实体及其内容的大量信息,从有关对象类型的信息,到能够对资源使用的各种有效的请求方法。总之,实体首部可以告知报文的接收者它在对什 么进行处理。

  • Location : 告知客户端实体实际上位于何处,用于将接收端定向到资源的位置上去。
  • Content-Base : 解析响应体中的相对URL时使用的基础URL。
  • Content-Encoding : 响应体的编码方式。
  • Content-Language : 理解响应体时最适宜使用的自然语言。
  • Content-Length : 响应体的长度或者尺寸。
  • Content-Location : 资源实际所处的位置。
  • Content-MD5 : 响应体的MD5校验和。
  • Content-Range : 在整个资源中此响应体表示的字节范围。
  • Content-Type : 响应体的对象类型。
  • Etag : 与此响应体相关的实体标记。
  • Expires : 响应体不再有效,要从原始的资源端再次获取此实体的日期和时间。
  • Last-Modified : 这个响应体最后一次呗修改的日期和时间。

1.4 链接管理


TCP连接是通过4个值来识别的,这4个值一起唯一地定义了一条连接。两条不同的TCP连接不能拥有4个完全相同 的地址组件值:

< 源IP地址、源端口号、目的IP地址、目的端口号 >

HTTP紧挨着TCP,位于其上层,所以HTTP事务的性能在很大程度上取决于底层TCP通道的性能。与建立TCP连接,以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。除非客户端或服务器超载,或正在处理复杂的动态资源,否则HTTP时延就是由TCP网络时延构成的。HTTP 事务的时延有以下几种主要原因:

  • 1客户端首先需要根据 URI 确定 Web 服务器的 IP 地址和端口号。如果最近没有对 URI 中的主机名进行访问,通过 DNS 解析系统将 URI 中的主机名转换成一个 IP 地址可能要花费数十秒的时间。
  • 2接下来,客户端会向服务器发送一条 TCP 连接请求,并等待服务器回送一个请求接受应答。每条新的TCP连接都会有连接建立时延。这个值通常最多只有一两秒钟,但如果有数百个 HTTP 事务的话,这个值会快速地叠加上去。
  • 3一旦连接建立起来了,客户端就会通过新建立的TCP管道来发送HTTP请求。数据到达时,Web服务器会从TCP连接中读取请求报文,并对请求进行处理。因特网传输请求报文,以及服务器处理请求报文都需要时间。
  • 4 然后,Web 服务器会回送 HTTP 响应,这也需要花费时间。

1.4.1 TCP链接性能

下面列出了一些会对 HTTP 程序员产生影响的、最常见的 TCP 相关时延,其中包括:

  • TCP 连接建立握手;
  • TCP慢启动拥塞控制;数据聚集的Nagle算法;
  • 用于捎带确认的TCP延迟确认算法;
  • TIME_WAIT时延和端口耗尽。

TCP连接握手需要经过以下几个步骤:

  • (1) 请求新的 TCP 连接时,客户端要向服务器发送一个小的 TCP 分组(通常是 40 ~ 60 个字节)。这个分组中设置了一个特殊的 SYN 标记,说明这是一个连接请求。
  • (2) 如果服务器接受了连接,就会对一些连接参数进行计算,并向客户端回送一个 TCP 分组,这个分组中的 SYN 和 ACK 标记都被置位,说明连接请求已被接受。
  • (3) 最后,客户端向服务器回送一条确认信息,通知它连接已成功建立(参见图 4-8c)。 现代的 TCP 栈都允许客户端在这个确认分组中发送数据。

由于因特网自身无法确保可靠的分组传输(因特网路由器超负荷的话,可以随意丢弃分组),所以TCP实现了自己的确认机制来确保数据的成功传输。每个 TCP 段都有一个序列号和数据完整性校验和。每个段的接收者收到完好的段 时,都会向发送者回送小的确认分组。如果发送者没有在指定的窗口时间内收到确 认信息,发送者就认为分组已被破坏或损毁,并重发数据。

TCP 数据传输的性能还取决于 TCP 连接的使用期(age)。TCP 连接会随着时间进行 自我“调谐”,起初会限制连接的最大速度,如果数据成功传输,会随着时间的推移 提高传输的速度。这种调谐被称为TCP慢启动(slow start),用于防止因特网的突 然过载和拥塞。

1.4.2 HTTP链接的处理

有几种现存和新兴的方法可以提高HTTP的连接性能。

  • 并行连接:通过多条TCP链接发起并发的HTTP请求。
  • 持久连接:重用TCP链接,以消除连接以及关闭延迟。
  • 管道化连接:通过共享的TCP连接发起并发的HTTP请求。

HTTP允许客户端打开多条连接,并行地执行多个HTTP事务。即使并行连接的速度可能会更快,但并不一定总是更快。客户端的网络带宽不足 (比如,浏览器是通过一个 28.8kbps 的 Modem 连接到因特网上去的)时,大部分的时间可能都是用来传送数据的。在这种情况下,一个连接到速度较快服务器上的HTTP事务就会很容易地耗尽所有可用的 Modem 带宽。如果并行加载多个对象,每 个对象都会去竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的性能提升就很小,甚至没什么提升。

Web客户端经常会打开到同一个站点的连接。比如,一个 Web 页面上的大部分内 嵌图片通常都来自同一个 Web 站点,而且相当一部分指向其他对象的超链通常都指 向同一个站点。允许 HTTP 设备在事务处理结束 之后将 TCP 连接保持在打开状态,以便为未来的 HTTP 请求重用现存的连接。在事 务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且, 已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。

持久连接与并行连接配合使用可能是最高效的方式。现在,很多Web应用程序都会打开少量的并行连接,其中的每一个都是持久连接。实现持久链接有HTTP/1.0+“keep-alive”连接,以及现代的 HTTP/1.1“persistent”连接。实现HTTP/1.0keep-alive连接的客户端可以通过包含Connection:Keep-Alive首部请求将一条连接保持在打开状态。如果响应中没有 Connection: Keep-Alive首部,客户端就认为服务器不支持keep-alive,会在发回响应报文之后关闭连接。

Keep-Alive连接的限制和规则:

  • 在 HTTP/1.0 中,keep-alive 并不是默认使用的。客户端必须发送一个 Connection: Keep-Alive 请求首部来激活 keep-alive 连接。
  • Connection: Keep-Alive 首部必须随所有希望保持持久连接的报文一起发送。 如果客户端没有发送 Connection: Keep-Alive 首部,服务器就会在那条请求 之后关闭连接。
  • 通过检测响应中是否包含Connection:Keep-Alive响应首部,客户端可以判 断服务器是否会在发出响应之后关闭连接。
  • 只有在无需检测到连接的关闭即可确定报文实体主体部分长度的情况下,才能 将连接保持在打开状态——也就是说实体的主体部分必须有正确的 Content- Length,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条 keep-alive 信道中回送错误的 Content-Length 是很糟糕的事,这样的话,事务 处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了。
  • 代理和网关必须执行Connection首部的规则。代理或网关必须在将报文转发出 去或将其高速缓存之前,删除在 Connection 首部中命名的所有首部字段以及 Connection 首部自身。
  • 严格来说,不应该与无法确定是否支持Connection首部的代理服务器建立 keep-alive 连接,以防止出现下面要介绍的哑代理问题。在实际应用中不是总能 做到这一点的。
  • 从技术上来讲,应该忽略所有来自HTTP/1.0设备的Connection首部字段(包 括Connection: Keep-Alive),因为它们可能是由比较老的代理服务器误转发 的。但实际上,尽管可能会有在老代理上挂起的危险,有些客户端和服务器还是 会违反这条规则。
  • 除非重复发送请求会产生其他一些副作用,否则如果在客户端收到完整的响应之 前连接就关闭了,客户端就一定要做好重试请求的准备。

HTTP/1.1 允许在持久连接上可选地使用请求管道。这是相对于 keep-alive 连接的又 一性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络 流向地球另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网 络条件下,这样做可以降低网络的环回时间,提高性能。对管道化连接有几条限制:

  • 如果HTTP客户端无法确认连接是持久的,就不应该使用管道。
  • 必须按照与请求相同的顺序回送HTTP响应。HTTP报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配起来了。
  • HTTP 客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。如果客户端打开了一条持久连接,并立即发出了 10 条请求, 服务器可能在只处理了,比方说,5 条请求之后关闭连接。剩下的 5 条请求会失败, 客户端必须能够应对这些过早关闭连接的情况,重新发出这些请求。
  • HTTP 客户端不应该用管道化的方式发送会产生副作用的请求(比如 POST)。总之,出错的时候,管道化方式会阻碍客户端了解服务器执行的是一系列管道化请 求中的哪一些。由于无法安全地重试 POST 这样的非幂等请求,所以出错时,就 存在某些方法永远不会被执行的风险。

总之,实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据(比 如关闭输出信道)之后,连接就会被完全关闭,而不会有重置的危险。

Loading Disqus comments...
Table of Contents