http 概览
1、四层模型:应用层、传输层、网际层、链接层
2、IP协议主要解决寻址和路由问题
3、ipv4,地址是四个用“.”分隔的数字,总数有2^32个,大约42亿个可以分配的地址
4、ipv6,地址是八个用“:”分隔的数字,总数有2^128个。
5、TCP协议位于IP协议之上,基于IP协议提供可靠的(数据不丢失)、字节流(数据完整)形式的通信,是HTT P协议得以实现的基础
6、域名系统:为了更好的标记不同国家或组织的主机,域名被设计成了一个有层次的结构
7、域名用“.”分隔成多个单词,级别从左到右逐级升高。
8、域名解析:将域名做一个转换,映射到它的真实IP
9、URI:统一资源标识符;URL:统一资源定位符
10、URI主要有三个基本部分构成:协议名、主机名、路径
11、HTTPS:运行在SSL/TLS协议上的HTTP
12 、SSL/TLS:建立在TCP/IP之上的负责加密通信的安全协议,是可靠的传输协议,可以被用作HTTP的下 层
13、代理(Proxy):是HTTP协议中请求方和应答方中间的一个环节。既可以转发客户端的请求,也可以转 发服务器的应答。
14、代理常见种类:匿名台历、透明代理、正向代理、反向代理
15、代理可以做的事:负载均衡、内容缓存、安全防护、数据处理。
16、 CDN,全称是“Content Delivery Network”,翻译过来就是“内容分发网络”。它应用了HTTP协议里的缓存和代理技术,代替源站响应客户端的请求。
代理(Proxy)是HTTP协议中请求方和应答方中间的一个环节
代理有很多的种类,常见的有:
- 匿名代理:完全“隐匿”了被代理的机器,外界看到的只是代理服务器;
- 透明代理:顾名思义,它在传输过程中是“透明开放”的,外界既知道代理,也知道客户端;
- 正向代理:靠近客户端,代表客户端向服务器发送请求;
- 反向代理:靠近服务器端,代表服务器响应客户端的请求;
CDN,实际上就是一种代理,它代替源站服务器响应客户端的请求,通常扮演着透明代理和反向代理
dns
DNS的核心系统是一个三层的树状、分布式服务,基本对应域名的结构:
- 根域名服务器(Root DNS Server):管理顶级域名服务器,返回“com”“net”“cn”等顶级域名服务 器的IP地址;
- 顶级域名服务器(Top-level DNS Server):管理各自域名下的权威域名服务器,比如com顶级域名服务 器可以返回apple.com域名服务器的IP地址;
- 权威域名服务器(Authoritative DNS Server):管理自己域名下主机的IP地址,比如apple.com权威域名 服务器可以返回www.apple.com的IP地址。
例如,你要访问“www.apple.com”,就要进行下面的三次查询:
- 访问根域名服务器,它会告诉你“com”顶级域名服务器的地址;
- 访问“com”顶级域名服务器,它再告诉你“apple.com”域名服务器的地址;
- 最后访问“apple.com”域名服务器,就得到了“www.apple.com”的地址。
虽然核心的DNS系统遍布全球,服务能力很强也很稳定,但如果全世界的网民都往这个系统里挤,即使不挤 瘫痪了,访问速度也会很慢。 所以在核心DNS系统之外,还有两种手段用来减轻域名解析的压力,并且能够更快地获取结果,基本思路就 是“缓存”。
首先,许多大公司、网络运行商都会建立自己的DNS服务器,作为用户DNS查询的代理,代替用户访问核心 DNS系统。这些“野生”服务器被称为“非权威域名服务器”,可以缓存之前的查询结果,如果已经有了记 录,就无需再向根服务器发起查询,直接返回对应的IP地址。
这些DNS服务器的数量要比核心系统的服务器多很多,而且大多部署在离用户很近的地方。比较知名的DNS 有Google的“8.8.8.8”,Microsoft的“4.2.2.1”,还有CloudFlare的“1.1.1.1”等等。
其次,操作系统里也会对DNS解析结果做缓存,如果你之前访问过“www.apple.com”,那么下一次在浏 览器里再输入这个网址的时候就不会再跑到DNS那里去问了,直接在操作系统里就可以拿到IP地址。
另外,操作系统里还有一个特殊的“主机映射”文件,通常是一个可编辑的文本,在Linux里 是“/etc/hosts”,在Windows里是“C:\WINDOWS\system32\drivers\etc\hosts”,如果操作系统在缓存 里找不到DNS记录,就会找这个文件。
有了上面的“野生”DNS服务器、操作系统缓存和hosts文件后,很多域名解析的工作就都不用“跋山涉 水”了,直接在本地或本机就能解决,不仅方便了用户,也减轻了各级DNS服务器的压力,效率就大大提升 了。
域名解析可以返回多个IP地址,所以一个域名可以对应多台主机,客户端收到多个IP地址 后,就可以自己使用轮询算法依次向服务器发起请求,实现负载均衡。
域名解析可以配置内部的策略,返回离客户端最近的主机,或者返回当前服务质量最好的主 机,这样在DNS端把请求分发到不同的服务器,实现负载均衡。
浏览器HTTP请求过程
- 浏览器从地址栏的输入中获得服务器的IP地址和端口号;
- 浏览器用TCP的三次握手与服务器建立连接;
- 浏览器向服务器发送拼好的报文;
- 服务器收到报文后处理请求,同样拼好报文再发给浏览器;
- 浏览器解析报文,渲染输出页面。
- 4次挥手
http报文是怎么样的
HTTP协议也是与TCP/UDP类似,同样也需要在实际传输的数据前附加一些头数据,不过与TCP/UDP不同的 是,它是一个“纯文本 纯 ”的协议,所以头数据都是ASCII码的文本,可以很容易地用肉眼阅读,不用借助程 序解析也能够看懂。
HTTP协议的请求报文和响应报文的结构基本相同,由三大部分组成:
- 起始行(start line):描述请求或响应的基本信息;
- 头部字段集合(header):使用key-value形式更详细地说明报文;
- 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。
这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但 与“header”对应,很多时候就直接称为“body”。
HTTP协议规定报文必须有header,但可以没有body,而且在header之后必须要有一个“空行”,也就 是“CRLF”,\r\n,十六机制的“0D0A”。
请求行
请求行由三部分构成:
- 请求方法:是一个动词,如GET/POST,表示对资源的操作;
- 请求目标:通常是一个URI,标记了请求方法要操作的资源;
- 版本号:表示报文使用的HTTP协议版本。 这三个部分通常使用空格(space)来分隔,最后要用CRLF换行表示结束。
1GET / HTTP/1.1
状态行
看完了请求行,我们再看响应报文里的起始行,在这里它不叫“响应行”,而是叫“状态行 状 ”(status line),意思是服务器响应的状态 服 。 比起请求行来说,状态行要简单一些,同样也是由三部分构成:
- 版本号:表示报文使用的HTTP协议版本;
- 状态码:一个三位数,用代码的形式表示处理的结果,比如200是成功,500是服务器错误;
- 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
1HTTP/1.1 200 OK
头部字段
不过使用头字段需要注意下面几点:
- 字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好;
- 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”。例如,“test-name”是合 法的字段名,而“test name”“test_name”是不正确的字段名;
- 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格;
- 字段的顺序是没有意义的,可以任意排列不影响语义;
- 字段原则上不能重复,除非这个字段本身的语义允许,例如Set-Cookie。
小结
- HTTP报文结构就像是“大头儿子”,由“起始行+头部+空行+实体”组成,简单地说就 是“header+body”;
- HTTP报文可以没有body,但必须要有header,而且header后也必须要有空行,形象地说就是“大 头”必须要带着“脖子”;
- 请求头由“请求行+头部字段”构成,响应头由“状态行+头部字段”构成;
- 请求行有三部分:请求方法,请求目标和版本号;
- 状态行也有三部分:版本号,状态码和原因字符串;
- 头部字段是key-value的形式,用“:”分隔,不区分大小写,顺序任意,除了规定的标准头,也可以任意 添加自定义字段,实现功能扩展;
- HTTP/1.1里唯一要求必须提供的头字段是Host,它必须出现在请求头里,标记虚拟主机名。
请求方法
- GET:获取资源,可以理解为读取或者下载数据;
- HEAD:获取资源的元信息;
- POST:向资源提交数据,相当于写入或上传数据;
- PUT:类似POST;
- DELETE:删除资源;
- CONNECT:建立特殊的连接隧道;
- OPTIONS:列出可对资源实行的方法;
- TRACE:追踪请求-响应的传输路径。
get/head
get: 是请求从服务器获取资源
在URI后使用“#”,就可以在获取页面后直接定位到某个标签所在的位置;使用If-Modified-Since字 段就变成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用Range字段就是“范围请求”, 只获取资源的一部分数据。
HEAD方法与GET方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返 回请求的实体数据,只会传回响应头,也就是资源的“元信息”。
HEAD方法可以看做是GET方法的一个“简化版”或者“轻量版”。因为它的响应头与GET完全相同,所以可 以用在很多并不真正需要资源的场合,避免传输body数据的浪费。
post/put
通常POST表示的是“新建”“create”的含义,而PUT则是“修改”“update”的含义
状态码
1××
1××类状态码属于提示信息,是协议处理的中间状态,实际能够用到的时候很少。
我们偶尔能够见到的是“101 Switching Protocols 1 ”。它的意思是客户端使用Upgrade头字段,要求在HTTP 协议的基础上改成其他的协议继续通信,比如WebSocket。而如果服务器也同意变更协议,就会发送状态码 101,但这之后的数据传输就不会再使用HTTP了。
2××
2××类状态码表示服务器收到并成功处理了客户端的请求,这也是客户端最愿意看到的状态码。
“200 OK 2 ”是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果,如果 是非HEAD请求,通常在响应头后都会有body数据。
“204 No Content 2 ”是另一个很常见的成功状态码,它的含义与“200 OK”基本相同,但响应头后没有 body数据。所以对于Web服务器来说,正确地区分200和204是很必要的。
“206 Partial Content 2 ”是HTTP分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的 部分数据时出现,它与200一样,也是服务器成功处理了请求,但body里的数据不是资源的全部,而是其中 的一部分。
状态码206通常还会伴随着头字段“Content-Range C ”,表示响应报文里body数据的具体范围,供客户端确 认,例如“Content-Range: bytes 0-99/2000”,意思是此次获取的是总计2000个字节的前100个字节。
3××
3××类状态码表示客户端请求的资源发生了变动,客户端必须用新的URI重新发送请求获取资源,也就是通 常所说的“重定向”,包括著名的301、302跳转。
“301 Moved Permanently 3 ”俗称“永久重定向”,含义是此次请求的资源已经不存在了,需要改用改用新 的URI再次访问。
与它类似的是“302 Found 3 ”,曾经的描述短语是“Moved Temporarily M ”,俗称“临时重定向”,意思是 请求的资源还在,但需要暂时用另一个URI来访问。
301和302都会在响应头里使用字段Location L 指明后续要跳转的URI,最终的效果很相似,浏览器都会重定向 到新的URI。两者的根本区别在于语义,一个是“永久”,一个是“临时”,所以在场景、用法上差距很 大。
比如,你的网站升级到了HTTPS,原来的HTTP不打算用了,这就是“永久”的,所以要配置301跳转,把所 有的HTTP流量都切换到HTTPS。
再比如,今天夜里网站后台要系统维护,服务暂时不可用,这就属于“临时”的,可以配置成302跳转,把 流量临时切换到一个静态通知页面,浏览器看到这个302就知道这只是暂时的情况,不会做缓存优化,第二 天还会访问原来的地址。
304 not Modified 是一个比较有意思的状态码,它用于If-Modified-Since等条件请求,表示资源未修 改,用于缓存控制。它不具有通常的跳转含义,但可以理解成“重定向已到缓存的文件”
4××
4××类状态码表示客户端发送的请求报文有误,服务器无法处理,它就是真正的“错误码”含义了。
“400 Bad Request 4 ”是一个通用的错误码,表示请求报文有错误,但具体是数据格式错误、缺少请求头还 是URI超长它没有明确说,只是一个笼统的错误,客户端看到400只会是“一头雾水”“不知所措”。所 以,在开发Web应用时应当尽量避免给客户端返回400,而是要用其他更有明确含义的状态码。
“403 Forbidden 4 ”实际上不是客户端的请求出错,而是表示服务器禁止访问资源。原因可能多种多样,例 如信息敏感、法律禁止等,如果服务器友好一点,可以在body里详细说明拒绝请求的原因,不过现实中通 常都是直接给一个“闭门羹”。
“404 Not Found 4 ”可能是我们最常看见也是最不愿意看到的一个状态码,它的原意是资源在本服务器上未 找到,所以无法提供给客户端。但现在已经被“用滥了”,只要服务器“不高兴”就可以给出个404,而我 们也无从得知后面到底是真的未找到,还是有什么别的原因,某种程度上它比403还要令人讨厌。
405 Method Not Allowed:不允许使用某些方法操作资源,例如不允许POST只能GET;
406 Not Acceptable:资源无法满足客户端请求的条件,例如请求中文但只有英文;
408 Request Timeout:请求超时,服务器等待了过长的时间;
409 Conflict:多个请求发生了冲突,可以理解为多线程并发时的竞态;
413 Request Entity Too Large:请求报文里的body太大;
414 Request-URI Too Long:请求行里的URI太大;
429 Too Many Requests:客户端发送了太多的请求,通常是由于服务器的限连策略;
431 Request Header Fields Too Large:请求头某个字段或总体太大;
5xx
5××类状态码表示客户端请求报文正确,但服务器在处理时内部发生了错误,无法返回应有的响应数据,是 服务器端的“错误码”。
“500 Internal Server Error 5 ”与400类似,也是一个通用的错误码,服务器究竟发生了什么错误我们是不知 道的。不过对于服务器来说这应该算是好事,通常不应该把服务器内部的详细信息,例如出错的函数调用栈 告诉外界。虽然不利于调试,但能够防止黑客的窥探或者分析。
“501 Not Implemented 5 ”表示客户端请求的功能还不支持,这个错误码比500要“温和”一些,和“即将 开业,敬请期待”的意思差不多,不过具体什么时候“开业”就不好说了。
“502 Bad Gateway ” 通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常. 访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
503 Service Unavailable: 表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服 务正忙,请稍后重试”的提示信息就是状态码503。
503是一个“临时”的状态,很可能过几秒钟后服务器就不那么忙了,可以继续提供服务,所以503响应报 文里通常还会有一个“Retry-After R ”字段,指示客户端可以在多久以后再次尝试发送请求。
http特点
- HTTP是灵活可扩展的,可以任意添加头字段实现任意功能;
- HTTP是可靠传输协议,基于TCP/IP协议“尽量”保证数据的送达;
- HTTP是应用层协议,比FTP、SSH等更通用功能更多,能够传输任意数据;
- HTTP使用了请求-应答模式,客户端主动发起请求,服务器被动回复请求;
- HTTP本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相 关的信息。
HTTP最大的优点是简单、灵活和易于扩展; 2. HTTP拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施; 3. HTTP是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用Cookie技术来实现“有状态”; 4. HTTP是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听; 5. HTTP是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改; 6. HTTP的性能不算差,但不完全适应现在的互联网,还有很大的提升空间。
头数据冗余,影响其性能
http 实体数据
- 数据类型表示实体数据的内容是什么,使用的是MIME type,相关的头字段是Accept和Content-Type;
- 数据编码表示实体数据的压缩方式,相关的头字段是Accept-Encoding和Content-Encoding;
- 语言类型表示实体数据的自然语言,相关的头字段是Accept-Language和Content-Language;
- 字符集表示实体数据的编码方式,相关的头字段是Accept-Charset和Content-Type;
- 客户端需要在请求头里使用Accept等头字段与服务器进行“内容协商”,要求服务器返回最合适的数 据;
- Accept等头字段可以用“,”顺序列出多个可能的选项,还可以用“;q=”参数来精确指定权重。
http 传输大文件
数据压缩
通常浏览器在发送请求时都会带着“Accept-Encoding”头字段,里面是浏览器支持的压缩格式列表,例如 gzip、deflate、br等,这样服务器就可以从中选择一种压缩算法,放进“Content-Encoding”响应头里,再 把原数据压缩后发给浏览器。
gzip等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多 媒体数据本身就已经是高度压缩的,再用gzip处理也不会变小(甚至还有可能会增大一点),所以它就失效 了。
分快传输
如果大文件整体不能变小,那就把它“拆开”,分解成多个小块,把这些小块分批发给浏览器,浏览器收到后再组装复原。
在响应报文里用头字段“Transfer-Encoding: chunked ”来表示,意思是报文里的body部分不是一次性发过来的,而是分成了许 多的块(chunk)逐个发送
分块传输也可以用于“流式数据”,例如由数据库动态生成的表单页面,这种情况下body数据的长度是未 知的,无法在头字段“Content-Length C ”里给出确切的长度,所以也只能用chunked方式分块发送。
“Transfer-Encoding: chunked”和“Content-Length”这两个字段是互斥的 ,也就是说响应报文里这两个 字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)。
分快传输编码规则
- 每个分块包含两个部分,长度头和数据块;
- 长度头是以CRLF(回车换行,即\r\n)结尾的一行明文,用16进制数字表示长度;
- 数据块紧跟在长度头后,最后也用CRLF结尾,但数据不包含CRLF;
- 最后用一个长度为0的块表示结束,即“0\r\n\r\n”。
范围请求
有了分块传输编码,服务器就可以轻松地收发大文件了,但对于上G的超大文件,还有一些问题需要考虑。
比如,你在看当下正热播的某穿越剧,想跳过片头,直接看正片,或者有段剧情很无聊,想拖动进度条快进 几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力。
范围请求不是Web服务器必备的功能,可以实现也可以不实现,所以服务器必须在响应头里使用字 段“Accept-Ranges: bytes ”明确告知客户端:“我是支持范围请求的”。
如果不支持的话该怎么办呢?服务器可以发送“Accept-Ranges: none”,或者干脆不发送“AcceptRanges”字段,这样客户端就认为服务器没有实现范围请求功能,只能老老实实地收发整块文件了。
请求头Range
是HTTP范围请求的专用字段,格式是“bytes=x-y”,其中的x和y是以字节为单位的数据范围。
Range的格式也很灵活,起点x和终点y可以省略,能够很方便地表示正数或者倒数的范围。假设文件是100 个字节,那么:
- “0-”表示从文档起点到文档终点,相当于“0-99”,即整个文件;
- “10-”是从第10个字节开始到文档末尾,相当于“10-99”;
- “-1”是文档的最后一个字节,相当于“99-99”;
- “-10”是从文档末尾倒数10个字节,相当于“90-99”。
服务器收到Range字段后,需要做四件事:
第一,它必须检查范围是否合法,比如文件只有100个字节,但请求“200-300”,这就是范围越界了。服务器就会返回状态码416,意思是“你的范围请求有误,我无法处理,请再检查一下”。
第二,如果范围正确,服务器就可以根据Range头计算偏移量,读取文件的片段了,返回状态码“206 Partial Content ”,和200的意思差不多,但表示body只是原数据的一部分。
第三,服务器要添加一个响应头字段Content-Range ,告诉片段的实际偏移量和资源的总大小,格式 是“bytes x-y/length”,与Range头区别在没有“=”,范围后多了总长度。例如,对于“0-10”的范围请 求,值就是“bytes 0-10/100”。
最后剩下的就是发送数据了,直接把片段用TCP发给客户端,一个范围请求就算是处理完了。
例如下面的这个请求使用Range字段获取了文件的前32个字节:
1GET /16-2 HTTP/1.1
2Host: www.chrono.com
3Range: bytes=0-31
返回的数据是(去掉了几个无关字段):
1HTTP/1.1 206 Partial Content
2Content-Length: 32
3Accept-Ranges: bytes
4Content-Range: bytes 0-31/96
5// this is a plain text json doc
有了范围请求之后,HTTP处理大文件就更加轻松了,看视频时可以根据时间点计算出文件的Range,不用 下载整个文件,直接精确获取片段所在的数据内容。
不仅看视频的拖拽进度需要范围请求,常用的下载工具里的多段下载、断点续传也是基于它实现的,要点 是:
- 先发个HEAD,看服务器是否支持范围请求,同时获取文件的大小;
- 开N个线程,每个线程使用Range字段划分出各自负责下载的片段,发请求传输数据;
- 下载意外中断也不怕,不必重头再来一遍,只要根据上次的下载记录,用Range请求剩下的那一部分就可 以了。
多段数据
它还支持在Range头里使用多个“x-y”,一次性获取多个片段数据。
这种情况需要使用一种特殊的MIME类型:“multipart/byteranges”,表示报文的body是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”给出段之间的分隔标记。
每一个分段必须以“–boundary”开始(前面加两个“-”),之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据, 最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束。
boundary替换成自己定义的
1GET /16-2 HTTP/1.1
2Host: www.chrono.com
3Range: bytes=0-9, 20-29
1HTTP/1.1 206 Partial Content
2Content-Type: multipart/byteranges; boundary=00000000001
3Content-Length: 189
4Connection: keep-alive
5Accept-Ranges: bytes
6--00000000001
7Content-Type: text/plain
8Content-Range: bytes 0-9/96
9// this is
10--00000000001
11Content-Type: text/plain
12Content-Range: bytes 20-29/96
13ext json d
14--00000000001--
报文里的“- -00000000001”就是多段的分隔符,使用它客户端就可以很容易地区分出多段Range 数据。
小结
- 压缩HTML等文本文件是传输大文件最基本的方法;
- 分块传输可以流式收发数据,节约内存和带宽,使用响应头字段“Transfer-Encoding: chunked”来表 示,分块的格式是16进制长度头+数据块;
- 范围请求可以只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字 段“Range”和响应头字段“Content-Range”,响应状态码必须是206;
- 也可以一次请求多个范围,这时候响应报文的数据类型是“multipart/byteranges”,body里的多个部分 会用boundary字符串分隔。
http的连接管理
连接相关的头字段
在HTTP/1.1中的连接都会默认启用长连接
不需要用什么特殊的头字段指定,只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的TCP连接,也就是 长连接,在这个连接上收发数据。
当然,我们也可以在请求头里明确地要求使用长连接机制,使用的字段是Connection,值是“keep-alive”
不过不管客户端是否显式要求长连接,如果服务器支持长连接,它总会在响应报文里放一个“Connection:keep-alive”字段,告诉客户端:“我是支持长连接的,接下来就用这个TCP一直收发数据吧”。
因为TCP连接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。如果有大量的 空闲长连接只连不发,就会很快耗尽服务器的资源,导致服务器无法为真正有需要的用户提供服务。
在客户端,可以在请求头里加上“Connection: close”字段,告诉服务器:“这次通信后就关闭连接”。服 务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API关闭TCP连接。
服务器端通常不会主动关闭连接,nginx 来说
- 使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发 就主动断开连接,避免空闲连接占用系统资源。
- 使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成1000,那么当 Nginx在这个连接上处理了1000个请求后,也会主动断开连接
实验环境配置了“keepalive_timeout 60”和“keepalive_requests 5”,意思是空闲连接最多60秒, 最多发送5个请求。所以,如果连续刷新五次页面,就能看到响应头里的“Connection: close”了
对头阻塞
“队头阻塞"与短连接和长连接无关,而是由HTTP基本的“请求-应答”模型所导致的。
因为HTTP规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求没有轻 重缓急的优先级,只有入队的先后顺序,排在最前面的请求被最优先处理。
如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是 其他的请求承担了不应有的时间成本。
还是用打卡机做个比喻。 上班的时间点上,大家都在排队打卡,可这个时候偏偏最前面的那个人遇到了打卡机故障,怎么也不能打卡 成功,急得满头大汗。等找人把打卡机修好,后面排队的所有人全迟到了
性能优化
因为“请求-应答”模型不能变,所以“队头阻塞”问题在HTTP/1.1里无法解决,只能缓解,有什么办法 呢?
公司里可以再多买几台打卡机放在前台,这样大家可以不用挤在一个队伍里,分散打卡,一个队伍偶尔阻塞 也不要紧,可以改换到其他不阻塞的队伍。
这在HTTP里就是“并发连接”(concurrent connections),也就是同时对一个域名发起多个长连接,用数 量来解决质量的问题。
HTTP协议建议客户端使用并发,但不能“滥用”并发。RFC2616里明确限制每个客户端最多并发2个连接。不过实践证明这个数字实在是太小了,众多浏览器都“无视”标准,把这个上限提高到了6~8。后来 修订的RFC7230也就“顺水推舟”,取消了这个“2”的限制。
公司发展的太快了,员工越来越多,上下班打卡成了迫在眉睫的大问题。前台空间有限,放不下更多的打卡 机了,怎么办?那就多开几个打卡的地方,每个楼层、办公区的入口也放上三四台打卡机,把人进一步分 流,不要都往前台挤。
这个就是“域名分片 域 ”(domain sharding)技术,还是用数量来解决质量的思路。
HTTP协议和浏览器不是限制并发连接数量吗?好,那我就多开几个域名,比如shard1.chrono.com、 shard2.chrono.com,而这些域名都指向同一台服务器www.chrono.com,这样实际长连接的数量就又上去 了,真是“美滋滋”。不过实在是有点“上有政策,下有对策”的味道。
小结
- 早期的HTTP协议使用短连接,收到响应后就立即关闭连接,效率很低;
- HTTP/1.1默认启用长连接,在一个连接上收发多个请求响应,提高了传输效率;
- 服务器会发送“Connection: keep-alive”字段表示启用了长连接;
- 报文头里如果有“Connection: close”就意味着长连接即将关闭;
- 过多的长连接会占用服务器资源,所以服务器会用一些策略有选择地关闭长连接;
- “队头阻塞”问题会导致性能下降,可以用“并发连接”和“域名分片”技术缓解。
- ddos 是利用长连接对服务器发起大量请求,耗尽服务器资源,“拒绝服务”
重定向
- 重定向是服务器发起的跳转,要求客户端改用新的URI重新发送请求,通常会自动进行,用户是无感知 的;
- 301/302是最常用的重定向状态码,分别是“永久重定向”和“临时重定向”;
- 响应头字段Location指示了要跳转的URI,可以用绝对或相对的形式;
- 重定向可以把一个URI指向另一个URI,也可以把多个URI指向同一个URI,用途很多;
- 使用重定向时需要当心性能损耗,还要避免出现循环跳转。
HTTP的Cookie机制
- Cookie是服务器委托浏览器存储的一些数据,让服务器有了“记忆能力”;
- 响应报文使用Set-Cookie字段发送“key=value”形式的Cookie值;
- 请求报文里用Cookie字段发送多个Cookie值;
- 为了保护Cookie,还要给它设置有效期、作用域等属性,常用的有Max-Age、Expires、Domain、 HttpOnly等;
- Cookie最基本的用途是身份识别,实现有状态的会话事务。
如果Cookie的Max-Age属性设置为0,cookie就会马上失效了。
cookie 一般大小不超过4k。
Cookie的有效期可以使用Expires和Max-Age两个属性来设置。
“Expires”俗称“过期时间”,用的是绝对时间点,可以理解为“截止日期”(deadline)。“Max-Age”用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上Max-Age,就可以得到失效的绝对时 间。
Expires和Max-Age可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用Max-Age 计算失效期。
设置Cookie的作用域,让浏览器仅发送给特定的服务器和URI,避免被其他网站盗用
作用域的设置比较简单,“Domain”和“Path”指定了Cookie所属的域名和路径,浏览器在发送Cookie前 会从URI中提取出host和path部分,对比Cookie的属性。如果不满足条件,就不会在请求头里发送Cookie。
使用这两个属性可以为不同的域名和路径分别设置各自的Cookie,比如“/19-1”用一个Cookie,“/19- 2”再用另外一个Cookie,两者互不干扰。不过现实中为了省事,通常Path就用一个“/”或者直接省略,表 示域名下的任意路径都允许使用Cookie,让服务器自己去挑。
属性“HttpOnly”会告诉浏览器,此Cookie只能通过浏览器HTTP协议传输,禁止其他方式访问,浏览器的 JS引擎就会禁用document.cookie等一切相关的API,脚本攻击也就无从谈起了。
还有一个属性叫“Secure”,表示这个Cookie仅能用HTTPS协议加密传输,明文的HTTP协议会禁止发送。 但Cookie本身不是加密的,浏览器里还是以明文的形式存在。
http 缓存控制
服务器缓存控制
- 缓存是优化系统性能的重要手段,HTTP传输的每一个环节中都可以有缓存;
- 服务器使用“Cache-Control”设置缓存策略,常用的是“max-age”,表示资源的有效期;
响应头
1Cache-control: max-age=30
服务器标记资源有效期使用的头字段是“Cache-Control”,里面的值“max-age=30”就是资源的有效时 间,相当于告诉浏览器,“这个页面只能缓存30秒,之后就算是过期,不能用。”
“max-age”是HTTP缓存控制最常用的属性,此外在响应报文里还可以用其他的属性来更精确地指示浏览 器应该如何使用缓存:
no_store:不允许缓存 ,用于某些变化非常频繁的数据,例如秒杀页面;
no_cache:它的字面含义容易与no_store搞混,实际的意思并不是不允许缓存,而是可以缓存 可 ,但在使用 之前必须要去服务器验证是否过期,是否有最新的版本;
must-revalidate:又是一个和no_cache相似的词,它的意思是如果缓存不过期就可以继续使用,但过期 了如果还想用就必须去服务器验证。
客户端的缓存控制
其实不止服务器可以发“Cache-Control”头,浏览器也可以发“Cache-Control”
当你点“刷新”按钮的时候,浏览器会在请求头里加一个“Cache-Control: max-age=0”。因为max-age 是“生存时间”,max-age=0的意思就是“我要一个最最新鲜的西瓜”,而本地缓存里的数据至少保存了几 秒钟,所以浏览器就不会使用缓存,而是向服务器发请求。服务器看到max-age=0,也就会用一个最新生成 的报文回应浏览器。
Ctrl+F5的“强制刷新”又是什么样的呢? 它其实是发了一个“Cache-Control: no-cache”,含义和“max-age=0”基本一样,就看后台的服务器怎么 理解,通常两者的效果是相同的。
试着点一下浏览器的“前进”“后退”按钮,再看开发者工具,你就会惊喜地发现“from disk cache”的字样,意思是没有发送网络请求,而是读取的磁盘上的缓存。
条件请求
条件请求一共有5个头字段,我们最常用的是“if-Modified-Since”和“If-None-Match”这两个。需要第一 次的响应报文预先提供“Last-modified”和“ETag”,然后第二次请求时就可以带上缓存里的原值,验证 资源是否是最新的。
如果资源没有变,服务器就回应一个“304 Not Modified”,表示缓存依然有效,浏览器就可以更新一下有 效期,然后放心大胆地使用缓存了。
“Last-modified”很好理解,就是文件的最后修改时间
ETag是“实体标签”(Entity Tag)的缩写,是资源的一个唯一标识 是 ,主要是用来解决修改时间无法准确区 分文件变化的问题。
比如,一个文件在一秒内修改了多次,但因为修改时间是秒级,所以这一秒内的新版本无法区分。
再比如,一个文件定期更新,但有时会是同样的内容,实际上没有变化,用修改时间就会误以为发生了变 化,传送给浏览器就会浪费带宽。
使用ETag就可以精确地识别资源的变动情况,让浏览器能够更有效地利用缓存。
浏览器收到数据就会存入缓存,如果没过期就可以直接使用,过期就要去服务器验证是否仍然可用;
验证资源是否失效需要使用“条件请求”,常用的是“if-Modified-Since”和“If-None-Match”,收到 304就可以复用缓存里的资源;
验证资源是否被修改的条件有两个:“Last-modified”和“ETag”,需要服务器预先在响应报文里设 置,搭配条件请求使用;
浏览器也可以发送“Cache-Control”字段,使用“max-age=0”或“no_cache”刷新数据。