HTTP 1.1学习笔记

前言

由于HTTP 1自身的局限性,它不能很好的为用户提供性能良好的WEB服务。于1999年6月正式发布了HTTP1.1标准REC2616,它厘清了之前版本中很多有歧义的地方,而且还新增了很多重要的优化,如持久连接、分块编码传输、状态码扩充、增强的缓存机制、传输编码及请求管道等。本文是个人在学习《WEB性能权威指南》后,又查阅了一些文档资料写的一篇随笔,仅供参考和个人以后查阅。下面将对比http1.0讲述一些在新版本中的重要改进。(本文最初发布于公司内网,外网原文地址:腾云阁--HTTP 1.1学习笔记)

1.持久连接

每个TCP连接在建立初期都需要进行三次握手,需要经历一次客户端与服务器间的完整往返,如果进行数据传输的话,至少还需要引发另外一次往返。再加上服务器端的处理请求的时间,就是可以得到每次请求的总时间。如果每次发送请求都是由新建的TCP连接发送的话,它至少需要两次完整的网络往返时间,因为每个TCP连接的建立都需要重新进行三次握手。

那么,能不能对TCP连接进行重用呢?

答案无疑是肯定的,HTTP1.1里添加了持久连接的支持,再次发送请求时,可以直接使用上次已经建立完成的TCP连接,这样就避免了第二次TCP连接时的三次握手、消除另一次TCP慢启动的往返,极大了减小了网络延时。如果重用CTP连接发送HTTP请求的次数越多,带来的性能提升越可观,因为在第一次经过三次握手建立连接之后,无需再花费多余的时间再建立连接。

注:目前,所有现代浏览器都尝试持久化HTTP连接,如果服务器支持的话。使用HTTP1.1的话,默认的就是持久连接;如果使用HTTP 1.0,可以明确使用connection:keep-alive首部声明使用持久连接;还应注意一些HTTP库和框架的默认行为,它们有时候并不是默认使用持久连接的。

2.HTTP管道

持久HTTP可以让我们重用已有的连接来完成多次请求,但这些请求需要满足先进先出的队列顺序:发送请求--等待响应,再发送下一个请求。HTTP/1.1允许多个http请求通过一个套接字同时被输出 ,而不用等待相应的响应。然后请求者就会等待各自的响应,这些响应是按照之前请求的顺序依次到达。(所有请求保持一个FIFO的队列,一个请求发送完之后,不必等待这个请求的响应被接受到,下一个请求就可以被再次发出;同时,服务器端返回这些请求的响应时也是按照FIFO的顺序)。

在高延迟和多请求的场景下,通过HTTP管道进行数据传输会有更大的性能提升,会节省更多的时间。经过仔细的观察,可以发现在HTTP1.1中存在一些局限,它严格串行的返回响应,它不允许多个数据交错到达(多路复用),只能等待一个响应完全返回后,下一个响应才能发送,无论下一个响应是否早于前一个响应完成处理,这也叫做队首阻塞。这就带来一个非常糟糕的体验,如果第一个请求需要处理的时间非常长,那么后续的请求即使被服务器已经处理完成,响应也不能立即返回,而是存储在服务端的缓存区中,等待第一个响应的完成,才能按照FIFO顺序返回。

由于TCP严格按照按顺序交付,丢失一个TCP分组就会阻塞所有高序号的分组,除非重传丢失的分组,这也会带来额外的延迟。由于HTTP1.1中不允许多路复用,HTTP管道也会带来一些不容忽视的问题:

  • 一个慢响应会阻塞所有后续请求;

  • 并行处理请求时,服务器需要缓存处理结果,会占用服务器资源,如果某个响应很大,很容易形成服务器的受攻击面;

  • 响应失败可能终止TCP连接,会强迫客户端重新发送对后续资源的请求,导致重复处理;

  • 网络环境中存在中间代理,检测管道兼容性十分必要;

  • 如果中间代理不支持管道,那它可能中断连接,也可能把所有请求串联起来。

正是由于存在这样或那样的问题,HTTP管道技术的应用比较有限,并没有大面积推广开来,即使一些支持它的浏览器也仅仅把它作为一个高级选项。如果你对客户端和服务端都有很强的控制力,依然可以使用它,会带来不错的性能提升。如果要在采用这种技术,你需要注意以下事项:

  • 确保HTTP客户端支持管道;

  • 确保HTTP服务器支持管道;

  • 应用必须处理中断的连接并恢复;

  • 应用必须处理中断的请求的幂等问题;

  • 应用必须保持自身不受出问题的代理的影响。

实践中部署HTTP管道的最佳途径,就是在客户端和服务端使用HTTPS,这样就可以免除不支持管道的中间代理的干扰。

3.增强的缓存机制

在HTTP/1.0中,使用Expire头域来判断资源的fresh或stale,并使用条件请求(conditional request)来判断资源是否仍有效。
相关字段:

  • Date: 服务器响应的时间;

  • Expires: 资源过期时间;

  • Last-Modified: 资源最后修改时间;

  • If-Modified-Since: 用来验证资源是否过期;

大致策略为:

如果Expires设置的时间在Date之后,则浏览器在Expires标记的时间之前都不会访问服务器了,而是使用浏览器缓存;如果Expires设置的时间在Date之前,或者浏览器时间已经在Expires之后,那么再次访问资源时, 浏览器就要向服务器发送请求,但不是重新拉取数据,而是询问服务器该资源是否过期,方法时,把上次response中Last-Modified的时间作为If-Modified-Since的时间,发送请求,服务器对比该时间和资源目前的更改时间,如果未更改,则返回304,否则传输新文件。此外,HTTP/1.0中还定义了Pragma:no-cache头域,客户端使用该头域说明请求资源不能从cache中获取,而必须回源获取。

HTTP/1.0中,If-Modified-Since头域使用的是绝对时间戳,精确到秒,但使用绝对时间会带来不同机器上的时钟同步问题,文档的 更新周期小于1s, 都会出现问题。

为了提供更好的缓存机制,HTTP1.1对以往的机制进行了一个渐进性的改进。HTTP/1.1提倡的缓存机制是,对比文档的hash值,文档内容变,则hash变,用相对时间代替绝对时间,这样就可以解决使用绝对时间戳带来一些问题。

HTTP/1.1 继承 HTTP/1.0 所以HTTP/1.0的相关字段仍然有效,保留的这些字段就是为了兼容那些仅支持HTTP/1.0的客户端。 HTTP/1.1服务器不应该设置与1.0矛盾的过期策略, 1.1的服务器在没有文档hash值时,也可以使用If-Modified-Since进行判断文档过期。
HTTP1.1中新增了以下字段:

  • Cache-Control: 用来控制浏览器的缓存行为;

  • ETag: 文档的Hash值;

  • If-None-Match: 用来验证资源是否过期,即文档Hash值是否变化。

而HTTP/1.1中引入了一个ETag头域用于重激活机制,它的值ETag可以用来唯一的描述一个资源。请求消息中可以使用If-None-Match头域来匹配资源的entitytag是否有变化。

为了使caching机制更加灵活,HTTP/1.1增加了Cache-Control头域(请求消息和响应消息都可使用),它支持一个可扩展的指令子集:例如max-age指令支持相对时间戳;private和no-store指令禁止对象被缓存;no-transform阻止Proxy进行任何改变响应的行为;no-cache浏览器缓存,但是认为是过期缓存;no-store浏览器不缓存;max-age:缓存有效时间段等等。

如果想要浏览器每次发送请求,还启用缓存,那就使用Cache-Control: no-cache, 每次访问图片,浏览器都会去验证Etag。

Cache使用关键字索引在磁盘中缓存的对象,在HTTP/1.0中使用资源的URL作为关键字。但可能存在不同的资源基于同一个URL的情况,要区别它们还需要客户端提供更多的信息,如Accept-Language和Accept-Charset头域。为了支持这种内容协商机制(content negotiation mechanism),HTTP/1.1在响应消息中引入了Vary头域,该头域列出了请求消息中需要包含哪些头域用于内容协商。

4.分块编码传输

HTTP消息中可以包含任意长度的实体,通常它们使用Content-Length来给出消息结束标志。但是,对于很多动态产生的响应,只能通过缓冲完整的消息来判断消息的大小,但这样做会加大延迟。如果不使用长连接,还可以通过连接关闭的信号来判定一个消息的结束。

HTTP/1.1中引入了Chunke dtransfer-coding来解决上面这个问题,发送方将消息分割成若干个任意大小的数据块,每个数据块在发送时都会附上块的长度,最后用一个零长度的块作为消息结束的标志。这种方法允许发送方只缓冲消息的一个片段,避免缓冲整个消息带来的过载。

在HTTP/1.0中,有一个Content-MD5的头域,要计算这个头域需要发送方缓冲完整个消息后才能进行。而HTTP/1.1中,采用chunked分块传递的消息在最后一个块(零长度)结束之后会再传递一个拖尾(trailer),它包含一个或多个头域,这些头域是发送方在传递完所有块之后再计算出值的。发送方会在消息中包含一个Trailer头域告诉接收方这个拖尾的存在。

5.状态码扩充

状态码是试图理解和满足请求的三位数字的整数码。HTTP/1.0中只定义了16个状态响应码,对错误或警告的提示不够具体。HTTP/1.1引入了一个Warning头域,增加对错误或警告信息的描述。

截止到HTTP1.1中包含的状态码大致如下:

状态代码 状态信息 含义

消息(1**)

100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分。(HTTP 1.1新)
101 Switching Protocols 服务器将遵从客户的请求转换到另外一种协议(HTTP 1.1新)

成功(2**)

200 OK 一切正常,对GET和POST请求的应答文档跟在后面。
201 Created 服务器已经创建了文档,Location头给出了它的URL。
202 Accepted 已经接受请求,但处理尚未完成。
203 Non-Authoritative Information 文档已经正常地返回,但一些应答头可能不正确,因为使用的是文档的拷贝(HTTP 1.1新)。
204 No Content 没有新文档,浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
205 Reset Content 没有新的内容,但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容(HTTP 1.1新)。
206 Partial Content 客户发送了一个带有Range头的GET请求,服务器完成了它(HTTP 1.1新)。

重定向(3**)

300 Multiple Choices 客户请求的文档可以在多个位置找到,这些位置已经在返回的文档内列出。如果服务器要提出优先选择,则应该在Location应答头指明。
301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL。
302 Found 类似于301,但新的URL应该被视为临时性的替代,而不是永久性的。注意,在HTTP1.0中对应的状态信息是“Moved Temporatily”。
出现该状态代码时,浏览器能够自动访问新的URL,因此它是一个很有用的状态代码。

注意这个状态代码有时候可以和301替换使用。例如,如果浏览器错误地请求 http://host/~user (缺少了后面的斜杠), 有的服务器返回301,有的则返回302。

严格地说,我们只能假定只有当原来的请求是GET时浏览器才会自动重定向。请参见307。
303 See Other 类似于301/302,不同之处在于,如果原来的请求是POST,Location头指定的重定向目标文档应该通过GET提取(HTTP 1.1新)。
304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 Use Proxy 客户请求的文档应该通过Location头所指明的代理服务器提取(HTTP 1.1新)。
307 Temporary Redirect 和302(Found)相同。许多浏览器会错误地响应302应答进行重定向,即使原来的请求是POST,即使它实际上只能在POST请求的应答是303时 才能重定向。由于这个原因,HTTP 1.1新增了307,以便更加清除地区分几个状态代码:当出现303应答时,浏览器可以跟随重定向的GET和POST请求;如果是307应答,则浏览器只能跟随对GET请求的重定向。(HTTP 1.1新)

请求错误(4**)

400 Bad Request 请求出现语法错误。
401 Unauthorized 客户试图未经授权访问受密码保护的页面。应答中会包含一个WWW-Authenticate头,浏览器据此显示用户名字/密码对话框,然后在填写合适的Authorization头后再次发出请求。
403 Forbidden 资源不可用。服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致。
404 Not Found 无法找到指定位置的资源。这也是一个常用的应答。
405 Method Not Allowed 请求方法(GET、POST、HEAD、DELETE、PUT、TRACE等)对指定的资源不适用。(HTTP 1.1新)
406 Not Acceptable 指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容(HTTP 1.1新)。
407 Proxy Authentication Required 类似于401,表示客户必须先经过代理服务器的授权。(HTTP 1.1新)
408 Request Timeout 在服务器许可的等待时间内,客户一直没有发出任何请求。客户可以在以后重复同一请求。(HTTP 1.1新)
409 Conflict 通常和PUT请求有关。由于请求和资源的当前状态相冲突,因此请求不能成功。(HTTP 1.1新)
410 Gone 所请求的文档已经不再可用,而且服务器不知道应该重定向到哪一个地址。它和404的不同在于,返回407表示文档永久地离开了指定的位置,而404表示由于未知的原因文档不可用。(HTTP 1.1新)
411 Length Required 服务器不能处理请求,除非客户发送一个Content-Length头。(HTTP 1.1新)
412 Precondition Failed 请求头中指定的一些前提条件失败(HTTP 1.1新)。
413 Request Entity Too Large 目标文档的大小超过服务器当前愿意处理的大小。如果服务器认为自己能够稍后再处理该请求,则应该提供一个Retry-After头(HTTP 1.1新)。
414 Request URI Too Long URI太长(HTTP 1.1新)。
416 Requested Range Not Satisfiable 服务器不能满足客户在请求中指定的Range头。(HTTP 1.1新)

服务器错误(5**)

500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。
501 Not Implemented 服务器不支持实现请求所需要的功能。例如,客户发出了一个服务器不支持的PUT请求。
502 Bad Gateway 服务器作为网关或者代理时,为了完成请求访问下一个服务器,但该服务器返回了非法的应答。
503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个Retry-After头。
504 Gateway Timeout 由作为代理或网关的服务器使用,表示不能及时地从远程服务器获得应答。(HTTP 1.1新)
505 HTTP Version Not Supported 服务器不支持请求中所指明的HTTP版本。(HTTP 1.1新)

6.Host头域

在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。由于HTTP 1.0不支持Host请求头字段,WEB浏览器无法使用主机头名来明确表示要访问服务器上的哪个WEB站点,这样就无法使用WEB服务器在同一个IP地址和端口号上配置多个虚拟WEB站点。在HTTP 1.1中增加Host请求头字段后,WEB浏览器可以使用主机头名来明确表示要访问服务器上的哪个WEB站点,这才实现了在一台WEB服务器上可以在同一个IP地址和端口号上使用不同的主机名来创建多个虚拟WEB站点。

7.请求方式新增

客户程序向服务器发送的请求可以有不同的类型,这样服务器可以根据不同的请求类型进行不同的处理。在HTTP1.0中,定义了三种最基本的请求类 型,GET、POST和HEAD,当使用GET和POST方法时,服务器最后都将结果文档返回给客户程序,浏览器将刷新显示;而HEAD请求则不同,HEAD请求在客户程序和服务器之间进行交流,而不会返回具体的文档,它仅仅交流一些内部数据,这些数据不会影响浏览的过程。因此HEAD方法通常不单独使用,而是和其他的请求方法一起起到辅助作用。一些搜寻引擎使用的自动搜索机器人使用这个方法来获得网页的标志信息,或者进行安全认证时,使用这个方法来传递认证信息。

除了上述三种最常见的访问方法之外,在HTTP1.1中还定义了更多的访问方法类型,具体如下:

  • PUT:向指定资源位置上传其最新内容;

  • DELETE:请求服务器删除Request-URI所标识的资源;

  • OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性;

  • TRACE:回显服务器收到的请求,主要用于测试或诊断;

  • CONTENT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

这些方法并不常用,因而大部分Web服务器软件并没有实现他们。然而对于特定场合他们还是非常有用的,如果服务器不支持客户发送的请求方法,服务器将返回错误并立即关闭连接。

8.带宽优化

HTTP/1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,又比如下载大文件时需要支持断点续传功能,而不是在发生断连后不得不重新下载完整的包。HTTP/1.1中在请求消息中引入了range头域,它允许只请求资源的某个部分。在响应消息中Content-Range头域声明了返回的这部分对象的偏移值和长度。如果服务器相应地返回了对象所请求范围的内容,则响应码为206(Partial Content),它可以防止Cache将响应误以为是完整的一个对象。

另外一种情况是请求消息中如果包含比较大的实体内容,但不确定服务器是否能够接收该请求(如是否有权限),此时若贸然发出带实体的请求,如果被拒绝也会浪费带宽。HTTP/1.1加入了一个新的状态码100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为权限拒绝了请求,就回送响应码401(Unauthorized);如果服务器接收此请求就回送响应码100,客户端就可以继续发送带实体的完整请求了。注意,HTTP/1.0的客户端不支持100响应码。但可以让客户端在请求消息中加入Expect头域,并将它的值设置为100-continue。

节省带宽资源的一个非常有效的做法就是压缩要传送的数据。Content-Encoding是对消息进行端到端(end-to-end)的编码,它可能是资源在服务器上保存的固有格式(如jpeg图片格式);在请求消息中加入Accept-Encoding头域,它可以告诉服务器客户端能够解码的编码方式。而Transfer-Encoding是逐段式(hop-by-hop)的编码,如Chunked编码。在请求消息中加入TE头域用来告诉服务器能够接收的transfer-coding方式。

9.性能优化

9.1使用多个TCP连接

上文已经说明,HTTP1.X并不支持多路复用,请求需要在客户端排队等待发送,而且容易遇到队首阻塞的问题,并不能很好的提高数据传输速率。那么,既然不能对单一连接进行多路复用,那是不是可以同时打开多个连接进行数据传输呢?答案是肯定的,浏览器开发商为了解决这个问题,使浏览器支持客户端最多打开六个连接,这样我们就可以更快速的进行通信。任何事情都两面性,同时打开多个连接势必带来一些优化和问题,具体如下:
优点:

  • 客户端可以并行发起多个请求;

  • 服务器可以并行处理多个请求;

  • 第一次往返可以发送的累计分组数量是原来的6倍;

缺点:

  • 更多的套接字会占用更多的资源;

  • 并行TCP流之间竞争共享的带宽;

  • 处理多个套接字,实现更为复杂;

  • 即使并行TCP流,应用的并发能力也受限制。

这种打开多个连接的方式,也带来了一些坏处,那为什么现在还使用的如此广泛呢?主要由以下三个原因:

  • 作为绕过HTTP限制的一个权宜之计;

  • 作为绕过TCP中低起始拥塞窗口的一个权宜之计;

  • 作为客户端绕过不能使用TCP窗口缩放的一个权益之计。

9.2域名分区

由于HTTP1.1协议不支持多路复用,迫使浏览器开发商为了提高通信效率,引入并维护着连接池,每个主机可以有6个TCP流。根据HTTP Archive统计,目前平均每个页面要包含90个左右的资源,如果这些资源都来自于同一个主机,即使可以同时打开6个TCP流,依然会导致明显的排队情形。我们并不需要把所有的资源都放在同一个主机上,可以分开放置到不同的域名下,这样就可以增加可以同时打开的TCP流总数,可以突破浏览器的连接限制,实现更高的并发能力。

理论上来说,使用的域名越多,并行能力也就越强。但是,在发送请求之前都需要进行DNS解析,不同的域名需要分别进行解析,都需要进行额外的DNS查询,如果域名数量过多,会导致大量的额外解析;在TCP连接中存在的慢启动机制,有时候也会降低性能;而且每多一个套接字都需要客户端和服务端消耗资源进行维护;更糟糕的是,开发者需要手动的把这些资源进行分区,部署到不同的域名下。域名分区的数量太大或太小都会影响性能,但如何确定最优的分区数量并是个很好回答的问题,因为页面中资源的数量、客户端连接的可用带宽及延迟等都会影响分区数量的合理性。

要想确定合适的域名分区数量,只能用最原始的方式从最小分区开始不断的测试,观察不同分区数目对应用的影响,然后选择最优的一个值作为固定分区数目。

9.3连接与合并

最快的请求就是不用请求,不管什么协议或什么类型的应用,减少请求次数总是最好的优化手段。如果每个资源都是必不可少的,那你可以考虑把这些资源打包到一块,通过一次网络请求获取。连接和拼合技术属于以内容为中心的应用层优化,通过减少网络往返开销,可以明显的提升性能。可是这些技术也需要额外的处理、部署和编码,也会带来额外的复杂性。把多个资源打包到一起,也可能给缓存带来负担,一些细微的更新都需要重新请求资源并缓存,有时候当前页面并不需要文件中的其他一些资源,这都会影响页面的执行速度。

在采用连接与合并技术时,虽然会减少网络往返开销和提升性能,但也会增加应用的复杂度,以致缓存、更新、执行速度、渲染页面等问题。所以,采用这种优化时,应综合考虑,寻求一种最佳的文件打包粒度。

9.4嵌入资源

嵌入资源也是一种很常见的优化方法,把资源嵌入文档可以减少请求的次数。嵌入页面的资源适合特别小的,使用次数很少,最好是一次的资源。实践中,一个经验规则是只考虑嵌入1-2KB以下的资源,因为小于这个标准的资源会导致比它自身更高的HTTP开销。然而,如果嵌入资源频繁变更,也会导致宿主文档的无效缓存率升高。如果应用中要使用很小的、个别的文件,在考虑是否嵌入时,可以参考以下建议:

  • 如果文件很小,而且只有个别页面使用,可以考虑嵌入;

  • 如果文件很小,但需要在多个页面中使用,应该考虑集中打包;

  • 如果文件经常需要更新,就不要嵌入了;

  • 通过减少HTTP cookie的大小将协议开销最小化。

参考资料:
《WEB性能权威指南》
《HTTP/1.1与HTTP/1.0的区别》
《HTTP 1.1与HTTP 1.0的比较》
http://www.faqs.org/rfcs/rfc1945.html
http://www.faqs.org/rfcs/rfc2616.html

posted @ 2016-11-30 09:17  默语  阅读(2708)  评论(0编辑  收藏  举报