目录
前言
本文是对两篇博文的整理和摘抄:对《HTTP权威指南》的摘要与总结和《HTTP权威指南》读书笔记。尤其推荐前者。
第一章 HTTP 概述
- web 客户端:最常见的客户端就是 web 浏览器,比如 Chrome、Firefox、Opera 等。
- web 服务器:为 web 客户端提供数据的程序,比如 Apache、nginx 等。
- 资源:就是数据,比如说一张图片,一个视频文件。上网的过程其实就是获取资源的过程。每个资源都有属于它自己的媒体类型 (MIME type)。web 浏览器通过媒体类型来决定如何处理这个资源。资源名被称为统一资源标识符 (URI)。URI 最常见的形式是统一资源定位符 (URL),URL 也就是我们常说的网址。URI 的另一种形成是 URN(统一资源名),强大但发展不好。
- 事务:一个 HTTP 事务是由一条请求命令和一个响应结果组成。它们之间通过 HTTP 报文 (HTTP message) 通信。简单来讲,一对请求和响应构成一个事务。
- 方法:HTTP 请求命令,这些命令被称为 HTTP 方法 (HTTP method),比如:GET、POST等。
- 报文:HTTP 的报文有三部分,分别为起始行,首部字段和主体。如下图:
- 连接:HTTP 在客户端和服务器之间通过 TCP/IP 连接并传输数据。
- 协议版本:HTTP/0.9、HTTP/1.0、HTTP/1.0+、HTTP/1.1、HTTP/2。目前1.1和2是主流。
- Web 的结构组件:在互联网上,使用 HTTP 协议的除了上面提到的 Web 浏览器和 Web 服务器这两种应用程序,还有代理服务器(位于客户端和服务器之间的 HTTP 中间实体)、缓存服务器(HTTP 的仓库,使常用页面的副本可以保存在离客户端更近的地方,是一种特殊的代理服务器)、网关(连接其他应用程序的特殊 Web 服务器)、隧道(对 HTTP 通信报文进行盲转发的特殊代理)、Agent 代理(发起自动 HTTP 请求的半智能 Web 客户端,包括 Web 浏览器)。
- 网关(gateway),是一种特殊的服务器,说白了就是协议转换器,一个客户端使用 http 请求一个 ftp 服务器上的资源,那么就需要网关来完成协议的转换。
- 隧道: http 隧道就是用 http 链接转发非 http 数据,即用 http 把非 http 的数据包装一层。
第二章 URL 与资源
- 首先明确 URL 是独立于 http 范畴的概念。URL 的方案(Scheme)部分可以是 http,也可以是 ftp。
- URL 具有非常规范的格式,总共有9个“组件”。我们在浏览器中常见的 URL 主要由方案、主机、端口、路径、参数、查询和片段组成。
- 每个组件都有特定的符号跟前面的组件分隔开,如“/”后面就是路径,“;”后面是参数,“?”后面是查询,“#”后面是片段。
- 相对 URL。明确了“URL 是组件拼接起来的”之后,对于相对路径的处理就不难理解了。所谓“解析”,就是把 URL 按组件拆开,根据有无某些组件来处理字符串,最终可以拼接,或者是“搭建”出绝对路径来。
- URL 编码机制,因为会出现“不安全”的字符,所以把某些字符做编码处理。
第三章 HTTP 报文
报文流
-
HTTP 使用术语流入 (inbound) 和流出 (outbound) 来描述事务处理 (transaction) 的方向:
-
所有的报文都会向下游流动,所有报文的发送者都在接收者的上游:
状态码
100~199:信息提示
200~299:成功
300~399:重定向
400~499:客户端错误
500~599:服务器错误
首部
通用首部
请求和响应都可以用。细分为通用信息性首部和通用缓存首部,如下:
请求首部
提供更多有关请求的信息。细分为请求信息性首部、Accept 首部、条件请求首部、安全请求首部和代理请求首部,如下:
响应首部
提供更多有关响应的信息。细分为信息性响应首部、协商首部和安全响应首部。
实体首部
描述主体的长度和内容,或者资源自身。细分为信息性实体首部、内容首部和实体缓存首部。
扩展首部
规范中没有定义的新首部,即使用者自定义的首部。HTTP 程序虽然不认识,但会对其进行转发。
其他
- 原来 HTTP 0.9的响应报文中只有内容,原来一个通信协议可以如此简单(都是 TCP)的功劳。
- 首部是键值对
- post 方法和 put 方法的区别:post 方法是提交服务器需要的参数,put 是请求服务器把请求报文里的主体存在服务器上
- options 方法,客户端用来询问服务器支持哪些方法(万一有扩展方法)
- delete 方法,客户端请求服务器删除一份文档
- HTTP 的格式很简单,只有三部分:起始行、首部和主体。请求和响应之间只有起始行的格式不一样。
- trace 方法是用来环回诊断的。环回的意思就是发过去再收回来,有点像扔飞镖,而这个飞镖就是请求报文。客户端发送的请求经过一系列中间节点之后被服务器收到,服务器把它收到的请求(包括起始行、首部)原封不动地填进响应主体,这样客户端就能够知道自己的请求在整个过程中发生了什么,首部是不是被中间节点修改了等等。
- 如果你想知道你访问的网站(比如知乎)是使用什么 web 服务器,那么你可以查看“server” 首部。我查了几个,大部分都是 nginx,Tengine(基于 nginx),百度的首页是 JSP3(难道用的 jsp?),知乎是 CLOUD ELB 1.0.0,成电官网是 apache。
第四章 连接管理
TCP 连接
- TCP 为 HTTP 提供了一条可靠的比特传输管道,从 TCP 连接的一端填入字节会从另一端以原有的顺序正确地传送出来。
- HTTP 要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的 TCP 连接按序传输。TCP 收到数据流之后,会将数据流切片成被称为段的小数据块,并将段封装在 IP 分组中,通过因特网进行传输。所有这些工作都是通过 TCP/IP 软件来处理的,HTTP 程序员什么都看不到。如下图所示:
HTTP 连接
串行连接
每一个事务建立一个连接。
串行连接的注意点:
- 串行载入会将 TCP 时延叠加起来。
- 加载一个资源时,其他地方没有任何动静,会让人觉得很慢。
- 有些浏览器在对象加载完毕之前无法获知对象的尺寸导致无法确定对象的位置,所以在加载了足够多的对象之前,无法在屏幕上显示任何内容,一个资源加载完了,但用户看到的可能还是一片空白。
并行连接
通过多条 TCP 连接发起并发的 HTTP 请求。
并行连接的注意点:
- 可能会提高页面的加载速度,因为时延重叠,并行装载,增加了带宽的利用率。
- 如果在用户网络比较慢的情况下,不一定会更快,因为都在竞争这个有限的带宽,而且大量连接会消耗很多内存资源,从而引发自身性能问题。实际上浏览器确实使用了并行连接,但会对连接数做限制。
- 即使并行连接没有加快页面的传输速度,但通常也会给人==“感觉”会快一些==。因为用户会看到加载进展,整个屏幕有很多动作在进行。
持久连接
重用 TCP 连接,以消除连接及关闭时延。
持久连接的注意点:
- 客户端首部包含 Connection: Keep-Alive 时,表示想要开启持久连接,那么服务器响应首部也要包含相同的首部。如果响应中没有 Connection: Keep-Alive 首部,客户端就认为服务器不支持长连接,会在收到响应报文之后关闭连接。
- 只有当连接上所有报文都有正确的报文长度时(实体主体部分的长度都和相应的 Content-Length 一致),或者用分块传输编码方式编码时,连接才能持久保持。
- 客户端和服务器任意一方可以在任意时刻发送首部 Connection: close 来关闭持久连接。
- 如果客户端在收到完整的响应之前连接关闭了,就一定要做好重试请求的准备。
- 如果访问的服务器过程中有代理,而代理不支持持久连接,只是无脑转发,那么可能会有问题。因为客户端在这个连接中再次发送请求时,代理会忽略客户端的请求,导致客户端一直在等待,直到超时为止。所以不支持持久连接的代理必须在将报文转发出去之前,删除和持久连接相关的首部。
- HTTP/1.1的代理必须能够分别管理与客户端和服务器的持久连接,每个持久连接都只适用于一跳传输。
- 一个用户客户端对任何服务器或代理最多只能维持两条持久连接,以防止服务器过载。代理可能需要更多到服务器的连接来支持并发用户的通信,所以如果有N个用户试图访问服务器的话,代理最多要在任意服务器或父代理维持2N条连接。
管道化连接
通过共享的 TCP 连接发起并发的 HTTP 请求。管道化连接是在持久连接上使用请求管道,这是相对于 keep-alive 连接又一性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向另一端服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。
管道化连接的注意点:
- 如果 HTTP 客户端无法确认连接是持久的,就不应该使用管道。
- 必须按照与请求相同的顺序回送 HTTP 响应。HTTP 报文中没有序列号标签,因此如果收到的响应失序了,就没办法与其请求匹配起来了。
- 如果发送请求过程中连接关闭了,那么客户端要能重新发送缓存区中那些待发送和已发送但未响应的请求。比如客户端一次性发送10条,但是响应了5条服务器就关闭了连接,客户端要能再建立连接并重新发送剩下的那5条。
- 不应该把管道化应用在非幂等的请求(POST),要等待上一条幂等请求响应完毕,单独发送这个非幂等请求,防止出现多次执行的风险。
关闭连接
- 所有 HTTP 客户端、服务器、代理都可以在任意时刻关闭一条 TCP 传输连接。通常会在一条报文结束时关闭连接,但出错的时候,也可能在首部行的中间或其他奇怪的地方关闭连接。对管道持久化连接来说,这种情形是很常见的。比如持久连接在空闲一段时间之后,服务器可能就会将其关闭。但关闭的时候正好客户端发送了请求,这种是可能发生的,也是无法预料的。
- TCP 连接是双向的,每一端都有一个输入队列和一个输出队列,比如客户端的输出队列数据最终会出现在服务器的输入队列中。如下图:
- 应用程序可以关闭 TCP 输入输出信道中的任意一个或都关闭了。如下图:
- 对于半关闭来讲,一般输出信道的关闭是比较安全的,但是输入信道的关闭比较危险,因为你不知道对方会不会给你发送数据。如果想向已经关闭输入信道发送数据,操作系统会回送一条 TCP “连接被对端重置”的报文。大部分接收方的操作系统在收到这条报文后,会作为很严重的错误处理,会删除接收方还未读取的所有缓存数据。对管道化连接来说这是非常糟糕的事情。比如发送方已经在一条持久连接上发送了10条管道式请求了,并且响应也都收到了,正在输入(接收)缓冲区存着呢,还没来得及处理。此时你又发送了第11条请求,但是对方已经关闭了输入信道使你收到了一条连接被重置的信息,那么这个重置信息会导致你的接收缓存区被清空,那10条响应的报文就没了。。。
其他
- “HTTP 连接实际上就是 TCP 连接及其使用规则”,这解释了为什么 http0.9这么简单也可以实现功能,后面只是规则更多了而已,而在建立连接这件事情上,TCP 才是主角,所以本章有很多内容都是与 TCP 有关。
- http 的时延跟 TCP 有很大关系,可以通过设置 TCP 的相关选项来优化 http 的速度。那么在优化 TCP 参数之前,一定要搞懂 TCP 为什么这么干。TCP 不可能无故给你造成时延,你要优化它,那么你就要替代它做原本要做的事情。例如,通过禁用 Nagle 算法来提高性能,这时你要保证不会往 TCP 中写入小块数据。
- Connection 首部是逐跳首部(就是只能在一条 http 连接中生存)。
- 客户端发起并行连接不一定提高速度。如果带宽占满了,并行连接只会按比例分配;并行连接还更消耗资源(开辟更多线程、占用更多内存),这个时候可能降低性能;服务器可以拒绝来自一个客户端的不合理并行连接。
- 持久连接可以避免 TCP 的慢启动
- Http1.0使用 keep-alive 首部,虽然1.1已经不支持了,但还是很广泛
- Keep-alive 首部会出现哑代理问题,简单点说,就是代理盲目地转发了它不应该转发的 connection 首部(这是个逐跳首部),会引起一系列的问题。这个问题描述起来有些麻烦,打个比方,你和你情人通过一个朋友传情书,传递情书的方法写在了情书里面(听起来很奇怪),需要你朋友看完后删掉,感人的是,你这个朋友是个文盲,没有把这部分内容给删掉,原封不动地给了你情人,你情人看了这部分内容(自己脑补)一气之下把你甩了,这就是这个问题的严重性。这算是 http1.0的遗留问题,制定规范的没有考虑会有代理这个东西,所以出现了这个问题。同样的,因为没有考虑代理,还带来了别的问题,下面第六章会介绍。
- 当然,后面 http1.1针对代理也做了一些规定,但这时候网络世界的复杂性就体现出来了,有的机器用的还是1.0,数量还不少,所以 http2.0和 IPv6这种新的基础协议应用起来这么缓慢不是没有原因的。
- Http 1.1默认就是持久连接
- 关闭连接一点也不简单啊,如果使用管道化连接(流水线)传输非幂等(如 post),连接过早终止会出现不确定的后果,非幂等请求一定要等前一条请求的响应
- TCP 连接是双向的(全双工通信),两端都不可以先关闭输入连接,会引发 TCP 的连接重置错误,这个错误在管道化连接中是灾难性的,如果对面关闭了输入连接,本地会被对面发来的连接重置 TCP 报文清空自己的接收缓冲。
- 感觉要想学好这一章,学好 http 连接的部分,对 http 性能进行深度的优化,就必须学好 TCP
第五章 Web 服务器
Web 服务器的工作过程
下图是一个简单的 web 服务器的工作过程(商用 web 服务器会复杂很多):
- 建立连接:接受一个客户端连接,或者如果不希望与这个客户建立连接,就将其关闭。
- 建立连接,把 IP 地址从 TCP 连接中解析出来并将新连接添加到 Web 服务器连接列表中,做好监视连接上数据传输的准备。当然,Web 服务器可以随意拒绝或立即关闭任意一条连接。
- 可以通过“反向 DNS "查询客户端主机名,用于访问控制和日志记录。但是由于可能会比较耗时,一般是禁止的。
- 接收请求:从网络中读取一条 HTTP 请求报文。
- Web 服务器需要从网络中读取数据,经部分报文数据临时存储在内存中,直到收到足以进行解析的数据并理解其意义为止。
- 解析请求起始行,查找请求方法,指定的资源标识符以及版本号,各项之间由空格分隔,以回车换行 (CRLF)(\r\n) 结尾。
- 读取以 CRLF 结尾的请求首部(开始读取首部)。
- 检测只有 CRLF 的空行(代表首部结束)。
- 如果有的话,读取请求主体,长度由首部的 Content-Length 指定。
- 有些服务器还会用便于进行报文操作的内部数据结构来存储请求报文。比如存储成一个字典,方便调用。
- 不同的 Web 服务器会以不同的方式接收请求:
- 处理请求:对请求报文进行解释,并采取行动。
- 将报文分析并操作。包括要请求什么资源,以什么方式。
- 访问资源:访问报文中指定的资源。
- 通过 URI 查找对应根目录下面的资源文件,或者返回一个目录索引,或者映射到动态资源,即连接一个应用程序,可以动态生成想要的资源。
- 可以添加访问控制,指定资源的访问权限。
- 构建响应:创建带有正确首部的 HTTP 响应报文。
- 构建响应的状态码、响应首部、响应主体、主体 MIME 类型等。
- 也可以返回重定向响应,让浏览器去别的地方取资源。
- 发送响应:将响应回送给客户端。
- 服务器存在和各个客户端之间的多条连接,有的是空闲的,有的在向服务器发数据,有的在向客户端响应数据,有的有持久连接,有的没有持久连接,服务器要记录连接的状态,选择正确的连接响应数据。
- 记录日志:将与已完成事务有关的内容记录在一个日志文件中。
- 最后,当事务结束时,Web 服务器会在日志文件中添加一个条目,来描述已执行的事务。
其他
- 这一章的内容只是简单地介绍了一下服务器。服务器处理连接请求常用的策略:单线程(性能差,速度慢)、多线程(资源消耗大)、监视连接,并使用单线程轮流处理请求(性能低)、监视连接,并使用多线程轮流处理请求;最后一个就是线程池,前两个都是要重新建立线程,第三个就是只有一个线程的线程池,省去了创建线程的开销,相应地实现、维护更费劲了。
第六章 代理
什么是代理
Web 代理服务器是网络的中间实体。位于客户端和服务器之间,扮演着“中间人”的角色:
代理分两种:一个是公共代理,可以给多个人使用;一个是私有代理,专门给自己使用的,不太常见,但是一般浏览器都会自带一个小型的缓存代理,以提高性能。
代理都可以做哪些事
- 儿童过滤器:可以利用过滤器代理来防止学生访问成人内容。
- 文档访问控制:可以做集中式访问控制,比如不允许客户端 A 访问服务器 X,不允许 B 访问 Y,C 访问 Z 时需要输入验证口令等。
- 安全防火墙:可以使用防火墙代理限制某一端的出或入。
- Web 缓存:代理缓存维护了常用文档的本地副本,并将它们按需提供给客户端。
- 反向代理:区别于代理的是,反向代理通常服务于服务器端,一个反向代理后面可以存在多个服务器。
- 内容路由器:可与反向代理搭配使用,根据不同情况选择不同的下游服务器。
- 转码器:代理服务器在将内容响应给客户端之前,可以对内容做修改。比如图片压缩,语言转换。
- 匿名者:匿名者代理会主动从 HTTP 报文中删除身份特性,比如客户端 IP 地址,Referer 首部。
如何将流量导向代理
如何搭建代理
(略)
Via 首部
规定每经过一个代理,都要在 Via 首部中添加代理名称。如下图:
然后请求的 Via 首部和响应的 Via 首部正好相反:
Server 首部
Server 响应首部用来标示最终的那个服务器所使用的软件,比如下面这几种:
注意,它不是用来表示代理信息(Via用来显示代理信息),而是真实的服务器的信息。
TRACE 方法
用来追踪代理链传输的请求报文,当 TRACE 请求到达目的服务器时,整条请求报文都会被封装在一条 HTTP 响应的主体中回送给发送端。如下图:
可以理解为客户端想看一下服务端收到请求时的 Via 首部,因为它返回的最主要的信息就是 Via 请求首部。
其中可以使用 Max-Forwards 指定最大跳数,当 Max-Forwards 为0时,如果没有到达服务器,代理会直接返回给客户端:
OPTIONS 方法
查看下一跳服务器(也可以是代理)支持的所有方法
Allows 首部
如上图,用来表示服务器支持的方法。
其他
- 网关是协议转换器,重点在不同协议的转换上,代理则不需要进行协议的转换,它的重点在于添加或者增强功能,有点”润滑剂“、”插件“的味道。
- 代理可以实现的功能很多,比如儿童过滤器、访问控制、防火墙、web 缓存、反向代理(加速器)、内容路由(可以实现付费速度提升)、转码、匿名等等
- 反向代理可以发起与其它服务器的通信(web 服务器是不可以的)ngnix 就是使用了反向代理,提升了性能。
- 提起反向代理,不得不说一下翻墙的原理。GFW 基本上通过黑白名单和流量特征分析来工作,传统的 VPN 流量容易被分析出问题,而酸酸或者酸酸乳可以混淆流量,将流量伪装成普通流量,同时境外的服务器不在黑名单上,突破这两道坎,你的流量就很容易发送出去了。反向代理我觉的就应该指的是境外的服务器,充当一个境外被墙网站的反向代理,访问其它服务器,返回流量。反向代理距离服务器更近。
- 代理获取流量的方法有四种:修改客户端、修改网络、修改 DNS 命名空间、修改服务器(重定向)
- 修改客户端又有几种方法:手动配置、浏览器厂商预先配置、PAC 文件、WPAD 协议
- 有个点一直未注意到,就是 http 请求一般发送的是部分 uri,不包括方案、主机和端口,当然,如果是客户端直接与服务器通信的话,当然没问题,因为连接是已经在两端建立好了,这样可以避免发送冗余信息。但是有代理就不一样了,代理没法从部分 uri 中解析真实的服务器地址啊。
- 当然,如果客户端知道自己连接的是代理也容易,发送完整 uri 就得了,可是如果不知道呢(透明代理,比如反向代理)?这时候 Host 首部就派上用场了,但是如果代理不认识 host 呢?那就得用别的方法了,方法是有的,太麻烦就不多说了。
- 另外一个问题,如果客户端直接与服务器通信,那么如果服务器停用了(或者搬迁了),那么客户端应该会直接知道,浏览器会做一些事情(容错机制)尝试找到正确的地址,但是如果使用的是透明代理,浏览器与代理成功建立连接的一刻,客户端就会认为连接成功了,然后代理却发现该 uri 停用了,这个时候代理就应该替浏览器做那些容错的事情。
- Max-forwards 首部:还剩下几跳就应当返回
- 代理如果遇见自己不认识的首部,应当努力将其转发到下一跳。
第七章 缓存
缓存的优点
- 减少了冗余的数据传输
- 缓解了网络瓶颈的问题
- 降低了对原始服务器的要求
- 降低了时延
缓存的种类
一般分两种:私有缓存和公有缓存。如下:
私有缓存:又称专用缓存,是个人的缓存,一般你的浏览器会内建有私有缓存。
公有缓存:又称代理缓存,是人为搭建的,一般为多个用户提供缓存服务。
缓存的几种情况
无论是私有还是公有缓存,在有缓存的情况下,会有下面这几种情况:
【注:缓存作为名词讲,可以代表缓存服务器,如下图中间的那个虚线框代表缓存服务器。缓存也可以作为动词讲,表示缓存服务器可以缓存文档,缓存的文档可以被称为缓存对象,也可以简称为缓存,如下图虚线框里面的三个文档都可以称为缓存对象或者称为缓存。所以==“缓存”有的时候是指缓存服务器,有的时候是指缓存的文档,有的时候指的是一个临时存储的动作==,需要注意区分。下面我尽量区分开,防止读者歧义。】
- (a) 是缓存对象没有过期的情况,缓存服务器直接把缓存对象原样返回给客户端了(有时候首部会做些改写),状态码200。
- (b) 是缓存对象过期了,去服务器中寻找,发现已经不是原来缓存的那个文件了,从服务器拿来新的首部和主体替换掉之前的那个缓存对象,并发送给客户端,状态码200。此时如果想要区分客户端收到的是缓存对象还是服务器对象,可以通过响应头中的 Date 判断, Date 如果是以前的时间代表是缓存数据。因为 Date 的值一般都是服务器即时生成的。
- ©是缓存对象过期了,去服务器中寻找,发现之前缓存的文件和服务器上的一样没有变,那么只需从服务器上拿来需要改动的首部,把原来已有的缓存对象的首部做下修改替换(比如 Date),还是返回原来缓存中的内容,只不过首部发生了部分变化,响应状态码304。
下图是(a)的过程图:
缓存的处理过程
通常会经历七个步骤:
- 接收:缓存服务器检测网络连接上的活动并读取输入数据。
- 解析:缓存服务器将请求报文处理成易于操作的数据结构。
- 查找:缓存服务器根据请求 url 查找文档是否存在,它可能在内存,磁盘,甚至附近的另一台计算机中。如果本地没有,它会去原始服务器(或父代理,后面省略)中去取;如果本地有,那本地中会有已经存储了的原始服务器响应首部和主体,以及缓存对象的元数据(用来记录对象在缓存中停留了多长时间和被用过多少次等)。
- 新鲜度检测:如果文档还处在缓存期内,那么就认为这个文档是“新鲜的”,否则被认为是“过时的”。如果文档还是“新鲜的”那没什么可说的,如果文档是“过时的”,则缓存服务器要再次与原始服务器进行确认,查看文档是否发生变化。如果文档发生变化,则把文档的首部和实体都重新拿来,如果文档没有发生变化,则把文档的部分需要改写的首部拿来。
- 创建响应:将已缓存的原始服务器的响应首部作为响应首部的起点,并进行改造以便与客户端的请求相匹配。比如缓存中存储的是 http/1.0的响应,但是客户端用的是 http/1.1的请求,那么需要进行相应的修改再返回给客户端。实体部分无需改动。
- 发送:一旦响应首部准备好了,缓存就将首部和实体回送给客户端。
- 日志:每个缓存事务结束后,一般都会保存日志文件以及与缓存的使用有关的数据统计。
再看下面一张图助于理解:
与文档是否新鲜相关的首部
- Expires:
HTTP/1.0+的响应首部,形如Expires: Fri, 05 Jul 2002, 05:00:00 GMT
表示一个绝对的过期时间,超过这个时间表示文件过期。这个首部有点老了,而且由于每个服务器可能存在时间不一致的情况,使用绝对时间可能会不准确。一般推荐使用 Expires 的HTTP/1.1的代替者——Cache-Control: max-age。 - Cache-Control: max-age=< second >
HTTP/1.1的响应首部,形如Cache-Control: max-age=484200
表示一个相对过期时间,即从获取文件开始经过484200秒后过期。max-age通常被称作最大生存时间,单位为秒,0表示立刻过期。
与服务器再验证相关的首部
到了这一步,说明文档已经过期了,要去服务器中比较:
- If-Modified-Since: < date >
这是一个请求首部,简称 IMS。让 IMS 中的时间与服务器文档最后修改时间 (Last-Modified) 做比较,如果时间一致,说明服务器的文档从上次获取到现在都没有修改过,则只需获取新的首部,包括一个新的过期时间,并对缓存中的首部进行更新,响应304。如果时间不一致,说明服务器上的文档被修改了,则获取一份新首部的新文档,并将其替换在旧文档的位置上,然后发送给客户端,响应200。If-Modified-Since 请求首部通常与一个 Last-Modified 响应首部配合使用。因为每次 If-Modified-Since 的值都是前一次缓存对象首部中 Last-Modified 的值,If-Modified-Since 会用这个值去和原始服务器 Last-Modified 的值比较。 - If-None-Match: < tags >
这也是一个用来再验证的请求首部,你也可以简称它为 INM。上面我们提到的那个 If-Modified-Since 其实会存在一些问题,比如文档内容没有变只是被重写一下,文档只修改的很少的无关紧要的一些东西无需每个人都重新获取,有些服务器无法准确的判断文档修改日期等,都会导致不必要的文档重传。或假设文档被亚秒级修改,Last-Modified 无
法感知文档的变化 (If-Modified-Since 和 Last-Modified 的时间都只能表示到秒)而错失重传的情况!这时我们的 If-None-Match(实体标签再验证)首部就闪亮登场了。
其实就是给文档打个标签,只有文档内容变了标签才会发生改变,If-None-Match 通常与响应首部 ETag 配合使用。
看下图:
比较标签值,一样的话响应304,不一样的话响应200。标签可以有强弱之分,强的标签只要文档改动了标签就会发生变化,默认是强标签。还有一种弱标签,弱标签在改动一些不重要的东西时标签不会变化,只是在修改了重要的东西时标签才会改动。弱标签一般会在原标签上有个前缀 “W/”,比如 ETag: “v2.6” 写成弱标签就是 ETag: W/"v2.6"
。
注:如果服务器回送了一个实体标签 ETag,HTTP/1.1客户端就必须使用实体标签验证器 If-None-Match。如果服务器回送了一个文件最后修改时间 Last-Modified,客户端就要使用 If-Modified-Since 验证。如果服务器的实体标签和文件最后修改日期都提供了,客户端就应该使用这两种再验证方案。如果服务器收到两种验证,只有两个条件都满足时
才能返回304。
- 一个清晰的总结:
- If-Modified-Since(请求首部) <=> Last-Modified(响应首部)
- If-None-Match(请求首部) <=> ETag(响应首部)
控制缓存的能力
服务器可以通过几种方式指定文档过期之前可以将其缓存多长时间,按照优先级递减的顺序,如下:
- Cache-Control: no-store
- Cache-Control: no-cache
- Cache-Control: must-revalidate
- Cache-Control: max-age
- Expires
- 没有过期信息,让缓存自己确定(使用LM-factor算法,这里就不细说了)
解释如下图:
no-store 和 no-cache 的区别:
- no-store 的响应会禁止缓存服务器缓存响应的文件,缓存服务器会向客户端转发 no-store,然后把它删除。
- no-cache 的响应实际上是可以存储到本地缓存区的,但是缓存服务器每次在提供数据之前都需要先和服务器做验证。
no-cache 和 must-revalidate 的区别:
- no-cache:告诉浏览器、缓存服务器,不管本地副本是否过期,使用资源副本前,一定要到源服务器进行副本有效性校验。
- must-revalidate:告诉浏览器、缓存服务器,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。must-revalidate 也可以配合 max-age 等一起使用,比如
Cache-Control: max-age=10, must-revalidate
。 - 参考自:web性能优化之:no-cache与must-revalidate深入探究
Cache-Control 是一个通用首部,还可以用来在请求时发送,意义如下:
我们在浏览器强制刷新,一般是在请求中带了 Cache-Control: no-cache 首部。
其他
- 文档命中率说明阻止了多少去往外部的事务,提高它,对降低整体时延有好处。我觉得是这个降低了建立连接的频次。
- 字节命中率说明阻止了多少流量去往互联网,提高它,对节省带宽有好处
- 网状缓存不是简单的层次机构,与 CDN 相关
- 缓存中最重要的部分是保持副本的新鲜
- 一种是文档过期,就是服务器发给缓存的首部中设置一个“保质期”;另外一个就是到了日期之后缓存需要询问服务器该副本是否过期,这叫做服务器再验证。再验证的时候,条件 GET 很有用。条件请求中,除了日期作为判断条件,也可以使用实体标签 etag。etag 可以分为强弱验证器,所谓弱验证器,就是服务器不希望文档在发生一点不重要的改变时,就更新所有的缓存,所以这个时候可能发送没发生改变的弱验证器
Cache-Control:no-store
首部禁止缓存,而Cache-Control:no-cache
是可以有本地缓存,只是使用前需要再验证- 新鲜度计算中最重要的变量:文档使用期,这个算法不难就是难记,感觉也没必要记,了解这个使用期的计算需要考虑逐跳使用期和网络时延。
- 关于缓存有一个比较有趣的问题,就是缓存与广告。内容提供商靠广告获取收益,他要去统计广告数量,但是缓存拦截了很大的访问量,于是有些广告商会使用各种去除缓存的技术,如使用 CGI 动态生成广告,但这不是损害缓存对服务器的积极作用吗?!所以代理缓存还得照顾广告的利益,定期向服务器汇报页面命中的次数统计。
第八章 网关、隧道、中继
- 网关在 http 框架下的分类有服务端网关HTTP/* 和客户端网关 */HTTP
- 原来 http 起始行中,可以直接写 GET ftp://… 这样的语句来直接请求非 http 资源,这时就需要网关来处理了
- 资源网关:CGI,之前一直没弄懂动态资源生成技术为啥叫通用网关接口,现在弄明白了,凡是不是静态资源、不是按照目录直接查找的或者调用其他接口生成内容的,都由接口来处理
- CGI 是独立于服务器的,可以使用任意语言实现
- 隧道就是使用 http 包装非 http 流量。我的理解就是,隧道的两端还需要进行协议的转换,也就是包装和解包的操作,如果不建立隧道的话,一个 ftp 服务器可能直接收到一个 http 里面包装的 ftp 报文,它是不认识的。
- 隧道使用 CONNECT 方法建立
- 有趣的是,隧道一开始是为了使 SSL 流量穿过防火墙的,而 https 是把 http 放在 ssl/tls 上传输
- 为了避免对隧道的滥用,网关应该只对知名端口如 https 的443打开隧道
- 中继是没有完全遵循 http 规范的简单 http 代理。简单盲中继容易引发代理那一章所讲的 keep-alive 相关问题
第九章 Web 机器人
如果想做一个搜索引擎式的爬虫,可以参考里面的建议。
- 会有那种恶意针对搜索引擎爬虫的服务器,把自己的目录设置成循环的,让爬虫深陷其中
- 大规模爬虫去重的话,就要用到搜索树或者散列表来存储已爬取的 URL
- 机器人要使用 Host 首部,否则有可能服务器不知道把该请求映射到哪个虚拟主机中去,从而获取错误的内容
- 机器人是可能访问到 CGI 的,这种情况会加大服务器的开销
- 服务器通过 robots.txt 来告诉机器人可爬的 url 列表
- 机器人为了避免开销,会周期性缓存 robots.txt,服务器会使用标准的缓存控制机制来控制该文件的缓存
第十章 HTTP-NG
(略)
第十一章 客户端识别与 cookie 机制
HTTP 最初是一个匿名、无状态的请求/响应协议。但如今,现代的 Web 站点希望能够个性化显示,希望对连接另一端的用户有更多的了解,并且能在用户浏览页面时对其进行追踪,所以需要对 HTTP 添加一种识别用户的机制。
承载用户身份信息的 HTTP 首部
- From:From 包含 Email 地址,可以很好地用来识别用户,但容易被人伪造,所以很少使用。
- User-Agent:可以告知服务器用户浏览器信息,对于针对不同的浏览器的区别展示比较有用,但不能识别特定的用户。
- Referer:可以了解用户的兴趣所在,比如从一个篮球的网站跳转过来的,但依旧无法完全标识用户。
- Authorization:通过输入账号密码登录来识别用户,服务器使用 WWW-Authorization 首部提示输入登录账户密码,客户端使用 Authorization 首部并包含账户密码访问页面。但是每个站点如果都需要输入账号密码才能记录该用户的信息,太麻烦了。
- Client-IP/X-Forwarded-For:通过 IP 地址来识别不同的来源也是一个方式,但是通过 IP 识别用户存在着很多缺点:比如多个用户共用一个 IP,动态分配 IP, 通过 NAT 转换来的 IP,通过代理访问等都会导致在识别用户上出现问题。
胖 URL:一种在 URL 中嵌入识别信息的技术。为每个用户生成特定的 URL,这种 URL 称为胖 URL,如下:
其中 002-1145265-8016838 是这个用户特有的标识码。但是会存在以下问题:
- 丑陋的胖 URL,容易给用户造成困惑;
- 无法共享 URL 给其他人,因为它包含了你的信息;
- 破坏缓存,因为每个都不一样,没有公用的 URL;
- 重写 URL 页面会增加服务器额外的负荷;
- 一不小心逃逸出了服务器提供的 URL,可能就找不回来了;
- 会话无法持久,用户退出之后信息就丢失了。
cookie
一种功能强大且高效的持久身份识别技术。cookie 是当前,识别用户,实现持久会话的最好方式。cookie 的工作方式如下所示:
可以笼统地将 cookie 分为两类:会话 cookie 和持久 cookie。如果 Set-Cookie 没有设置 Expires 或 Max-Age,那它就是会话 cookie,否则就是持久 cookie。
Set-Cookie 属性
- Expires:绝对过期时间。
- Max-Age:相对过期时间。如果 Expires 和 Max-Age 同时存在,Max-Age 的优先级最高。如果这两个都不存在,则会话结束后清除 cookie。
- Domain:指定对某个域时添加 cookie。
- Path:指定 Path 目录及子目录添加 cookie。
- Secure:https 请求时添加 cookie。
- HttpOnly:必须是 http 请求时添加 cookie。比如通过 API 方式操作的无法携带 cookie。
其他
- 服务器登录认证应该是 http 提供的两种认证方法之一,对于不是特别需要保密的会话来说就比较繁琐了,还需要记住用户名和密码,很有可能用户会觉得麻烦而放弃继续浏览
- 胖 URL,就是为每个用户生成一个状态标识码,在用户发送请求的时候就可以知道这个用户的状态了,相当于把 cookie 放在了 url 里面,这样做显然增大了耦合性,这种私有的 url 会降低缓存的性能,用户在分享链接时有可能泄漏隐私或者是失效的链接,服务器要重写 html 使得每个 url 变胖,用户如果一不小心没有使用这个 url 了,那么就会丢失所有的进展。
- 解决这种需要轻微保存用户状态的、改善用户体验的问题的方法就是使用 cookie,它既不像登录认证那样繁琐,也可以避免上面的问题
- cookie 有 cookie0和 cookie1,set-cookie 函数设置的是0版本,set-cookie2是1版本
- 每个 cookie 都是键值对
- cookie 罐就是保存 cookie 的文件,像个罐子,每个 cookie 都由一些属性,如值、域、过期时间等等
- cookie 的域里面确定站点,path 属性里面确定路径
- 这里 cookie 的隐患在于,广告商可能在不同的站点使用相同的 cookie 跟踪你的足迹,这样就可以在你浏览不同网站时“精准”投放广告了。
- 跟胖 URL 一样,cookie 存在缓存问题,缓存代理不应该缓存 set-cookie 首部,也不应该缓存带 cookie 首部的请求的响应
- cookie 是把双刃剑,一般人都不会禁用 cookie,在不完全了解 cookie 之前,我也觉得 cookie 像宣传的那样好,对隐私没有侵犯。对一般人来说,他们就更不了解了,再加上复杂的用户隐私协议和站点的宣传,通常的操作就是点个同意就完事了,这可能会给很多第三方 cookie 很多可乘之机。
第十二章 基本认证
基本认证比较好理解,内容也不多,就是用户在访问带有安全域的网页时,服务器会通过401(或代理的407)状态码与 WWW-Authorization(或代理的 Proxy-Authorization)的响应首部弹出验证框,用户输入账号密码之后,通过值为用户与密码的 base64加密后的 Authorization(或代理的 Proxy-Authorization)请求首部提交后,即可访问正常页面。
基本认证会存在以下安全问题:
- 账户密码通过 base64传输的,容易被破解。
- 即使无法破解,数据包被他人捕获后,可通过重放攻击获取访问权。
- 即使在内部使用,觉得被知道也无所谓,但是有可能被有心人用来尝试使用该密码对此用户其他网站的登录。
- 没有针对代理和作为中间人的中间节点的防护。
- 可能会被假冒服务器骗过基本认证。
所以,基本认证一般需要配合 SSL 或 TLS 使用。
其他
- 基本认证机制就是服务器需要认证客户端,这在保密会话中是必须的
- http 自带两种客户端认证机制,基本认证机制和摘要认证
- 基本认证很简单,不安全
- 基本认证中用户名和密码几乎是明文传输的
第十三章 摘要认证
书中的摘要认证主要参考自 rfc2617,但现在已经被 rfc7616取代。
摘要认证是另一种 HTTP 认证协议,它试图修复基本认证协议的严重缺陷。具体来说,摘要认证进行了如下改进:
- 永远不会以明文方式在网络上发送密码
以密码摘要的形式传输,比如 MD5。 - 可以防止恶意用户捕获并重放认证的握手过程。
发送的摘要中混有随机数,这样每次的摘要都不一样。 - 可以有选择地防止对报文内容的篡改。
发送的摘要可以连带着主体内容一起计算。这样如果主体发生改变,服务器那端可以发现。 - 防范其他几种常见的攻击方式。
然后再附上几张图,基本上就明白什么是摘要认证了:
其他
- 摘要认证克服了基本认证的很多缺点,大大加强了安全性
- 摘要认证首先传输的是密码的摘要
- 摘要算法可以应用于报文主体,防止篡改
- qop 首部可以指定摘要的范围
- 预授权是相对于每次请求都要认证来说的。预授权的意思是,每次把授权消息和请求一起发给服务器,可以提高效率
- 在预授权中,有好几种随机数的生成方法
- 摘要认证可以进行对称认证,只要提供 qop 指令,就必须进行双向认证
- qop 指令包含在 WWW-Authenticate、Authorization 和 Authorization-info 中,客户端和服务器可以通过 qop 指令协商保护的质量
- 如果代理处于某些无害的原因【如转义字符】修改了 URI,摘要认证会检查 URI 的完整性,这时摘要认证就会被破坏
- 几乎每一个议题都会涉及到缓存,和代理,本章里缓存不应当缓存认证请求对应的响应,除非 Cache-Control 指定 must-revalidate 或者 public
- 可以指定是只对首部进行完整性保护还是保护整个报文
- 摘要认证不能防止窃听,SSL 加密可以
第十四章 安全 HTTP
上述的两种认证还是不够强大,我们需要一种能够提供下列功能的 HTTP 安全技术:
- 服务端认证(客户端知道它们是在与真正的而不是伪造的服务器通话)
- 客户端认证(服务器知道它们是在与真正的而不是伪造的客户端通话)
- 完整性(客户端和服务器的数据不会被修改)
- 加密(客户端和服务器的对话是私密的,无需担心被窃听)
- 效率(一个运行的足够快的算法,以便低端的客户端和服务器使用)
- 普适性(基本上所有的客户端和服务器都支持这些协议)
- 管理的可扩展性(在任何地方的任何人都可以立即进行安全通信)
- 适应性(能够支持当前最知名的安全方法)
- 在社会上的可行性(满足社会的政治文化需要)
加密与解密
上图中,
如果编码所用密钥和解码所用密钥相同,我们把这称为:对称密钥加密。流行的对称密钥加密算法包括:DES、Triple-DES、RC2和RC4。
如果编码所用密钥和解码所用密钥不同,我们把这称为:非对称密钥加密或称为公开密钥加密。编码密钥一般是公开的,解码密钥一般是保密的。比如:RSA。
下图是对称密钥加密和公开密钥加密的对比图:
我们可以很明显的看到,公开密钥加密只需要对外提供一个公开密钥即可,而对称密钥加密需要服务器和每个客户端都存有一套独立的私有密钥。这在管理上将是一个噩梦。所以HTTPS 采用 RSA 方式加解密==。
数字签名
使用加密系统对报文进行签名,以说明是谁编写的报文,同时证明报文未被篡改过,这种技术被称为数字签名。
简单来讲:A 把报文的摘要(比如 MD5)通过自己的私有密钥加密并和明文报文一并传给 B,然后 B 用 A 的公钥解码签名得到报文的摘要,再与实际收到的报文的摘要作比较,如果一样,说明没有被改动过,不然,说明报文可能已经被修改过了。签名也可以证明是作者编写了这条报文,不然如果有人伪造作者发了一个包,在签名时因为不知道作者的私
有秘钥,随便使用一个密钥加密后,接收者都不能还原本来的报文摘要,因为接收者使用的是作者的公钥解的密。
数字证书
==数字证书中包含了由某个受信任组织担保的用户或公司的相关信息。==某个受信任组织一般被称为“证书颁发机构”,用户或公司一般是服务器的归宿方。比如我个人申请了个数字证书,你可以理解为——证书颁发机构给我发了一个包含了我的相关信息的数字证书。证书的格式及说明如下所示:
验证证书
通过 HTTPS 建立了一个安全 Web 事务之后,现代的浏览器都会自动获取所连接服务器的数字证书并对签名颁发机构进行检查。如果这个机构是个很有权威的功能签名机构,浏览器可能已经知道其公开密钥了,然后通过如下操作验证完整性:
如果对签发机构一无所知,浏览器就无法确定是否应该信任这个签名颁发机构,它通常会向用户显示一个对话框,看看他是否相信这个签名发布者。
HTTPS 的完整访问过程
SSL 握手过程中,主要完成以下工作:
- 交换协议版本号;
- 选择一个两端都了解的密码;
- 对两端的身份进行认证;
- 生成临时的会话密钥,以便加密信道。这个会话密钥是客户端生成并发送给服务器的,是一个新的对称密钥,用它加密速度快,取代了使用对方的公钥加密。
服务器证书是一个显示了组织名称、地址、服务器 DNS 域名以及其他信息的 X.509 v3派生证书。
【注:TLS 是 SSL 的升级版,SSL 3.0的升级版是 TLS 1.0】
其他
- HTTPS 也就是 SSL/TLS 上的 HTTP,它可以对报文进行加密,以达到防窃听的作用
- 安全 HTTP 事务会首先获取服务器的安全证书,安全证书是知名机构颁发的,安全证书上包含站点的公钥,客户端使用这个公钥验证该安全证书的数字签名
- https 的端口是443
- tcp 建立连接后,通信双方会初始化 ssl 层,对加密参数进行沟通,叫做 SSL 握手,握手完成后才开始交换正式报文
- 如果有代理,代理是不能解析加密之后的流量的,那么需要明文告知代理建立一条隧道来传输
第十五章 实体和编码
常见的实体首部
Content-Type
- 实体中所承载对象的类型。其值是标准化的MIME类型,比如 text/html,image/git 等。
- 后面还可以加个参数 charset。比如
Content-Type: text/html; charset=iso-8859-4
用于转换成特定的字符编码。 - 还有一种参数 boundary 来定义分隔符。
- boundary 的第一种用法(当在客户端的多元素表单提交时,作用在客户端):
- 上图我写了一个表单,提交了一段文字(333),一个文件和一张图片后发送的数据包的样子。
- 使用
boundary=----WebKitFormBoundarybA9UqAdEA5kH0U5F
定义了分隔符。
- boundary 的第二种用法(当响应客户端的多范围请求时,作用在服务端):
- 当我在客户端的请求带有
Range: bytes=100-200, 300-400
首部时,服务器用 boundary 的值做分隔按顺序分段传送需要的数据。
- 当我在客户端的请求带有
- boundary 的第一种用法(当在客户端的多元素表单提交时,作用在客户端):
Content-Length
- 所传送实体主体的长度或大小。
- 如果对文本文件进行了 gzip 压缩的话,Content-Length 就是压缩后的大小。
- 除非使用了分块编码,否则 Content-Length 就是带有实体主体的报文必须使用的。
- 使用 Content-Length 是为了能够检测出服务器崩溃而导致的报文只发送了部分,这被称为报文截尾,并且 Content-Length 对持久连接的多个响应报文进行分段时有很重要的作用。
- 有一种情况在使用持久连接时可以没有 Content-Length,即采用分块编码 (chunked encoding)。在分块编码的情况下,数据是分为一系列的块来发送的,每块都有大小说明。哪怕服务器在生成首部的时候不知道整个实体的大小(通常是因为实体是动态生成的),仍然可以使用分块编码传输若干已知大小的块。
- 确定实体主体长度的规则(太长了,偷个懒):
Content-Language
与所传送对象最相匹配的人类语言。
Content-Encoding
客户端有个 Accept-Encoding 的首部,用于告知服务器可接受的内容编码,如果不写表示可接收任何编码方式(等价于发送 Accept-Encoding: *)。
其中 q 表示优先级,1.0优先级最高,0表示不接收这类编码。identity 表示不编码。
Content-Location
一个备用位置,请求时可通过它获得对象。
Content-Range
如果这是部分实体,这个首部说明它是整体的哪个部分。
Content-MD5
- 实体主体内容的校验和。服务器把实体主体计算 MD5然后放到 Content-MD5首部中,用来给客户端检测报文是否被修改或损坏。如果被传输编码了则先要解码后计算,如果被内容编码了不需要解码,直接算内容编码后的 MD5。
- Content-MD5除了用来检测完整性,还可以当做散列表的关键字,用来快速定位文档并消除不必要的重复内容存储。
Last-Modified
所传输内容在服务器上创建或最后修改的日期时间。
Expires
实体将要失效的日期时间。
Allow
该资源所允许的各种请求方法,比如 GET 和 HEAD。
ETag
这份文档特定实例的唯一验证码。ETag 首部没有正式定义为实体首部,但它对许多涉及实体的操作来说,都是一个重要的首部。
Cache-Control
指出应该如何缓存该文档。和 ETag 首部类似,Cache-Control 首部也没有正式定义为实体首部。
传输编码之分块编码
- 传输编码和内容编码一样,也是作用于实体主体上的可逆变换,但使用它们是由于架构方面的原因,同内容的格式无关。
- 使用传输编码是为了改变报文中的数据在网络上传输的方式。最新的 HTTP 规范只定义了一种传输编码,就是分块编码。
- 分块编码把报文分割为若干个大小已知的块。块之间是紧挨着发送的,这样就不需要在发送之前知道整个报文的大小了。要注意的是,分块编码是一种传输编码,因此是报文的属性,而不是主体的属性。
- 分块与持久连接这段都很重要,就直接贴出来了:
内容编码与传输编码的结合
范围请求
- 请求首部形如:
Range: bytes=20224-
[或上面讲到的多范围请求] - 响应首部形如:
Content-Range: bytes 20224-40000/40001
- 在客户端完整请求服务器数据时,一般服务器返回的首部会带有
Accept-Ranges: bytes
表示自己可以接收 range 请求,如果客户端在请求时中断,可以通过 Range 从断点续传。Range 首部在流行的点对点 (Peer-to-Peer, P2P) 文件共享客户端软件中得到了广泛的应用。
差异编码
简单来讲就是服务器不传送全部数据,只传送变化的数据。
其他
- 本章主要介绍有关报文实体的内容
- 报文实体由两部分组成:实体首部和实体主体
- 实体首部是用来描述主体信息的
- 实体主体就是要传输的原始信息
- 如果实体主体是是经过内容编码的,Content-Length 就是编码后的长度
- 多部分主体:一个报文实体中包含多个不同的实体部分,每个实体都包含实体首部和主体,相互之间使用特定的分隔符分开
- 内容编码就是压缩方式
- 传输编码是改变数据的传输方式,比如分块编码,将报文分割为不同的块进行传输
- 实例:同样的 URL 指向资源对象会随着时间而变化,对象的不同版本称为一个实例
- HTTP 规定了实例操控的响应和请求操作,用来操控对象的实例
- 实例操控的主要方法是范围请求和差异编码
- 如果客户端拥有的实例不再新鲜,那么就要请求新的实例,范围请求可以让客户端请求文档的一部分
- 范围请求在 P2P 中使用广泛
- 差异编码:发送实例改变的部分【改变了一个符号等等】
- 差异编码中,服务器会发送一系列的差异指令,告诉客户端修改哪些部分,涉及到的首部有ETag、IM等
第十六章 国际化
- Accept-Charset:表明客户端支持的字符系统有哪些,比如
Accept-Charset: iso-8859-1, utf-8
,服务器可通过首部Content-Type: text/html; charset=utf-8
响应。如果服务器没有给 charset,客户端可能就要设法从文档内容中推断出字符集,比如 HTML 中可以通过<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
定义字符集。 - Accept-Language:表明客户端支持的语言,比如
Accept-Language: es
,服务器响应首部使用 Content-Language,如果响应的内容由两种语言组成,可以写为Content-Language: es, en
。
其他
- 主要就是字符集相关的东西
- 在 Content-type 首部的 charset 参数里指定字符集
- 字符集就是如何将主体的二进制编码转换为字符
- 编码:按照编码后字符集的映射规则,把字符映射为字符代码,按照字符集的编码方案把字符代码转换为二进制码,简单来说就是:字符–数字–二进制
- 字符编码方案有这么几种:固定宽度【编码后每个字符所占比特固定】、可变宽度、可变宽度【有模态:可以在文本中切换使用不同的字符集】
- http 首部必须由US-ASCII字符集中的字符构成
第十七章 内容协商与转码
内容协商
内容协商简单来讲,就是客户端和服务器端协商要发送的内容的格式。一般是客户端给服务器提供选择,服务器在里面根据自己的情况,以最大意愿满足客户端请求并发送内容。
因为客户端驱动的方式比较慢,还繁琐,所以不怎么用了,主要还是以服务器驱动为主。服务器驱动的常用首部:
Accept首部,一般可以带个质量值q,用来表明给出的选择的优先级。比如:Accept-Language: en;q=0.5, fr;q=0.0, nl;q=1.0, tr;q=0.0
,表示客户端告诉服务器,优先发荷兰 (nl) 语的版本,如果没有就发英语 (en) 的,但一定不要发法语 (fr) 和土耳其语 (tr)。
其中有个 Vary 首部,服务器用来告知中间代理缓存根据客户端不同的 Vary 首部值缓存不同的内容。举个例子,比如服务器的响应首部带有 Vary: Accept-Language
,代理缓存收到这个首部之后,会根据客户端不同的 Accept-Language 缓存多份数据。见下图:
上图代理缓存服务器根据请求语言的不同缓存了3份不同语言的文档。
转码
转码操作不仅可以在服务器上完成,还可以在代理缓存上完成。如下图:
其他
- 这一章主要讲服务器如何发送合适的内容
- 内容协商基于客户端和服务器或代理实现
- 基于客户端就是服务器发送可选项,客户端来选择
- 基于服务端就是服务器根据客户端的首部集来选择发送
- 基于代理的透明协商就是让代理代替客户端协商
- 基于服务端的所涉及的首部集主要有 Accept- 族【请求】、User-Agent 等
- 更加细化的偏好可以使用质量值【q】来确定
- 基于代理的设计中,服务器可以向代理发送vary首部来指定代理可以根据哪些首部来协商
- 转码:如果服务器中没有客户端需要的版本,就需要进行转码,转换成客户端需要的【比如插入广告】等
第十八章 Web 主机托管
- 这章主要是讲怎么将网站放到服务器上供他人访问。有些内容过时了。我知道的,现在如果个人想这样做,一般是去网上申请个域名,然后租一个 VPS,装个 Linux 系统,再装一个 web 服务器,再做下配置一下,把网页放上去就 OK 了。后来我听说还可以把网站搭到内网(这样就可以不需要花钱租 VPS 了,但要时刻保持开机状态),比如我们家里
的网络上,然后用花生壳之类的东西做内网穿透,也可以供其他人访问,但我没试过,不是太了解,等以后我要是亲自操作了我再单独写一篇吧。
第十九章 发布系统
- 书中提到 FrontPage 和 WebDAV。FrontPage 在2006年停止更新了,而 WebDAV 目前貌似国内只有坚果云在用,而且是用在云网盘上。
第二十章 重定向与负载均衡
12种重定向方法
HTTP 重定向(通用)
最常见到的重定向:
服务器响应301或302并带一个 Location 首部,浏览器收到后会自动访问 Location 中的地址:
但是 HTTP 重定向有几个缺点:
- 增加了用户时延,因为访问页面需要两次往返。
- 如果重定向服务器发生故障,站点就会瘫痪。
- 传输的内容越少,因引入重定向服务器的代价越大。
DNS重定向(通用)
客户端向 DNS 服务器发送请求后,DNS 服务器返回多个 IP,客户端通常会选择第一个 IP 访问。
基于 DNS 的重定向算法:
- 轮转算法:就是把每个 IP 轮流放在响应的最前面。
- 负载均衡算法:把负载最轻的 Web 服务器放在最前面。
- 邻近路由算法:把距离客户端最近的服务器放在最前面(CDN 通常的实现方法,但是有个缺点,公司内部的权威 DNS 服务器通常获取到的是客户端的本地 DNS 服务器 IP 而不是客户端实际的 IP)。
- 故障屏蔽算法:DNS 服务器监视网络状况,将请求绕过出现服务中断或故障的服务器。
任播寻址(通用)
让多个服务器使用同一个 IP 地址,并且将服务器伪造成路由器,向其他周边路由器发送通知。客户端发送的请求会通过骨干路由器的“最短路径”路由功能将其发送给离它最近的服务器。
第二十一章 日志记录与使用情况跟踪
(略)