炸裂!万字长文拿下 HTTP 我在鹅厂等你!(1),html5 移动开发框架
加入 HTTP Header,让 HTTP 处理请求更加灵活
增加响应状态码,标记出错的原因
提供国际化(不同语言)支持
典型的请求过程
GET /image.html HTTP/1.0User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)200 OKDate: Tue, 17 Nov 2020 09:15:31 GMTContent-Type: text/html<HTML> 一个包含图片的页面 <IMG SRC="/image.gif"></HTML>
HTTP1.0 通信过程
HTTP1.0
HTTP /1.1
1995 年是不平凡的一年,网景公司和微软开启浏览器大战,谁都想当老大。1999 年 HTTP/1.1 发布并成为标准,写入 RFC,以为以后不管是网关还是 APP 等,只要你要使用 HTTP,就得遵守这个标准。
继续增加了 PUT 等方法
允许持久连接
随着文件越来越大,图片等信息越来越复杂,如果每一次上传下载文件都需要建立连接断开连接的过程将增加大量的开销。为此,提出了持久连接,也就是一次 TCP 连接可以具有多个 HTTP 请求。当然持久连接是可选择的,如果考虑关闭,只需要使用 Connecttion:close 关闭即可。长连接如下图所示
长连接
强制要求 Host 头
我们知道,在电商系统中,经常会因为促销活动导致流量飙升,为了缓解流量,其中有种方法即加缓存或者加服务器。如果是单台服务器负载过大,数据库可能分库分表。数据结构算法中分而治之方法亦是如此。那么 HTTP 中,同样的道理,如果文件太大,就大文件切分为小文件块发送。
HTTP /2
HTTP/1.1 的出现,几年间出来大量牛掰的互联网公司,发展实在是太快,但是 HTTP1.1 中这几点成为诟病
原因 1 TCP 自带慢启动
顾名思义,"慢启动"从 0 到 1 循循渐进。轿车启动不会按下按钮就直接起飞,而是缓慢调节到适合的速度。这不是挺好的?为什么会带来性能问题呢。我们知道一个页面有静态数据,动态页面,很多小文件在加载的过程中就会直接发起请求,这样导致太多的请求都会经历慢启动过程,花费时间太多。
原因 2 多条 TCP 连接带宽竞争
带宽固定,多条 TCP 连接同时发起竞争带宽资源,由于各个 TCP 连接之间没有通信机制,也无法得知哪些资源优先级更高,从而导致想快速下载的资源反而延迟下载。
原因 3 头部阻塞
阻塞,在网络编程中,我们采用异步,多路复用(epoll)方式尽量让 cpu 少等待多干事。在 HTTP1.1 中,虽然大家共用了一条 TCP 通道,但是第一个请求没有结束,第二请求就可能阻塞等待,也就是说不能同时发送接收数据。那么一个网页很多数据文件,如果能够同时发出请求,让部分数据文件能够得到响应并预处理,这样就大大的利用了带宽和 cpu 的资源。基于这些因素,在 HTTP2 中出现了新的方案
如何解决头部阻塞呢?
HTTP 是一问一答的模式,大家都在这个队列排队导致堵塞,那就多个队列并发进行,也就是"对同一个域名发起多个长连接"。举个例子,在火车站排队买票的时候,如果只有一个窗口可用,大家只能苦等,多开几个窗口就可缓解这个问题。
这个时候用户数 * 并发数(上限 6-8)已经不错得效果,但是互联网速度太快,火车站就这么大,窗口也就这么多,怎么办,建新的火车站进行分流(大部分城市都有什么东站 西站)。在这里叫做"域名分片",使用多个域名,这些域名指向同一服务器。
HTTP/3
HTTP/2 看似很完美了吧,但是 Google 轮子哥可不服,其他人在研究 HTTP/2 的时候,它们就在琢磨 QUIC。那 QUIC 有啥牛掰的地方呢
QUIC 是 Google 开发的一个基于 UDP 且能像 TCP 一样具有可靠性特点的协议。具备像 HTTP/2 一样的应用数据二进制分帧传输。其主要解决的问题有两个。
进一步解决线头阻塞问题。通过独立不同流,让各个流之间实现相互独立传输,互不干扰
切换网络时的连接保持。wifi 和 3G/4G 经常需要来回切换。基于 TCP 的协议,会因为网络的切换导致 IP 地址的改变。而基于 UDP 的 QUIC 协议,及时切换也可以恢复之前与服务器的连接。(这里推荐大家可以去看看 MPTCP)
4 HTTP 报文详解
客户端与服务端进行交互的信息为报文。客户端为请求报文,服务端为响应报文。我们先用 wireshark 抓一个博客看看
报文层次结构
GET /article/12 HTTP/1.1Host: www.xxx.cnConnection: keep-aliveCache-Control: max-age=0Upgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.106 Safari/537.36Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9Cookie: SESSION=so9nlsvenminor5abs65sh9dsa
HTTP/1.1 200 OKServer: nginxDate: Sun, 17 May 2020 17:04:29 GMTContent-Type: text/html; charset=UTF-8Transfer-Encoding: chunkedConnection: keep-aliveVary: Accept-EncodingX-Powered-By: blade-2.0.6-BETAContent-Encoding: gzip
请求报文
请求报文
请求报文通常由三部分组成:
起始行:描述请求或者响应的基本信息
头部字段集合:key-value 形式说明报文
消息正文:实际传输诸如图片等信息。具体如下图试试
1 请求方法:一共有八种方法选择,如下图所示。采用不同的方法获取不同的资源
HTTP 请求方法详解
说一下非常常见的几种请求方法
Get:从服务器中取资源。可以请求图片,视频等
HEAD:和 Get 类似,但是从服务器请求的资源不会反悔请求的实体数据,只会反悔响应头
POST/PUT:对应于 GET,向服务器发送数据
2 URI
统一资源标识符(Uniform Resource Identifier),严格来说不等于网址,它包含 URL 和 URN,可是 URL 太出名了以致于 URL="网址"。无论开发,测试运维配置都离不开 URI,所以好好掌握。
网络层的 IP 主要目的是解决路由和寻址。现在的 IP 地址按照"."分割,总共 2 的 32 次方大约 42 亿。对于计算机来说比较方便,但是对于人类来说还是不容易记忆,此时出现 DNS 了,他把 IP 地址映射为我们平时常见的"redis.org",按照"."分割域名,从左到右级别越高,最右边为"顶级域名"。如下图所示
域名体系
好了,现在 TCP 提供可靠(数据不丢失)且字节流(数据完整性),而且也有方便我们记忆的域名,但是互联网资源千万种,也不知道访问什么(图片,文字,视频一大堆),这个时候 URI(统一资源标识符)出现了,那长啥样?
URI 格式
协议名:HTTP 协议,另外还有 ftp 等协议。告知访问资源时使用什么协议。
紧接着是分隔符:"://"
主机名:标记互联网主机,可以是 IP 也可以是域名,如果不写端口则使用默认端口,例如 HTTP 为 80,HTTPS 为 443.
登录认证信息:登录主机时的用户名密码(不建议,直接告诉了别人你的隐私信息)
主机名:此处可以是域名也可以是 IP,如果不写端口号则是默认端口。比如 HTTP 默认端口为 80,HTTPS 默认端口为 443
资源所在位置:资源在主机上的位置,使用“/”分隔多级目录,在这里是“/en/download.html”。注意,必须"/"开头
参数:用"?"开始,表示额外的请求要求。通常使用"key=value"的方式存在,如果多个"key=value"则使用"&"相连。
看几个例子
[http://nginx.org/en/download.html](
)
file:///E:/Demo/index/
这里注意是三个"///",因为前面"://"作为分隔符,资源路径按照"/"开头。
既然规则这么多,对于接收方而言需要完成的解析也需要遵守规则,全球用户很多使用 HTTP,每个国家地区所使用语言不同,HTTP 为了能对其进行统一处理,引入了 URI 编码,方法比较简单,将非 ASCII 或者特殊字符全部转换为十六进制字节值,同时在前面加入"%"。比如空格被转换为"%20","中国"就编码为"%E4%B8%AD%E5%9B%BD%0A"。
3 请求体
响应报文
响应报文
状态行----服务器响应的状态
<1> 版本号:使用的 HTTP 什么版本
<2> 状态码:不同数字代表不同的结果,就如我们在编码时,通过返回不同的值代表不同的语义。
状态码一共分为 5 类。
1××:处于中间状态,还需后续操作
2××:成功收到报文并正确处理
"200 OK"
最常见的成功状态码,表示一切正常,客户端获得期许的处理结果。如果不是 Head 请求,那么在响应头中通常会有 body 数据。
"204 No Content"
这个的含义和"200"很相似,不同之处在于它的响应头中没有 body 数据。
"206 Partial Content"
是 HTTP 分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的部分数据时出现,它与 200 一样,也是服务器成功处理了请求,但 body 里的数据不是资源的全部,而是其中的一部分。状态码 206 通常还会伴随着头字段“Content-Range”,表示响应报文里 body 数据的具体范围,供客户端确认,例如“Content-Range: bytes 0-99/5000”,意思是此次获取的是总计 5000 个字节的前 100 个字节。
3××:重定向到其他资源位置
"301 Moved Permanently"
“永久重定向”,意思是本地请求的资源以及不存在,使用新的 URI 再次访问。
“302 Found”
“Moved Temporarily”,“临时重定向”,临时则所请求的资源暂时还在,但是目前需要用另一个 URI 访问。
301 和 302 通过在字段 Location 中表明需要跳转的 URI。两者最大的不同在于一个是临时改变,一个是永久改变。举个例子,有时候需要将网站全部升级为 HTTPS,这种永久性改变就需要配置永久的"301"。有时候晚上更新系统,系统暂时不可用,可以配置"302"临时访问,此时不会做缓存优化,第二天还会访问原来的地址。
“304 Not Modified”
运用于缓存控制。它用于 If-Modified-Since 等条件请求,表示资源未修改,可以理解成“重定向已到缓存的文件”(即“缓存重定向”)。
4××:请求报文有误,服务器无法处理
"400 Bad Request”
通用错误码,表示请求报文有错误,但是这个错误过于笼统。不知道是客户端还是哪里的错误,所以在实际应用中,通常会返回含有明确含义的状态码。
“403 Forbidden”
注意了,这一个是表示服务器禁止访问资源。原因比如涉及到敏感词汇、法律禁止等。当然,如果能让客户端有一个清晰的认识,可以考虑说明拒绝的原因并返回即可。
“404 Not Found”
这可能是我们都知道且都不想看到的状态码之一,它的本意是想要的资源在本地未找到从而无法提供给服务端,但是现在,只要服务器"耍脾气"就会给你返回 404,而我们也无从得知后面到底是真的未找到,还是有什么别的原因,
"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:请求头某个字
段或总体太大;
5××:服务器错误,服务器对请求出的时候发生内部错误。
“500 Internal Server Error”
和 400 类似,属于一个通用的错误码,但是服务器到底是什么错误我们不得而知。其实这是好事,尽量少的将服务器资源暴露外网,尽量保证服务器的安全。
“502 Bad Gateway”
通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误,但具体的错误原因也是不知道的。
“503 Service Unavailable”
表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服务正忙,请稍后重试”的提示信息就是状态码 503。
503 是一个“临时”的状态,
暂时比较忙,稍后提供服务。在响应报文中的“Retry-After”字段,指示客户端可以在多久以后再次尝试发送请求。
4 请求体
上面大部分都是涉及到 header 部分,还有非常重要的 body,everybody
头字段注意事项
<1> 字段名不区分大小写,例如“Host”也可以写成“host”,但首字母大写的可读性更好;
<2> 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线"_"。例如,“test-name”是合法的字段名,而“test name”“test_name”是不正确的字段名;
<3> 字段名后面必须紧接着“:”,不能有空格,而":"后的字段值前可以有多个空格;
<4> 字段的顺序是没有意义的,可以任意排列不影响语义;
<5> 字段原则上不能重复,除非这个字段本身的语义允许,例如 Set-Cookie。
HTTP 的 body 常常被分为这几种的类别
<1> text:超文本 text/html,纯文本 text/plain
<2> audio/video:音视频数据
<3> application: 可能是文本,也可能是二进制,交给上层应用处理
<4> image: 图像文件。image/png 等
但是带宽一定,数据大了通常考虑使用压缩算法进行压缩,在 HTTP 中使用 Encoding type 表示,常用的压缩方式有下面几种
<1> gzip:
一种数据格式,默认且目前仅使用 deflate 算法压缩 data 部分
<2> deflate:
deflate 是一种压缩算法,是 huffman 编码的一种加强
<3> br:
br 通过变种的 LZ77 算法、Huffman 编码以及二阶文本建模等方式进行数据压缩,其他压缩算法相比,它有着更高的压塑压缩效率
使用相应的压缩方法在带宽一定的情况下确实有不错的效果,但是 gzip 等主要针对文件压缩效果不错,但是对视频就不行了。这个时候是不是可以使用数据结构中常用的分而治之,大化小再合并的方式呢,
文件拆分
ok,在报文中使用"Transer-Encoding:chunked"表示,代表 body 部分数据是分块传输的。另外在 body 中存在一个 content-length 字段表示 body 的长度,两者不能共存,另外很多时候是流式数据,body 中没有指明 content-length,这个时候一般就是 chunked 传输了。
现在可以通过采用分块的方式增强带宽的利用率,那他的编码规则如何呢
<1> 每一个分块包含长度和数据块
<2> 长度头按照 CRLF 结束
<3> 数据块在长度快后,且最后 CRLF 结尾
<4> 使用长度 0 表示结束,"0\r\n\r\n"
我们还是看图加深印象
chunked 分块
分块解决了咋们一部分问题,但是有的时候我们想截断发送怎么办呢。在 HTTP 中提供了使用字段“Accept - Ranges: bytes”,明确告知客户端:“我是支持范围请求的”。那么 Range 范围是怎样的呢,Range 从 0 开始计算,比如 Range:0-5 则读取前 6 个字节,服务器收到了这个请求,将如何回应呢
<1> 合法性检查。比如一共只有 20 字节,但是请求 range:100-200。此时会返回 416----"范围请求有误"
<2> 范围正常,则返回 216,表示请求数据知识一部分
<3> 服务器端在相应投资端增加 Content-Range,格式"bytes x-y/length"。
敲黑板:断点续传怎么操作?
<1> 查看服务器是否支持范围请求并记录文件大小
<2> 多个线程分别负责不同的 range
<3> 下载同时记录进度,即使因为网络等原因中断也没事,Range 请求剩余即可
现在我们通过 MIME-TYPE 和 Encoding-type 可以知道 body 部分的类型,下一步将是对内容进行协商。HTTP 中,请求体中使用 Accept 告诉服务端需要什么类型数据(我能处理哪些类型数据),响应头中使用 Content 表明发送了什么类型数据,具体如下图所示
好了,为了各个国家民族顺利友好的沟通和明确的区分。HTTP 请求头中使用"type-subtype",注意此时分隔符是"-"。比如 en-GB 表示英式英语,zh-CN 表示常用的汉语,那对于客户端而言,它通过 Accept-Language 来标记自己可以理解的自然语言,对应的服务端使用 Content-Language 表明实体数据使用的语言类型,如下图所示。
字符集和编码
Cookie 机制
HTTP 是无状态、无记忆的,Cookie 机制的出现让其有记忆功能,是怎么个实现呢
Cookie
从上图我们可以知道 Cookie 是由浏览器负责存储,并不是操作系统负责,我们换个浏览器打开同样的网页,服务就认不出来了。
Cookie 常见的应用一个是身份识别,一个是广告追踪,比如我们在访问网页视频或者图片的时候,广告商会悄悄给我们 Cookie 打上标记,方便做关联分析和行为分析,从而给我推荐一些相关内容。
HTTP 代理
之前介绍的都是一问一答的情景,但是在大部分的情况下都会存在多台服务器进行通信服务。其中比较常见的就是在请求方与应答方中间增加一个中间代理。
代理
代理作为中间位置,相对请求方为服务端,相当于后端服务端为请求方。代理常见的功能为负载均衡。在负载均衡中需要区分正向代理与反向代理,其中也就会涉及调度算法,比如轮询,一致性哈希等。
正向代理与反向代理
那么问题来了,代理作为隐藏身份,相当于隐藏了真实的客户端与服务端,那在是不是
5 HTTPS
好人占多数,坏人也不少。总有些要搞坏事,因为 HTTP 是明文,所以需要想办法保护明文,从而出现了 https。
安全是什么
安全四要素
机密性
对信息进行保密,只能可信的人可以访问(让我想起时间管理者)。
完整性
数据在传输过程中内容不被"篡改"。虽然机密性对数据进行保密了,但是有上策也有下策(hack)
身份认证
证明自己的身份是本人,保证其消息发给可信的人
不可否认
君子一言驷马难追,说话算数,说过的话做过的事要有所保证
HTTPS
HTTP 和 HTTPS
从上图我们知道 HTTPS 无非是在传输层和应用层中间加了一层 TLS,正是 TLS 紧跟当代密码学的步伐,尽全力的保障用户的安全。老规矩,我们用 wireshark 看看长
什么样子。
TLS
可以看出在交互的过程中多了不少新东西,了解 TLS,TLS 由 SSL 握手协议,SSL 修改密码规范协议,SSL 警报协议,SSL 记录协议组成。
TLS 组成
SSL 握手协议:
相对于三次握手
记录协议
记录为 TLS 发送接收数据的基本单位。它的自协议需要通过记录协议发出。如果多个纪录数据则可以一个 TCP 包一次性发出。
警报协议
类似 HTTP 状态码,通过反馈不同的消息进行不同的策略。
变更密码规范协议
告诉对方,从此刻开始,后续的数据将使用加密算法进行加密再传输。
对称加密与非对称加密
对称加密
对称加密,顾名思义,加密方与解密方使用同一钥匙(秘钥)。具体一些就是,发送方通过使用相应的加密算法和秘钥,对将要发送的信息进行加密;对于接收方而言,使用解密算法和相同的秘钥解锁信息,从而有能力阅读信息。
对称加密
非对称加密
在对称加密中,发送方与接收方使用相同的秘钥。那么在非对称加密中则是发送方与接收方使用的不同的秘钥。其主要解决的问题是防止在秘钥协商的过程中发生泄漏。比如在对称加密中,小蓝将需要发送的消息加密,然后告诉你密码是 123balala,ok,对于其他人而言,很容易就能劫持到密码是 123balala。那么在非对称的情况下,小蓝告诉所有人密码是 123balala,对于中间人而言,拿到也没用,因为没有私钥。所以,非对称密钥其实主要解决了密钥分发的难题。如下图
评论