前言
说到http协议,相信大家也都不陌生。毕竟是跟工作息息相关的,想着好好深入一下,正好看到了罗老师的《透视HTTP协议》,也算是说透了揉碎了,讲的明明白白。下面就里面的内容做了这篇学习总结
开始之前先抛出几个问题:
-
是什么在促进HTTP的发展?
-
post请求是什么样的?
-
缓存在浏览器network中的体现?
-
HTTP的大文件传输?一切都在发展,想想更好的方案
-
HTTP协议跟TCP/IP协议栈的关系
下面就开始唠了~~~
2HTTP 发展史
http发展史
HTTP 的发展历程
HTTP/0.9(90年代初)
受限于当时的大环境,只支持get的纯文本传输,后定义为HTTP/0.9 --- 是小范围的试炼
-
get纯文本数据的传输
HTTP/1.0(93年)
得益于jpeg及mp3等数据格式的问世,用户期待图文并茂的网页 --- 小有成就
-
增加了 HEAD、POST 等新方法;
-
增加了响应状态码,标记可能的错误原因;
-
引入了协议版本号概念;
-
引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
-
传输的数据不再仅限于文本。
HTTP/1.1(99年)
经历了第一次浏览器大战的HTTP/1.0扛住压力,推动了http的进一步完善。--- 破茧成蝶,正式成为标准(RFC 2616)
-
增加了 PUT、DELETE 等新的方法;
-
增加了缓存管理和控制;
-
明确了连接管理,允许持久连接;
-
允许响应数据分块(chunked),利于传输大文件;
-
强制要求 Host 头,让互联网主机托管成为可能。
不过由于 HTTP/1.1 太过庞大和复杂,所以在 2014 年又做了一次修订,原来的一个大文档被拆分成了六份较小的文档,编号为 7230-7235,优化了一些细节,但此外没有任何实质性的改动。
更上一层楼,HTTP/2(2015年)
“挟用户以号令天下”,Google 借此顺势把 SPDY 推上了标准的宝座,互联网标准化组织以 SPDY 为基础开始制定新版本的 HTTP 协议,最终在 2015 年发布了 HTTP/2,RFC 编号 7540。
-
二进制协议,不再是纯文本;
-
可发起多个请求,废弃了 1.1 里的管道;
-
使用专用算法压缩头部,减少数据传输量;
-
允许服务器主动向客户端推送数据;
-
增强了安全性,“事实上”要求加密通信。
追求极致,HTTP/3
这一次还是Google,而且它要“革自己的命”。2018 年,互联网标准化组织 IETF 提议将“HTTP over QUIC”更名为“HTTP/3”并获得批准,HTTP/3 正式进入了标准化制订阶段。
结合发展史的总结
-
从无到有
-
从简单到复杂
-
从实现到不断的优化扩展
-
从无人知晓到无人不晓
-
追求安全、高速、便捷的传输
综上,大家可以思考下第一个问题了,你觉得呢? 有兴趣的可以去看参考资料里的协议详解。
3HTTP 的报文
客户端
请求行 GET / HTTP/1.1
服务端
状态行 HTTP/1.1 200 OK
关于头字段
HTTP 协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:
-
通用字段:在请求头和响应头里都可以出现;Date Cache-Control, Connection,Date,Pragma,Transfer-Encoding,Upgrade
-
请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;Host,User-Agent,Referer Accept Accept-Charset Accept-Encoding Accept-Language, Authorization,Expect,From,If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,Max-Forwards, Range等
-
响应字段:仅能出现在响应头里,补充说明响应报文的信息;Accept-Ranges,Age,ETag,Location,Proxy-Authenticate,Retry-After,Server,Vary等
-
实体字段:它实际上属于通用字段,但专门描述 body 的额外信息。Content-Length,Allow,Content-Encoding,Content-Language, Content-Length,Content-Location,Content-MD5,Content-Range,Content-Type,Expires,Last-Modified
注意:1,请求头的限制:各个web服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。
apache2来说,它默认值是8k;Apache里,可按下面方式调整:LimitRequestLine 请求行的字节数 LimitRequestFields 请求头域的个数 默认值100 LimitRequestFieldSize 配置允许的请求头字节大小 默认值是8190 nginx默认的header长度上限是4k,也可根据情况配置,如下两种方式:client_header_buffer_size 16k; large_client_header_buffers 4 16k;
2,如果header头信息请求超过了,nginx会直接返回400错误 3,关于头字段大小写,尽量首字母大写 4,字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_” 。否则响应 (400 Bad Request)
综上,大家可以思考下第二个问题了,post请求是需要向服务端提交数据的,可要结合实体字段~ 有兴趣的在跟后端联调时抓包看看吧
4HTTP 的缓存机制
HTTP/1.0提供了Pragma及Expires的缓存字段,到HTTP/1.1扩展了更多头字段Cache-Control、Last-Modified / If-Modified-Since, Etag / If-None-Match等。
为什么用缓存
在传输中间有其他备份,不用每次都去服务器请求,链路更短响应更迅速。也就是:
-
减少了冗余的数据传输,节省了网费。
-
缓解了服务器的压力, 大大提高了网站的性能
-
加快了客户端加载网页的速度
怎么缓存的
http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源。
从网上抓了张图,如下:
另一张图,不信你还不懂
强缓存与协商缓存
chrome浏览器:
200 from memory cache 不访问服务器,直接读缓存,从内存中读取缓存。此时的数据是缓存到内存中的,当kill进程后,数据将不存在 200 from disk cache 不访问服务器,直接读缓存,从磁盘中读取缓存,当kill进程时,数据还是存在。304 Not Modified 访问服务器,发现数据没有更新,服务器返回此状态码。然后从缓存中读取数据。
命中强制缓存时,资源会显示from memory cache or from disk cache
缓存相关的头字段
通用首部字段
字段名称 | 说明 |
---|---|
Pragma | "no-cache"表示禁用缓存 (HTTP/1.0)已废弃 |
Cache-Control | no-store no-cache max-age等,请求响应的值页有区别 |
请求首部字段
字段名称 | 说明 |
---|---|
If-Modified-Since | 比较资源最后的更新时间是否一致 |
If-Unmodified-Since | 比较资源最后的更新时间是否不一致 |
If-Match | 比较 ETag 是否一致 |
If-None-Match | 比较 ETag 是否一致 |
响应首部字段
字段名称 | 说明 |
---|---|
ETag | 资源的匹配信息(通过强比较算法生成值) |
实体首部字段
字段名称 | 说明 |
---|---|
Expires | 资源的过期时间(HTTP/1.0),有弊端 |
Last-Modified | 资源的最后一次修改时间 |
关于Cache-Control
网上找了张图,方便大家理解~
综上,大家工作的过程中也留意下chrome的network,结合思考下~
5HTTP 的大文件传输
如何在有限的带宽下快捷的传输大文件呢?更有效的节省内存、带宽等资源。
数据压缩(把大文件整体变小)
客户端请求时带上Accept-Encoding(支持的压缩方式),响应时指定Content-Encoding(此响应的压缩方式),按此压缩后发给客户端。
gzip 等压缩算法通常只对文本文件有较好的压缩率,而图片、音频视频等多媒体数据本身就已经是高度压缩的,效果不好。在 Nginx 里就会使用“gzip on”指令,启用对“text/html”的压缩。
分块传输
如果大文件整体不能变小,那就把它“拆开”,分解成多个小块。
HTTP协议1.1版本提供了分开传输机制(Chunked transfer encoding)。也就是服务端的”化整为零“,即把大文件拆开,分成多个小块来传输。
注意:HTTP/2 中已经不支持 chunked 这一格式了,因为其本身提供了更加高级的流机制来实现类似功能。
Transfer-Encoding
这个响应头有下面几个值:
-
chunked:数据分块发送。此时应缺省 Content-Length 响应头。意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
-
compress:使用 Lempel-Ziv-Welch 算法进行传输的格式,目前没有浏览器在支持。
-
deflate:使用 deflate 压缩算法 zlib 结构。
-
gzip:使用 Lempel-Ziv coding 编码的压缩格式。
-
identity:标识身份函数(e.g. no compression, nor modification)。
也可以同时指定多个值,用逗号分隔,像这样:Transfer-Encoding: gzip, chunked。
“Transfer-Encoding: chunked”和“Content-Length”这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现。
范围请求
HTTP协议还提出了范围请求(range requests)的概念。允许客户端在请求头里使用专用字段来表示只获取文件的一部分,相当于是客户端的“化整为零”。
试想你正在追剧,想拖着看,这实际上是想获取一个大文件其中的片段数据,而这时就用到范围请求了。
相关头字段
请求头Range是 HTTP 范围请求的专用字段,格式是“bytes=x-y”,其中的 x 和 y 是以字节为单位的数据范围;响应头里有“Accept-Ranges: bytes”标识服务端支持范围请求的。
请求
bytes=x-y
-
表示第二个500字节:bytes=500-999
-
表示最后500个字节:bytes=-500
-
表示500字节以后的范围:bytes=500-
-
第一个和最后一个字节:bytes=0-0,-1
-
同时指定几个范围:bytes=500-600,601-999
请求报文
GET /test HTTP/1.1
Host: www.test.com
Range: bytes=0-31
响应报文
HTTP/1.1 206 Partial Content
Content-Length: 32
Accept-Ranges: bytes
Content-Range: bytes 0-31/96
先发个 HEAD,看服务器是否支持范围请求,同时获取文件的大小;开 N 个线程,每个线程使用 Range 字段划分出各自负责下载的片段,发请求传输数据;下载意外中断也不怕,不必重头再来一遍,只要根据上次的下载记录,用 Range 请求剩下的那一部分就可以了。
范围请求-多段数据
-
刚才说的范围请求一次只获取一个片段,其实它还支持在 Range 头里使用多个“x-y”,一次性获取多个片段数据。请求头Range同时指定几个范围:bytes=500-600,601-999
-
特殊的 MIME 类型:“multipart/byteranges”,表示报文的 body 是由多段字节序列组成的,并且还要用一个参数“boundary=xxx”来分隔段。
多段数据的格式与分块传输也比较类似,但它需要用分隔标记 boundary 来区分不同的片段,可以通过图来对比一下。
每一个分段必须以“- -boundary”开始(前面加两个“-”),之后要用“Content-Type”和“Content-Range”标记这段数据的类型和所在范围,然后就像普通的响应头一样以回车换行结束,再加上分段数据,最后用一个“- -boundary- -”(前后各有两个“-”)表示所有的分段结束。
例如,Telnet 发出有两个范围的请求:
请求报文
GET /test2 HTTP/1.1
Host: www.test.com
Range: bytes=0-9, 20-29
响应报文
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000000001
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000000001
Content-Type: text/plain
Content-Range: bytes 0-9/96
// this is
--00000000001
Content-Type: text/plain
Content-Range: bytes 20-29/96
ext json d
--00000000001--
大文件传输的总结
-
压缩 HTML 等文本文件是传输大文件最基本的方法;
-
分块传输可以流式收发数据,节约内存和带宽,使用响应头字段“Transfer-Encoding: chunked”来表示,分块的格式是 16 进制长度头 + 数据块;
-
范围请求可以只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字段“Range”和响应头字段 “Content-Range”,响应状态码必须是 206;
-
也可以一次请求多个范围,这时候响应报文的数据类型是“multipart/byteranges”,body 里的多个部分会用 boundary 字符串分隔。要注意这四种方法不是互斥的,而是可以混合起来使用,例如压缩后再分块传输,或者分段后再分块,实验环境的 URI“/16-3”就模拟了后一种的情形,你可以自己用 Telnet 试一下。》综上,大家也思考下新时代里大文件传输的方式。五、相关的协议
6相关的协议
-
TCP/IP传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。
-
应用层的主要协议有HTTP、Telnet、FTP、SMTP等,是用来接收来自传输层的数据或者按不同应用要求与方式将数据传输至传输层;传输层的主要协议有UDP、TCP,是使用者使用平台和计算机信息网内部数据结合的通道,可以实现数据传输与数据共享;网络层的主要协议有ICMP、IP、IGMP,主要负责网络中数据包的传送等;而网络访问层,也叫网路接口层或数据链路层,主要协议有ARP、RARP,主要功能是提供链路管理错误检测、对不同通信媒介有关信息细节问题进行有效处理等。
下面是wireshake的抓包,访问http://127.0.0.1/
图片来源:罗老师的透析http协议
Wireshark 里就会有捕获的数据包(域名访问的话还会有dns解析)
图片来源:罗老师的透析http协议
浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;浏览器用 TCP 的三次握手与服务器建立连接;浏览器向服务器发送拼好的报文;服务器收到报文后处理请求,同样拼好报文再发给浏览器;浏览器解析报文,渲染输出页面。
上面是极简情况下,现在的大多数场景更复杂,如下图:NSFileHandle_3.png
综上 ,最后一个问题的答案也揭晓了