Oh!Coder

Coding Life

读《HTTP权威指南》笔记(一)

| Comments

本着扫盲的目的,开始读《HTTP权威指南》这本书。按照计划,本周读完了这本书的第一部分,HTTP:Web的基础

本部分分为四章。主要讲述了HTTP相关基础知识。下面对每一章节做一些摘录和总结。

第一章:HTTP概述

从题目中可以看出,本章主要对常见相关概念做了简要的描述。尤其是一些常见的名词定义及其简要的解释。这里简要摘录一些进行罗列。

资源(Page.5)

Web服务器是Web资源(Web resource)的宿主。Web资源是Web内容的源头。

MIME类型(Page.6~Page.7)

Web服务器为所有HTTP对象数据附加一个MIME类型。最初设计MIME(Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。
MIME类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间由一条斜杠来分隔。例如,

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

URI(Page.7)

服务器资源名称为统一资源标示符(Uniform Resource Identifier,URI)。URI有两种形式,分别称为URLURN

URL(Page.7~Page.8)

统一资源标示符(URL)是资源标示符最常见的形式。URL描述了一台特定服务器上某资源的特定位置。大部分URL都遵循一种标准格式,这种格式包含三个部分。

  • RL的第一部分被称为方案(scheme),说明了访问资源所使用的协议类型。这部分通常是HTTP协议(http://)。
  • 第二部分给出了服务器的因特网地址(比如,www.ohcoder.com
  • 其余部分指定了Web服务器上的某个资源(比如,/specials/saw-blade.gif)。

现在几乎所有的URI都是URL。

URN(Page.8~Page.9)

URI的第二种形式就是统一资源名(URN)。URN是作为特定内容的唯一名称使用的,与目前的资源所在地无关。通过URN,还可以用同一个名字通过多种网络访问协议来访问资源。URN仍然处于试验阶段,还未大范围使用。

事务(Page.9)

一个HTTP事务由一条(从客户端发往服务器的)请求命令和一个(从服务器发回客户端的)响应结果组成。这种通信是通过名为HTTP报文(HTTP message)的格式化数据块进行的。

方法(Page.9)

HTTP支持几种不同的请求命令,这些命令被称为HTTP方法(HTTP method)。每条HTTP请求报文都包含一个方法。

状态码(Page.10)

每条HTTP响应报文返回时都会携带一个状态码。状态码是一个三位数字的代码,告知客户端请求是否成功,或者是需要采取其他行动。

报文(Page.11~Page.12)

HTTP报文是由一行一行的简单字符串组成的。HTTP报文都是纯文本,不是二进制代码,所以人们可以很方便地对其进行读写。从Web客户端发往Web服务器的HTTP报文称为请求报文(request message)。从服务器发往客户端的报文称为响应报文(response message)。HTTP报文包括三部分,

  • 起始行
  • 首部字段
  • 主体

代理(Page.19)

代理位于客户端和服务器之间,接收所有客户端的HTTP请求,并将这些请求转发给服务器(可能会对请求进行修改之后转发)。
处于安全考虑,通常会将代理作为转发所有Web流量的可信任中间节点使用。代理还可以对请求和响应进行过滤。

缓存(Page.20)

Web缓存(Web cache)或代理缓存(proxy cache)是一种特殊的HTTP代理服务器,可以将经过代理传送的常用文档复制保存起来。

网关(Page.20)

网关(gateway)是一种特殊的服务器,作为其他服务器的中间实体使用。通常用于将HTTP流量转换成其他的协议。

隧道(Page.21)

隧道(tunnel)是建立起来之后,就会在两条连接之间对原始数据进行盲转发的HTTP应用程序。HTTP通常在一条或多条HTTP连接上转发非HTTP数据,转发时不会窥探数据。HTTP隧道的一种常见用途是通过HTTP连接承载加密的安全套接字(SSL,Secure Sockets Layer)流量,这样SSL流量就可以穿过只允许Web流量通过的防火墙了。

Agent代理(Page.21)

用户Agent代理(或者简称为Agent代理)是代表用户发起HTTP请求的客户端程序。类似于大家常见的“网络蜘蛛(spiders)”或“Web机器人(Web robots)”等。

好啦,其实整个第一章就是在解释上面的这些专业名词,看起来挺枯燥的是吧,呵呵。没办法,这些都是基础,后面的几章内容要么是展开讲解,要么会引用到这些基本概念。不过就我感觉来说,有些网络上的专业名词,如果以前没有怎么接触过网络基础知识的话,感觉理解起来还是有门槛的。比如网关的概念,其实更多就是一种协议转换器。

那么,进入下一章。

第二章:URL与资源

本章就是在讲URL。众所周知Web上的资源都是靠URL进行定位的,所以URL与资源就捆绑到了一起。

从第一章的URI定义中得知,URL是URI的一个子集,与URN一起组成URI。其中,(Page.26)URL是通过描述资源的位置来标识资源的,而URN则是通过名字来识别资源的,与它们当前所处位置无关。但是关于这一点,书中提示说(Page.26)本书有时会不加区分地使用URI和URL,但我们讲的基本上都是URL。

相对于完整的URL来说,我们平时上网输入的URL都是简化的。(Page.29)大多数URL语法都建立在这个由9部分构成的通用格式上:

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

其中,URL最重要的3个部分是方案(scheme)主机(host)路径(path)。这三个方面也是我们平时上网经常输入使用的三个部分。下面对这几个部分做一个简单的介绍(Page.29)。

组件:方案(scheme)
描述:访问服务器以获取资源时要使用哪种协议。
默认值:无

组件:用户(user)
描述:某些方案访问资源时需要的用户名。
默认值:匿名

组件:密码(password)
描述:用户名后面可能要包含的密码,中间由冒号(:)分隔。
默认值:<E-mail地址>

组件:主机(host)
描述:资源宿主服务器的主机名或分IP地址。
默认值:无

组件:端口(port)
描述:资源宿主服务器正在监听的端口号。很多方案都有默认端口号(HTTP的默认端口号为80)。
默认值:每个方案特有

组件:路径(path)
描述:服务器上资源的本地名,由一个斜杠(/)将其与前面的URL组件分隔开来。路径组件的语法是与服务器和方案有关的。
默认值:无

组件:参数(params)
描述:某些方案会用这个组件来指定输入参数。参数为名/值对。URL中可以包含多个参数字段,它们相互之间以及与路径的其余部分之间用分号(;)分隔。
默认值:无

组件:查询(query)
描述:某些方案会用这个组件传递参数以激活应用程序(比如数据库、公告板、搜索引擎以及其他因特网网关)。查询组件的内容没有通用格式。用字符“?”将其与URL的其余部分分隔开来。
默认值:无

组件:片段(frag)
描述:一小片或一部分资源的名字。引用对象时,不会将frag字段传送给服务器;这个字段是在客户端内部使用的。通过字符“#”将其与URL的其余部分分隔开来。
默认值:无

以上部分是对URL的组成做了说明。关于这部分书上又做了部分详细的说明,不过这里就不罗列了,基本上知识点就这么多了。接下来讲到了相对URL

URL有两种方式:绝对的相对的。相对URL是不完整的。要从相对URL中获取访问资源所需的全部信息,就必须相对于另一个,被称为其基础(base)的URL进行解析。对于相对URL是有相应的算法进行补全的。有些浏览器在用户输入URL之后会进行“自动扩展”。关于“自动扩展”的特性有以下两种方式(Page.37):

  • 主机名扩展
  • 历史扩展

本章最后提到URL中的“方案”,其实这里所说的方案大部分属于TCP/IP协议中应用层上的协议。大体上这章就讲了这么多吧。

第三章:HTTP报文

先明确一下HTTP报文定义。(Page.46)HTTP报文是在HTTP应用程序之间发送的数据块。这些数据块以一些文本形式的元信息(meta-information)开头,这些信息描述了报文的内容及含义,后面跟着可选的数据部分。

对于事务处理的描述,HTTP是使用术语流入(inbound)流出(outbound)来描述流动方向的。HTTP报文由三个部分组成:(Page.47)对报文描述的起始行(start line)、包含属性的首部(header)块、以及可选的、包含数据的主体(body)部分。 (Page.48)所有的HTTP报文都可以分为两类:请求报文(request message)和响应报文(response message)。这两种报文的格式非常接近,这是请求报文的格式:
\<method\>\<request-URL\>\<version\> \<headers\>

<entity-body> </code>

下面是响应报文的格式:
\<version\>\<status\>\<reason-phrase\> \<headers\>

<entity-body> </code>

这里可以看出,请求报文和响应报文的格式只有起始行的语法不同而已。下面对各个部分做简要描述(Page.49~Page.50)。

  • 方法(method)
    客户端希望服务器对罪案执行的动作。
  • 请求URL(request-URL)
    命名了所请求资源,或者URL路径组件的完整URL。
  • 版本(version)
    报文所使用的HTTP版本,其格式看起来是这样的:
    HTTP/.</code> 其中,主要版本号(major)和次要版本号(minor)都是整数。
  • 状态码(status-code)
    这三位数字描述了请求过程中所发生的情况。
  • 原因短语(reason-phrase)
    数字状态码的可读版本,包含行终止序列之前的所有文本。
  • 首部(header)
    可以有零个或多个首部,每个首部都包含一个名字,后面跟着一个冒号(:),然后是一个可选的空格,接着是一个值,最后是一个CRLF。
  • 实体的主体部分(entity-body)
    实体的主体部分包含一个由任意数据组成的数据块。

接下来的章节其实是把HTTP各组成部分展开进行讲解。

起始行根据行为的不同,分为请求行和响应行。起始行中包括了一些请求的方法(Page.51),

方法:GET
描述:从服务器获取一份文档。
是否包含主体:否

方法:HEAD
描述:只从服务器获取文档的首部。
是否包含主体:否

方法:POST
描述:向服务器发送需要处理的数据。
是否包含主体:是

方法:PUT
描述:将请求的主体部分存储在服务器上。
是否包含主体:是

方法:TRACE
描述:对可能经过代理服务器传送到服务器上去的报文进行追踪
是否包含主体:否

方法:OPTIONS
描述:决定可以在服务器上执行哪些方法
是否包含主体:否

方法:DELETE
描述:从服务器上删除一份文档
是否包含主体:否

对于状态码来说,分为五类,每一类代表了不同的状态(Page.52)。

整体范围:100~199
已定义范围:100~101
分类:信息提示

整体范围:200~299
已定义范围:200~206
分类:成功

整体范围:300~399
已定义范围:300~305
分类:重定向

整体范围:400~499
已定义范围:400~415
分类:客户端错误

整体范围:500~599
已定义范围:500~505
分类:服务器错误

原因短语和状态码是成对出现的。使用版本号的目的是为使用HTTP的应用程序提供一种线索,便于互相了解对方的能力和报文格式。这里的版本号说明了应用程序支持的最高HTTP版本。

HTTP首部字段向请求和响应报文中添加了一些附加信息。本质上来说,它们只是一些名/值对的列表。对于HTTP的首部,书中分为了五类(Page.54)。

  • 通用首部
    既可出现在请求报文中,也可以出现在响应报文中。
  • 请求首部
    提供更多有请求的信息。
  • 响应首部
    提供更多有关响应的信息。
  • 实体首部
    描述主体的长度和内容,或者资源自身。
  • 扩展首部
    规范中没有定义的新首部。

剩下的章节基本上就是在对上面的这些知识点进行展开讲解了,这里就不展开罗列了。另外,插一句,在学这部分的时候,我认为可以结合具体的工具进行学习,比如,在讲这些信息结构的时候,如果理解有困难,可以结合Chrome/Safari浏览器的开发工具辅助学习。

对于Chrome浏览器,可以在工具选项里找到开发者工具选项,打开之后选中Network选项,然后刷新网页,选中左边的页面,右边显示区域就会显示相关数据。

pic

图中网站截图来自:CoolShell

Safari的操作与此类似,如果在Safari的菜单栏中找到开发,如果没有发现此菜单,可以先选择Safari浏览器的偏好设置,然后选中高级选项卡,选中下方的在菜单栏中显示“开发”菜单选项。关闭此窗口,即可发现顶部菜单栏处出现开发菜单项。选中开发菜单项,点开显示Web检查器,刷新网页,即可发现有数据更新。

pic

图中网站截图来自:CoolShell

通过上述两种方法,都能够更好的帮助我们理解相关概念。顺便还把这两个页面调试的利器进行了学习。理论结合实践,学习起来不会觉得漫无目的,不会产生不知道怎么用的偏差,个人觉得非常不错。

第四章:连接管理

这章主要是讲解HTTP的连接问题。本章开始,由于HTTP是基于TCP的,所以先从TCP的连接开始说起。大家都知道,TCP的连接属于可靠连接,而且TCP流是分段的,之后通过IP分组凡发送出去。

随后,谈到了TCP性能的问题。因为HTTP位于TCP的上层,所以HTTP事务的性能在很大程度上取决于底层TCP通道的性能。(Page.86)除非客户端或服务器超载,或正在处理复杂的动态资源,否则HTTP时延就是由于TCP网络时延构成的。文中以此为出发点,列了HTTP事务延迟的几种主要原因,书中讲解的比较啰嗦,我自己根据具体内容简单总结了一下(Page.86~Page.87)。

  1. 如果近期未访问相应URI,需要进行DNS解析,可能会花费数十秒的时间。
  2. 每条新的TCP连接建立时延,这个值通常最多有一两秒钟。
  3. 发送请求报文,等待服务器处理。
  4. Web服务器送回HTTP响应,也需要花费时间。

文中对于常见的TCP相关时延,列举了几种情况(Page.87~Page.91)。

  • TCP连接握手时延。
    通常HTTP事务不会交换太多数据,每一次握手都会产生一个可测量的时延。这样的话,小的HTTP事务可能会在TCP建立上花费50%的时间,或者更多。
  • 延迟确认
    由于网络原因,每次数据无法确保可靠传输,所以有可能因为确认信息产生时延。TCP将返回的确认信息与输出的数据分组结合在一起,可以更有效地利用网络。很多TCP栈都实现了一种“延迟确认”算法。但是,HTTP具有双峰特征的请求-应答行为降低了捎带信息的可能。
  • TCP慢启动
    TCP数据传输的性能还取决于TCP连接的使用期(age)。如果数据成功传输,会随着时间的推移提高传输的速度。这种调谐被称为TCP慢启动(slow start),用于防止因特网的突然过载和拥塞。
  • Nagle算法与TCP_NODELAY
    每个TCP段中都至少装载了40个字节的标记和首部,所以如果TCP发送了大量包含少量数据的分组,网络的性能就会严重下降。Nagle算法(根据其发明者John Nagle命名)试图在发送一个分组之前,将大量TCP数据绑定在一起,以提高网络效率。但是Nagle算法也会引发HTTP性能问题,小的HTTP报文可能无法填满一个分组,可能会因为等待那些永远不会到来的额外数据而产生延时。HTTP应用程序常常会在自己的栈中设置参数TCP_NODELAY,禁用Nagle算法提高性能。如果这么做的话,一定要确保会向TCP写入大块的数据,这样就不会产生一堆小分组了。
  • TIME_WAIT积累与端口耗尽
    当某个TCP端点关闭TCP连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的IP地址和端口号。这类信息会维持一小段时间,通常是所估计的最大分段使用期的两倍(称为2MSL,通常为2分钟)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。客户端每次连接服务器上去时,都会获得一个新的源端口,以实现连接的唯一性。但由于可用源端口的数量有限(比如,60000个),而且在2MSL秒(比如,120秒)内连接是无法重用的,连接率就被限制在了60000/120=500次/秒。如果再不断进行优化,并且服务器连接率不高于500次/秒,就可确保不会遇到TIME_WAIT端口耗尽问题。要修正这个问题,可以增加客户端负载生成及其的数量,或者确保客户端和服务器在循环使用几个虚拟IP地址以增加更多的连接组合。

为了提高HTTP连接的性能,书中提到四种方法,包括现存的和新兴的方法(Page.93)。

  • 并行连接
    通过多条TCP连接发起并发的HTTP请求。
  • 持久连接
    重用TCP连接,以消除连接及关闭时延。
  • 管道化连接
    通过共享的TCP连接发起并发的HTTP请求。
  • 复用的连接
    交替传送请求和响应报文(实验阶段)。

并行连接(Page.94~Page.95)

对于并行连接自身概念并不难理解,就是对一个页面可以同时进行多个事务。这里有一点想提一下的是,并行连接并不一定更快。当客户端的网络带宽不足时,大部分的时间可能都是用来传送数据的。一个连接到速度较快服务器上的HTTP事务就会很容易耗尽所有可用的Modem带宽。如果并行加载多个对象,每个对象都会去竞争这有限的带宽,每个对象都会以较慢的速度按比例加载,这样带来的性能提升就很小,甚至没什么提升。
但是对于并行连接,可能会让人感觉更快。因为并行连接可以让页面的显示同时进行刷新,让人感觉网速加快了。

持久连接(Page.96~Page.105)

在事务处理结束之后仍然保持在打开状态的TCP连接被称为持久连接。管理持久连接时要特别小心,不然就会积累出大量的空闲连接,耗费本地以及远程客户端和服务器上的资源。

对于持久连接,有两种类型:比较老的HTTP/1.0+“keep-alive”连接,以及现代的HTTP/1.1 “persistent”连接。不过在当前的HTTP/1.1规范中已经没有了keep-alive的说明了,keep-alive已经不再使用了。

实现HTTP/1.0 keep-alive连接的客户端可以通过包含Connection:Keep-Alive首部请求将一条连接保持在打开状态。如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有Connection:Keep-Alive首部,客户端就认为服务器不支持keep-alive,会在发回响应报文之后关闭连接。说到这里,有兴趣的话,可以通过Chorome的工具查看一下连接Google页面,你会发现带有Connection:Keep-Alive的首部。

当然,Keep-Alive首部完全是可选的,只有在提供Connection:Keep-Alive时才能使用它。

书中提到了Keep-Alive和哑代理的问题。为了避免此类问题,现代的代理都绝不能转发Connection首部和所有名字出现在Connection值中的首部。虽然Netscape实现了Proxy-Connection方法,但是对于多级代理还是会产生问题,并没有从根本上解决。

HTTP/1.1逐渐停止了对keep-alive连接的支持,用一种名为持久连接(persistent connection)的改进型设计取代了它。与HTTP/1.0+的keep-alive连接不同,HTTP/1.1持久连接在默认情况下是激活的。除非特别声明,否则HTTP/1.1假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1应用程序必须向报文中显示地添加一个Connection:close首部。HTTP/1.1客户端假定在收到响应后,除非响应中包含了Connection:close首部,不然HTTP/1.1连接仍维持在打开状态。

管道化连接(Page.105)

HTTP/1.1允许在持久连接上可选地使用请求管道。对于管道化连接有几条限制(Page.105)。

  • 如果HTTP客户端无法确认连接是持久的,就不应该使用管道。
  • 必须按照与请求相同的顺序回送HTTP响应。
  • HTTP客户端必须做好连接会在任意时刻关闭的准备,还要准备郝重发所有未完成的管道化请求。
  • HTTP客户端不应该用管道化的方式发送会产生副作用的请求(比如POST

关闭连接的奥秘(Page.106~Page.110)

HTTP应用程序可以在经过任意一段时间之后,关闭持久连接。所以每条HTTP响应都应该有精确的Content-Length首部,用以描述响应主体的尺寸。即使在非错误情况下,连接也可以在任意时刻关闭。这里提到一个幂等的概念。书中给出了一个简单的定义:(Page.108)如果一个事务,不管是执行一次还是很多次,得到的结果都相同,这个事务就是幂等的。我自己觉得这个概念不是很好理解,所以在网上搜了一下,发现CoolShell上有一篇文章,讲的还是挺详细的,反正我觉得我是看明白了。文中还解释了POST操作和PUT操作的区别。

套接字调用close()会将TCP连接的输入和输出信道都关闭了。这被称作“完全关闭”。还可以用套接字调用shutdown单独关闭输入或输出信道。这被称为“半关闭”。

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

好啦,第四章就这些东西,呵呵。

总体感觉第一部分的知识点很多,很多也都是基本概念,觉得要想灵活掌握,多联系一些实践或许理解的更深刻吧。

Comments