总览
什么是HTTP,HTTP缓存,HTTTP常见字段、HTTP版本变化,常见的状态码、HTTP安全问题
# 什么是HTTP
HTTP是超文本传输协议,专门用来在两点之间传输数据的约定和规范。不仅可用于浏览器至服务器,也可以使用在服务器至服务器
HTTP 协议是一个双向的应用层协议。
我们在上网冲浪时,假设浏览器是请求方 A ,目标网站是应答方 B,数据虽然是在 A 和 B 之间传输,但允许中间有中转或接力。
传递的过程中如果需要经过好多个中间人,这样的传输方式就从「A < --- > B」,变成了「A <-> N <-> M <-> B」。
而在 HTTP 里,需要中间人遵从 HTTP 协议
# HTTP版本
- HTTP0.9只支持GET方法,只能发送HTML格式字符串
- HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。增加头信息,每次只能发送一个请求(无持久连接)
- HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法,默认持久连接、请求管道化、增加缓存处理、增加Host字段、支持断点传输分块传输等。
- HTTP/2.0 二进制分帧、多路复用、头部压缩、服务器推送,基于 HTTPS 的安全性有保证
- HTTP/3.0 传输协议改用了 UDP 协议。
HTTP0.9和HTTP1.0
- 不再局限于 0.9 版本的纯文本格式,根据头信息中的 Content-Type 属性,可以支持多种数据格式,这使得互联网不仅仅可以用来传输文字,还可以传输图像、音频、视频等二进制文件。
- cache,就是当客户端在规定时间内访问同一网站,直接访问cache即可。其他的新增功能还包括状态码(status code)、多字符集支持、多部分发送(multi-part type)、权限(authorization)、缓存(cache)、内容编码(content encoding)等。
HTTP1.0和HTTP1.1
HTTP/1.1 采用了长连接的方式,这使得管道(pipeline)网络传输成为了可能。
即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以**减少整体的响应时间。**支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞
HTTP1.1和HTTP2.0
- 二进制协议
HTTP 1.1 版的头信息是文本(ASCII 编码),数据体可以是文本,也可以是二进制。
HTTP 2.0 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。增加了数据传输的效率,数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装 这一特性,使性能有了很大的提升
- 多工-并发传输
我们都知道 HTTP/1.1 的实现是基于请求-响应模型的。同一个连接中,HTTP 完成一个事务(请求与响应),才能处理下一个事务,也就是说在发出请求等待响应的过程中,是没办法做其他事情的,如果响应迟迟不来,那么后续的请求是无法发送的,也造成了队头阻塞的问题。
HTTP 2.0 复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞"(HTTP 2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比 HTTP 1.1大了好几个数量级)。
举例来说,在一个 TCP 连接里面,服务器同时收到了 A 请求和 B 请求,于是先回应 A 请求,结果发现处理过程非常耗时,于是就发送 A 请求已经处理好的部分, 接着回应 B 请求,完成后,再发送 A 请求剩下的部分。
HTTP/2 引出了 Stream 概念,多个 Stream 复用在一条 TCP 连接。1 个 TCP 连接包含多个 Stream,Stream 里可以包含 1 个或多个 Message,Message 对应 HTTP/1 中的请求或响应,由 HTTP 头部和包体构成。Message 里包含一条或者多个 Frame,Frame 是 HTTP/2 最小单位,以二进制压缩格式存放 HTTP/1 中的内容(头部和包体)。
针对不同的 HTTP 请求用独一无二的 Stream ID 来区分,接收端可以通过 Stream ID 有序组装成 HTTP 消息,不同 Stream 的帧是可以乱序发送的,因此可以并发不同的 Stream ,也就是 HTTP/2 可以并行交错地发送请求和响应。
- 头信息压缩
HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie
和 User Agent
,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。
HTTP 2.0 对这一点做了优化,引入了头信息压缩机制(header compression)。一方面,头信息使用 gzip 或compress 压缩后再发送;这就是所谓的 HPACK
算法,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
- 服务器推送
HTTP 2.0 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送(server push)。意思是说,当我们对支持 HTTP 2.0 的 web server 请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源。 服务器端推送的这些资源其实存在客户端的某处地方,客户端直接从本地加载这些资源就可以了,不用走网络,速度自然是快很多的。
HTTP2.0和HTTP3.0
HTTP/2 是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。一旦发生了丢包现象,就会触发 TCP 的重传机制,这样在一个 TCP 连接中的所有的 HTTP 请求都必须等待这个丢了的包被重传回来。
HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP 解决队头阻塞问题
UDP 发送是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
QUIC 有以下 3 个特点。
- 无队头阻塞
- 更快的连接建立
- 连接迁移
# HTTP常见字段
请求头字段
- User-Agent 浏览器身份标识字符串
- **Host ** 客户端发送请求时,用来指定服务器的域名。
- Date 发送该信息的日期和时间
- Referer 表示浏览器所表示的前一个页面,正是那个页面上的某个链接将浏览器带到了当前所请求的页面
- Content-Type 用于服务器回应时,告诉客户端,本次数据是什么格式。
- Content-Length 服务器在返回数据时会有
Content-Length
字段,表明本次回应的数据长度。 - Connection 字段 最常用于客户端要求服务器使用「HTTP 长连接」机制,以便其他请求复用HTTP 长连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态,HTTP/1.1 版本的默认连接都是长连接,但为了兼容老版本的 HTTP,需要指定
Connection
首部字段的值为Keep-Alive
。开启了 HTTP Keep-Alive 机制后, 连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接,一直持续到客户端或服务器端提出断开连接。 - Accept 字段 客户端请求的时候,可以使用
Accept
字段声明自己可以接受哪些数据格式。 - Accept-Charset 能够接受的字符集
- Accept-Encoding字段 客户端在请求时,用
Accept-Encoding
字段说明自己可以接受哪些压缩方法。 - Origin 发起一个针对跨域资源共享的请求
响应头字段
- Date 发送该信息的日期和时间
- Last-Modified 请求对象的最后修改日期
- Server 服务器名字
- Expires 指定一个时间,超过该时间则认为该响应过期
- Content-Encoding 字段 说明数据的压缩方法。表示服务器返回的数据使用了什么压缩格式
- Content-Disposition 可以让客户端下载文件并建议文件名的头部
# 解决粘包问题
HTTP 协议通过设置回车符、换行符作为 HTTP header 的边界,通过 Content-Length 字段作为 HTTP body 的边界,这两个方式都是为了解决“粘包”的问题
# HTTP请求方法
HTTP默认使用80端口,这个端口指的是服务端的端口,而客户端使用的端口是动态分配的。当我们没有指定端口访问时,浏览器会默认帮我们添加80端口。需要注意的是,HTTPS的默认端口为443,如果使用80端口访问HTTPS协议的服务器可能会被拒绝。
HTTP请求的方法:
GET与POST
先说明下安全和幂等的概念:
- 在 HTTP 协议里,所谓的「安全」是指请求方法不会「破坏」服务器上的资源。
- 所谓的「幂等」,意思是多次执行相同的操作,结果都是「相同」的。
如果从 RFC 规范定义的语义来看:
- GET 方法就是安全且幂等的,因为它是「只读」操作,无论操作多少次,服务器上的数据都是安全的,且每次的结果都是相同的。所以,可以对 GET 请求的数据做缓存,这个缓存可以做到浏览器本身上(彻底避免浏览器发请求),也可以做到代理上(如nginx),而且在浏览器中 GET 请求可以保存为书签。
- POST 因为是「新增或提交数据」的操作,会修改服务器上的资源,所以是不安全的,且多次提交数据就会创建多个资源,所以不是幂等的。所以,浏览器一般不会缓存 POST 请求,也不能把 POST 请求保存为书签。
“get”方法提交的数据会直接填充在请求报文的URL上,如“ https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1 ” “?”问号划分域名和get提交的参数,A=B中的A是参数名,B是参数值,多个参数之间用&进行分割,如果参数值是中文,则会转换成诸如%ab%12加密16进制码。一般来说,浏览器处理的URL最大限度长度为1024B(不同浏览器不一样),所以GET方法提交参数长度有限制。
“post”方法提交的数据会附在正文上,一般请求正文的长度是没有限制的,但表单中所能处理的长度一般为100k(不同协议不同浏览器不一样),而且需要考虑下层报文的传输效率,不推荐过长。
所以GET方法可以用来传输一些可以公开的参数信息,解析也比较方便,如百度的搜索的关键词,而POST方法可以用来提交一个用户的敏感信息(如果不使用HTTPS加密,报文正文仍旧是明文,容易被人截获读取)
# HTTP请求消息Request
客户端发送一个HTTP请求到服务器的请求消息包括以下格式
请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
Host: www.w3.org
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Referer: https://www.google.com.hk/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: authorstyle=yes
If-None-Match: "2cc8-3e3073913b100"
If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT
name=qiu&age=25
2
3
4
5
6
7
8
9
10
11
12
13
14
# HTTP响应消息Response
一般情况下,服务器接收并处理客户端发过来的请求后会返回一个HTTP的响应消息。
HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文
HTTP/1.1 200 OK
Date: Tue, 08 Jul 2014 05:28:43 GMT
Server: Apache/2
Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT
ETag: "40d7-3e3073913b100"
Accept-Ranges: bytes
Content-Length: 16599
Cache-Control: max-age=21600
Expires: Tue, 08 Jul 2014 11:28:43 GMT
P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"
Content-Type: text/html; charset=iso-8859-1
{"name": "qiu", "age": 25}
2
3
4
5
6
7
8
9
10
11
12
13
第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
第二部分:消息报头,用来说明客户端要使用的一些附加信息
第二行和第三行为消息报头, Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8
第三部分:空行,消息报头后面的空行是必须的
第四部分:响应正文,服务器返回给客户端的文本信息。
空行后面的html部分为响应正文。
更加详细可查阅ABNF定义的RFC文档 HTTP报文格式
# 常见的HTTP状态码
2xx 类状态码表示服务器成功处理了客户端的请求
- 200 OK 是最常见的成功状态码,表示一切正常。如果是非
HEAD
请求,服务器返回的响应头都会有 body 数据 - **204 No Content ** 也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
- **206 Partial Content ** 是应用于 HTTP 分块下载或断点续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
3xx 类状态码表示客户端请求的资源发生了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
- 301 Moved Permanently 表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
- 302 Found 表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
301 和 302 都会在响应头里使用字段 Location
,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
- 304 Not Modified 不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,也就是告诉客户端可以继续使用缓存资源,用于缓存控制。
4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
- 400 BadRequest 表示客户端请求的报文有错误,但只是个笼统的错误
- 401 Unauthorized 用户没有访问权限,需要进行身份认证
- **403 Forbidden ** 表示服务器禁止访问资源,并不是客户端的请求出错
- 404 Not Found 当资源不存在时,出现此结果。
- 405 Method Not Allowed 由于方法和资源组合不正确而出现此错误(服务器支持该方法但使用错误)。 例如,不能对一个实体集合使用 DELETE 或 PATCH。
5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
- 501 Not Implemented 表示客户端请求的方法服务器还不支持。
- 502 Bad Gateway 通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
- 503 Service Unavailable 表示服务器当前很忙,暂时无法响应客户端
# HTTP缓存
对于一些具有重复性的 HTTP 请求,比如每次请求得到的数据都一样的,我们可以把这对「请求-响应」的数据都缓存在本地,HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存
# 强制缓存
强缓存指的是只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存,决定是否使用缓存的主动性在于浏览器这边。
强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
Cache-Control
, 是一个相对时间;Expires
,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires 。
Expires即过期时间,存在于服务端返回的响应头中,告诉浏览器在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。比如下面这样: 表示资源在2019年11月22号8点41分
过期,过期了就得向服务端发请求。
Expires: Wed, 22 Nov 2019 08:41:00 GMT
但潜藏了一个问题,那就是服务器的时间和浏览器的时间可能并不一致,那服务器返回的这个过期时间可能就是不准确的。因此这种方式很快在后来的HTTP1.1
版本中被抛弃了
Cache-control 选项更多一些,设置更加精细,所以建议使用 Cache-Control 来实现强缓存。具体的实现流程如下:
当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control
Cache-Control:max-age=3600
1代表这个响应返回后在 3600 秒,也就是一个小时之内可以直接使用缓存。
它其实可以组合非常多的指令,完成更多场景的缓存判断:
public: 客户端和代理服务器都可以缓存。因为一个请求可能要经过不同的
代理服务器
最后才到达目标服务器,那么结果就是不仅仅浏览器可以缓存数据,中间的任何代理节点都可以进行缓存。private: 这种情况就是只有浏览器能缓存了,中间的代理服务器不能缓存。
no-store:非常粗暴,不进行任何形式的缓存。
s-maxage:这和
max-age
长得比较像,但是区别在于s-maxage是针对代理服务器的缓存时间。no-cache: 跳过当前的强缓存,发送HTTP请求,即直接进入
协商缓存阶段
。
强缓存分为memory cache和disk cache,存在浏览器内存还是磁盘,和资源的执行时间有关,如果某js文件执行时间长就会被放进磁盘,也就是大部分异步请求。像大部分css文件一般放进磁盘缓存因为只需加载一次不会频繁读取不需要存进内存缓存,图片资源较大一般也放进磁盘缓存,但base64一般放进内存缓存
# 协商缓存
你可能会看到过某些请求的响应码是 304
,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。协商缓存就是与服务端协商之后,通过协商结果来判断是否使用本地缓存。
协商缓存可以基于两种头部来实现。
http1.0使用的是Last-Modified
,http1.1使用的是ETag
请求头部中的
If-Modified-Since
字段与响应头部中的Last-Modified
字段实现响应头部中的
Last-Modified
:标示这个响应资源的最后修改时间;请求头部中的
If-Modified-Since
:当资源过期了,发现响应头中具有 Last-Modified 声明,则再次发起请求的时候带上 Last-Modified 的时间,服务器收到请求后发现有 If-Modified-Since 则与被请求资源的最后修改时间进行对比(Last-Modified),如果最后修改时间较新(大),说明资源又被改过,则返回最新资源,HTTP 200 OK;如果最后修改时间较旧(小),说明资源无新修改,响应 HTTP 304 走缓存。请求头部中的
If-None-Match
字段与响应头部中的ETag
字段响应头部中
Etag
:唯一标识响应资源;当浏览器再次请求访问服务器中的该资源时,首先会先检查强制缓存是否过期如果没有过期,则直接使用本地缓存;如果缓存过期了,会在 Request 头部加上 If-None-Match 字段,该字段的值就是 ETag 唯一标识请求头部中的
If-None-Match
:当资源过期时,浏览器发现响应头里有 Etag,则再次向服务器发起请求时,会将请求头 If-None-Match 值设置为 Etag 的值。服务器收到请求后进行比对,如果资源没有变化返回 304,如果资源变化了返回 200。
第一种实现方式是基于时间实现的,第二种实现方式是基于一个唯一标识实现的,相对来说后者可以更加准确地判断文件内容是否被修改,避免由于时间篡改导致的不可靠问题。
如果在第一次请求资源的时候,服务端返回的 HTTP 响应头部同时有 Etag 和 Last-Modified 字段,那么客户端再下一次请求的时候,如果带上了 ETag 和 Last-Modified 字段信息给服务端,这时 Etag 的优先级更高,也就是服务端先会判断 Etag 是否变化了,如果 Etag 有变化就不用在判断 Last-Modified 了,如果 Etag 没有变化,然后再看 Last-Modified。
注意,协商缓存这两个字段都需要配合强制缓存中 Cache-Control 字段来使用,只有在未能命中强制缓存的时候,才能发起带有协商缓存字段的请求。
# HTTP安全问题
HTTP协议默认是采取明文传输的,因此会有很大的安全隐患,常见的提高安全性的方法是:对通信内容进行加密后,再进行传输,常见的加密方式有
不可逆 单向散列函数:MD5、SHA等
单向散列函数也叫哈希函数、消息摘要函数或者指纹,可以根据根据消息内容计算出散列值 散列值的长度和消息的长度无关,无论消息是1bit、10M、100G,单向散列函数都会计算出固定长度的散列值
可逆 对称加密:DES、3DES、AES等非对称加密:RSA等
其它 混合密码系统
数字签名
证书
# 参考
HTTP 协议入门 - 阮一峰的网络日志 (ruanyifeng.com) (opens new window)
图解HTTP
小林coding