前端协议

本篇博客仅以个人学习记录使用

HTTP协议概述(HTTP协议为应答模式协议)

HTTP协议是WWW服务器和用户请求代理(例如浏览器等)之间通过应答请求模式传输超文本(例如HTML文件、JavaScript文件、CSS文件、图片甚至服务器接口数据等)内容的一种协议,协议的详细规范序号为RFC2616。

浏览器(用户请求代理)向服务器发送请求时头部中包含请求的方法GET、URL、协议版本号、请求头域字段(如请求接收类型Accept)、缓存控制Cache-control、浏览器Cookie和user-agent信息等,同时也可能会带上请求的正文内容。服务器接收请求处理后也是以一段响应报文作为返回,响应的内容包括HTTP消息响应的协议版本1.1/2、返回码304及返回描述Not Modified、缓存控制信息Cache-control以及正文的HTML内容等,当然如果返回码为304时请求响应返回的正文为空,浏览器将从本地缓存中读取文件。浏览器接收到服务器的返回后会进行解析,同时进行相应的操作,例如将服务器返回正文中的HTML内容装载至浏览器进行解析渲染,或是将服务器返回的JSON字符串解析成前端可用的JSON对象等。

通常一个完整的 HTTP报文由头部、空行、正文三部分组成。空行用于区分报文头部和报文正文,由一个回车符和一个行符组成。请求头通常由请求类型、请求URL、协议版本和扩展内容组成;请求头中还包含其他请求头部域信息,如Accept、Cookie、Cache-Control、Host等;请求正文可以携带浏览器端请求的内容,如POST、PUT请求的表单内容。响应返回报文的格式与此类似,响应报文头部由状态码,状态描述、协议版本、扩展内容组成;响应头包含响应头部域信息,如Date、Contont-Type、Cache-Control、Expires等;服务器返回给浏览器的信息可以放在报文正文部分。

HTTP1.1
长连接

HTTP1.1的长连接机制是通过请求头中keep-alive头域信息来控制的。HTTP1.0默认请求的服务器返回是没有keep-alive的,但在HTTP1.0中,如果要建立长连接,也可以在请求消息中包含Connection:keep-alive头域信息,如果服务器能识别这条连接得到的头域信息,则会在响应消息头域中也包含一个Connection:keep-alive返回,表示后面的文化请求可以复用之前得连接传输。但是在HTTP1.1协议中,任何HTTP请求的报文头部域都会默认包含
keep-alive。keep-alive的控制可以让客户端到服务器端之间的连接在一段时间内持续有效,当一个请求文件的传输连接建立以后,在服务器保持该连接的这段时间内,其他文件请求可以复用这个已经建立好的连接,而不用像HTTP1.0那样重新握手建立连接,这样就有效将建立和关闭连接的完网络开销平均到多个文件的请求上。但是需要注意的是,长连接的请求机制并不会节省传输内容的网络开销。

协议扩展切换

协议扩展切换是指,HTTP1.1协议支持在请求头部域消息中包含Upgrade头并让客户端通过头部标识令服务器知道它能够支持其他备用通信协议的一种
机制,服务器根据客户端请求的其他协议进行切换,切换后使用备用协议与客户端进行通信。例如WebSocket协议就是典型的应用,WebSocket协议通信是通过HTTP的方式建立的,通信连接建立完成后通知服务器切换到WebSocket协议来完成后面的数据通信。

WebSocket协议的连接建立过程:浏览器(假设用户使用的浏览器支持WebSocket)向服务器发送请求,并在消息头中添加Connection:Upgrade和Upgrade:websocket告诉服务器后面需要进行协议切换,切换成为WebSocket协议进行通信,如果服务端支持WebSocket服务并允许该客户端来连接,则可以在响应报文头中返回Upgrade和Connection消息头域,同意浏览器使用WebSocket来连接,同时返回的状态码为101表示请求还需要完成协议的切换

缓存控制

在HTTP1.1版本之前,浏览器缓存主要是通过对HTTP1.0的Expires头部控制来实现的,我们知道Expires只能根据绝对时间来刷新缓存内容,HTTP1.1增加了Cache-Control头域,可以支持max-age用来表示相对过期时间,另外请求服务器时也可以根据Etag和Last-Modified来判断是否从浏览器端缓存中加载文件,此时缓存的控制和判断将决定服务器的响应报文中头部内容的状态码是200还是304.下面来描述一下浏览器发送HTTP请求时进行缓存

读取判断的流程:

1.浏览器会先查询Cache-Control(这里用Expires判断也是可以的,但是Expires一般设置的是绝对过期时间,在HTTP1.1之前较为通用,Cache-Control设置的是相对过期时间,HTTP1.1后推荐使用Cache-Control来控制,如果两者都设置了,则只有Cache-Control的设置生效)来判断内容是否过期,如果未过期,则直接读取浏览器端缓存文件,不发送HTTP请求,否则进入下一步。

2.在浏览器端判断上次文件返回头中是否含有Etag信息,有则带上If-None-Match字段信息发送请求给服务器,服务端判断Etag未修改则返回304,如果修改则返回200,否则进入下一步。

3.在浏览器端判断上次文件返回头中是否含有Last-Modified信息,有则带上If-Modified-Since字段信息发送请求,服务端判断Last-Modified失效则返回200,有效则返回304。

4.如果Etag和Last-Modified都不存在,则直接向服务器请求内容。

以上步骤就是Cache-Control、Etag和Last-Modified控制请求缓存的主要过程。

部分内容传输优化

部分内容传输优化是指HTTP可以支持超文本文件的部分传输,例如,他允许请求一个文件的起始位置和一个偏移长度来进行文件内容的部分传输。

另外HTTP1.1请求允许携带一些数据参数信息一起发送到服务器,请求时的数据信息可以放在请求头(例如,GET、DELETE方法请求时)或正文(例如,POST、PUT方法请求时)中。HTTP请求在消息的正文中除了可以携带文本内容,也可以传输二进制数据,例如表单中使用formData提交上传文件时携带的就是二进制数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
HTTP报文的头部域信息内容其实有很多,每个头部域字段的控制都具有自己的逻辑和判断机制,以下是常见的一些头部域字段的设置。

1. Accept:告诉Web服务器自己能接收什么媒体类型
*/*表示能接收任何类型,type/*表示接收该类型下的所有子类型,一般格式为type/sub-type,多个类型使用q参数分割,
q的值代表quality请求质量,反映了用户对这类媒体类型的偏好程度,例如Acccept:text/plain; q=0.5,text/html,text/x-dvi;q=0.8,text/x-c

2.Accept-Charset:浏览器接收内容的字符集,通常是utf-8

3.Accept-Encoding:浏览器接收内容的编码方法,例如指定是否支持压缩,若支持压缩的话支持什么压缩方法,具体如Accept-Encoding:gzip,deflate,sdch

4.Accept-Language: 浏览器接收内容的语言。语言跟字符集是有区别的,例如中文是语言,中文有多种字符集,big5、关闭312、gbk等。该参数也可以设置多个,如Accept-Language:zh-CN,zh;q=0.8.

5.Accept-Ranges: Web服务器表明自己是否接受获取某个实体的一部分(比如文件的一部分)请求,这里主要用于部分文件传输,实际上我们用的比较少。bytes表示接受传输多大长度内容,none表示不接受。

6.Age;一般当服务器用自己缓存的实体去响应请求时,可以用该头部表明实体从产生到现在经过了多长时间,如Age:3600.、

7.Allow:该参数头部可以设置服务端支持接收哪些可用的HTTP请求方法,例如GET、POST、PUT,如果不支持,则会返回405(Method Not Allowed)

8.Authorization: 当客户端接收到来自Web服务器的WWW-Authenticate响应时,后面可以用该头部来携带自己的身份验证信息给Web服务器直接进行认证

9.Cache-Control:用来声明服务器端缓存控制的指令。包括请求设置指令和响应请求指令。

请求控制指令如下:

no-cache: 不使用缓存实体,要求从Web服务器去请求内容。

max-age:只接受Age值小于max-age值的内容,即没有过期的请求对象。

max-stale:可以接受过去的对象,但是过期时间必须小于max-stale值。

响应控制指令如下:

public:可以用Cache中内容回应任何用户。

private:只能用缓存内容回应先前请求该内容的具体用户。

no-cache:可以设置哪些内容不被缓存。

max-age:设置响应中包含对象的过期时间

ALL:no-store不允许缓存。

10.Connection:在请求头中,close告诉Web服务器或者代理服务器,在完成本次请求响应后断开连接,无需等待本次连接的后续请求,
keep-alive告诉Web服务器或者代理服务器,在完成本次请求响应后保持连接,等待本次连接的后续请求。
在响应头中,close连接已关闭。keep-alive保持连接,等待本次连接得到后续请求,如果浏览器请求保持连接,则该头部表明希望Web服务器保持连接的时长(秒),
例如,keep-alive:300.

11.Content-Encoding: 与请求头的Accept-Encoding对应,指Web服务器表明使用何种压缩方法(gzip,deflate)压缩响应中的对象。
例如,Content-Encoding:gzip。

12.Content-Language:与请求头中的Accept-Language对应,Web服务器告诉浏览器响应的媒体对象语言。

13.Content-Length:Web服务器告诉浏览器HTTP请求内容的长度。例如:Content-Length:1024.

14.Content-Range:Web服务器表明该响应包含的部分对象为整个对象的哪个部分。

15.Content-Type:与请求头的Accept对应,指明Web服务器告诉浏览器响应的对象的类型。例如,Content-Type:application/xml。

16.Etag:对象(比如URL)的标志值。一个对象(如HTML文件)如果被修改了,其Etag也会被修改,所以Etag的作用和Last-Modified差不多,主要供Web服务器判断一个对象是否改变。例如前一次请求某个HTML文件时获得了某Etag,当这次又请求该文件时,浏览器就会把先前获得的Etag值发送给Web服务器,然后Web服务器会将这个Etag值跟该文件当前的Etag值进行对比,判断文件是否改变。

17.Expires:Web服务器表明该实体将在什么时候过期,对于过期的对象,只有在跟Web服务器验证了其有效性后,才能用来响应客户请求,是HTTP/1.0的头部。例如,Expires:Sat,23May 2009 10:02:12 GMT。

18.Host:客户端指定自己访问的Web服务器的域名/IP地址和端口号。例如,Host: www.xxx.com

19.If-None-Match: 如果上次文件返回头中包含Etag信息,则会带上If-None-Match发送请求给服务器,判断请求返回304还是200.
例如,If-None-Match: W/"34b1f344ascc:d23"

20.If-Modified-Since: 如果上次文件返回头中包含了Last-Modified信息,则会带上If-Modified-Since发送请求给服务器,
判断请求是返回304还是200.例如:If-Modified-Since:Thu,10Apr21609:12:23 GMT"

21.If-Range: 浏览器告诉Web服务器,如果请求的对象没有改变,就把修改的部分返回给浏览器,如果对象改变了,就把整个对象返回给浏览器。
浏览器通过发送请求对象的Etag或者自己所知道的最后修改时间给Web服务器,让其判断对象是否改变。If-Range常常跟Range头部一起使用

22.Last-Modified:Web服务器设置的对象最后修改时间,比如文件的最后修改时间或者动态页面的最后产生时间。
例如:Last-Modified:Tue,06 Sep 2019 02:34:23 GMT

23.Location:Web服务器告诉浏览器,试图访问的对象已经被移到别的位置了,让浏览重定向去读取Location里面返回的内容。

24.Pramga:主要使用Pramga:no-cache,相当于Cache-Control:no-cache。例如,Pramga: no-cache.

25.Proxy-Authenticate: 代理服务器响应浏览器,要求其提供代理身份验证信息。
Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。

26.Range:浏览器告诉Web服务器自己想读取对象的哪部分。

27.Referer:浏览器像Web服务器表明自己是从哪个网页URL跳转访问当前请求中网址URL的,即跳转到当前页面的来源。
例如:Referer:http://wwww.xxxx.com

28.User-Agent: 浏览器的代理名称,位于请求头部,通常服务端可以根据这个设置获取到浏览器的种类和版本信息。
例如:Mozilla/5.0 Chrome/75.0

29.Server: Web服务器通过此头域表明自己是什么软件及版本信息。例如,Server:Apache/2.2.18(Unix)

30. Transfer-Encoding:Web服务器表明自己对应响应传输消息体做怎样的编码,如是否分块(chunked),Transfer-Encoding:chunked
HTTP2
对比HTTP1.x的优点

HTTP2完全采用二进制的格式来传输数据,而非HTTP1.x的默认文本格式。而二进制在网络中传输的基本单位一般为帧(Frame,一个帧可以理解为具有固定格式和长度的二进制数据包),每个镇包含几个固定部分内容;类型Type、长度Length、标记Flags、流标识Stream和Frame payload(帧有效载荷,一帧能携带的内容数据长度)。多个帧的传输在网络中就形成了帧的传输网络流,所以我们也可以理解为HTTP2协议是通过流式传输的,最大限度地节省了传输带宽。相比于HTTP1.x每次请求都会携带大量冗余头信息(例如浏览器cookie信息等),HTTP2就具有很大的优势了

HTTP2使用TCP多路复用的方式来降低网络请求连接建立和关闭的开销,多个请求可以通过一个TCP连接来完成。HTTP1.1虽然可以通过PipeLine实现并发请求,但是PipeLine是通过串行传输的,多个请求之间的响应可能会被阻塞。

1
2
3
这里明确的说一下TCP连接复用和HTTP1.1中keep-alive连接复用的区别;TCP复用传输是发生在传输层的,而keep-alive控制的文件的连接复用是在应用层的

keep-alive的连接复用是串行的,即一个文件传输完成后,下一个文件才能复用这个连接,而TCP连接复用是帧的多路复用,即不同文件的传输帧可以在一个TCP连接中一起同时进行流式传输。

HTTP2支持传输流的优先级和流量控制机制。HTTP2中每个文件传输流都有自己的传输优先级,并可以通过服务器来动态改变,服务器会保证优先级高的文件流先传输。例如在浏览器渲染中,服务器端就可以优先传输CSS文件来保证页面的渲染,然后在CSS文件全部传输完成后加载js脚本文件。其实这就和我们现在前端的一些优化规则有点相背离,例如使用HTTP2的情况下CSS文件就不一定要写在HTML的顶部,js也不一定要在HTML最底部写了,因为HTTP2的服务器自动就能帮你做好这件事了。

支持服务端推送。服务端能够在特定条件下把资源主动推送给客户端。就像浏览器端的资源预加载一样,例如资源推送可以在HTML文档下载之前让HTML的jshuoCSS文件预先进行下载,从而大大缩短页面加载渲染的等待时间。

所以基于这些HTTP2的优势特性,有人说,以往网站的一些优化规则将不再适用,其实两者也不完全是矛盾的。一方面,适用HTTP2会极大程度上提高网络的传输效率,让我们可以更少的关注页面的性能优化,是对现在开发优化手段的一个增强。另一个方面,现有的优化规则依然能对前端资源的加载和执行起到进一步优化的作用。

HTTPS协议通信过程

HTTPS协议是通过加入SSL(Secure Sockets Layer)层来加密HTTP数据进行安全传输额HTTP协议,同时启用默认的443端口进行数据传输。

HTTPS协议相比较于HTTP协议的请求报文区别不大,HTTPS在请求的头部域字段多了upgrade-insecure-requests,该头部字段指令很关键,他可以用于让页面打开的后续请求自动从HTTP请求升级到HTTPS请求。否则如果使用HTTPS来加载HTML文件,而HTML中加载的是HTTP链接的资源文件,则会产生Mixed Content 类型的错误,并且无法加载资源。所以添加了这个字段,作用就是让浏览器自动升级后面的请求为https请求,同时我们在服务器端响应头域中也要加入header("Content-Security-Policy:upgrade-insecure-requests")来返回给浏览器,否则浏览器默认安全类型策略会阻塞内容并提示block-all-mixed-content类型的错误。

前端实时协议

在实际的前端应用项目中,除了使用应答模式的HTTP协议进行普通网络资源文件的请求加载外,有时也需要建立浏览器客户端与服务端之间的实时连接进行通信,例如网页实时聊天得到应用场景,这就必须涉及到浏览器端的实时通信协议了。对于那些对实时性要求较高的应用场景,普通的HTTP(S)协议就并不适用。虽然前端可以通过Ajax定时间服务端轮询的方式来持续获取服务端的信息,但是这种方式效率相对较低,目前一般只用来处理浏览器上降级体验的实时场景。包括Ajax的方式在内,目前可用来在前端浏览器上进行实时通信的功能实现方式主要有WebSocket、Poll、Long-poll和DDP协议。

WebSocket通信机制

HTTP1.1的协议就已经支持使用Upgrade头域设置进行协议扩展切换,这样就可以实现从HTTP1.1协议切换到其他通信协议进行通信了。一种很典型的实时通信协议便是WebSocket,WebSocket是浏览器端和服务器端建立实时连接的一种通信协议,可以在服务器和浏览器端建立类似Socket方式的消息通信。

相对于HTTP1.1协议,WebSocket协议的优势是方便服务器和浏览器之间的双向数据实时通信。但我们要明白的是,HTTP2也支持服务端的消息推送,也是可以来适应这一场景的。

Poll(轮询)和 Long-poll(长轮询)

尽管HTML5的WebSocket为我们提供了实现前端实时化的方案,提供的API也很完备。但不幸的是,并非所有浏览器都支持WebSocket协议,在桌面或移动端浏览器应用开发时我们仍不能放心的使用它,这时我们就必须回到校友额HTTP协议上考虑采用Poll(轮询)和Long-Poll(长轮询)的方案来应对实时通信的场景了。

Poll

Poll方案很容易理解,即浏览器采用定时向服务器发送请求轮询的方法不断发送或拉取信息。浏览器每隔一秒向服务器发送一次请求,在一秒内服务器更新的内容在下一次轮询中将被浏览器拉取返回。所以这种方案相对来说实时性较差,而且没有新消息时依然需要不断轮询,比较消耗系统资源。

Long-Poll

HTTP请求可以设置一个较长的Timeout等待时间,这样网络轮询请求就可以维持一段较长的时间后返回结果,这也就是Long-Poll(长轮询)的基本思路。服务器只要在这段长轮询时间内进行响应,请求便会立即返回结果;如果这段时间服务器没有返回,浏览器端将自动响应超时并重新发起一个长轮询请求。相比于Poll,Long-Poll的实现更加节省系统资源,实时性更好,不用持续地定时发送网络请求。Long-poll目前一个很典型的应用场景就是网站通过对应的移动客户端进行扫描二维码登录,即用户使用移动客户端扫描二维码登录网站,成功后桌面浏览器页面自动响应跳转进入一个新的登录后页面。用户打开桌面浏览器页面后会立即发送一个用户登录状态查询的长轮询请求,同时开始使用移动客户端扫描二维码,扫描成功时移动客户端会调用接口改变用户的登陆状态,此时服务器可以不断轮询获取用户登录状态改变通知,一旦检测到用户使用移动客户端扫码登录,就将用户登录状态返回给浏览器的长轮询请求,用户浏览器请求到用户登录状态后完成后面的跳转,前端请求登录状态的轮询就可以使用AJAX来模拟实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function _getQrAuth() {
const self = this;

//请求查询登录状态
$.ajax({
url: '/api/v1/user/qrcode/auth',
type: 'get',
dataType: 'json',
cache: false,
tiemout: 30 * 1000, //设置30秒超时时间
success: function(data) {
//登录成功后自动跳转
if(data.retcode === 200) {
window.location.href = 'user_info.html?backUrl = ' + encodeURIComponent(backUrl);
}
error: function(e) {
console.log(JSON.stringify(e))
}
}
});

}

在nodejs服务器对应的处理 程序可以用如下方法来实现登陆状态的循环轮询。

const qrcodeAuth = function*(req,res) {
let ctx = this;

let authState = false;
while(true) {
//查询用户登录状态,已登录则 返回用户信息,否则返回false

authState = queryAuthState(ctx);
if(authState) {
ctx.body = {
retcode: 200
}
return ;
}
}
ctx.body = {
retcode: 500,
msg: 'tiem out'
}
};

1
Web应用中客户端和服务端建立实时通信的方式比较多,包括HTML5提供的WebSocket、Flash实现的WebSocket、XMLHttpRequest轮序长连接、XMLRequest Multipart Streaming、script标签的长时间轮询等。当然也还有一些不常用的方式,就不必多说了
前端DDP协议

DDP是一种新型的客户端与服务端的实时通信协议,由于兼容性的原因,目前使用还不广泛。DDP使用JSON的数据格式在客户端和浏览器之间进行数据传输通信,所以对于前端开发者来说使用非常方便。有名的Meteor Web框架的双向实时数据更新机制底层使用的就是DDP,这种协议模式下客户端可向服务端发起远程过程调用,客户端也可以订阅服务端数据,在服务端数据变化时,服务端会向客户端发起通知,触发浏览器响应的操作。当然我们借助DDP模块也可以创建一个简单的DDP协议的服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 引入DDP模块,创建客户端连接器
const DDPClient = require('ddp');
const client = new DDPClient({
host: 'localhost',
port: 3000
});

// 监听信息
client.on('message'. function(data, flags) {
console.log('[DDP 消息]:', data);
});

// 连接
client.connect(function(){
//客户端订阅 post
client.subscribe('post', [], function(){
console.log('[post 订阅消息]')
})
});

以Meteor为例,在Web浏览器端使用DDP协议仍然存在部分兼容性问题,所以目前DDP仍没有被广泛使用在实际项目中,但我们可以认为它是面向未来的一种前端实时协议,以后极有可能被广泛使用。
RESTful数据协议规范

REST并不是某一种具体的协议,而是定义了一种网络应用软件之间的架构关系并提出了一套与之对应得到网络之间交互调用的规则。与之类似的例如早期的WebSevice,当然WebSevice现在基本都不用了。而在REST形式的软件应用服务(这里讨论的主要是Web应用服务)中,每个资源都有一个与之对应的URI地址,资源本身都是方法调用的目标,方法列表对所有资源都是一样的,而且这些方法都推荐使用HTTP协议的标准方法,例如GET、POST、PUT、DELETE等。如果一个网络应用软件的设计是按照REST定义的,我们就可以认为它使用的交互调用的方法遵循RESTful规范。