通信协议
最近在做IM系统,在前端和服务端通信中使用了websocket和protoBuf。趁着还热乎,写下关于前端应用层面的通信协议。主要是关于是http协议。
http协议
http(超文本传输协议),是目前前端和服务端通信主要应用的应用层协议。http协议经历了4个比较重要的版本:
1、http1.0;
2、http1.1;
3、http spdy(读speedy);
4、http2.0;
在前端应用层面,这几个版本的主要区别是:
1、http1.0 每次http请求都需要重新建立连接/关闭连接(三次握手,四次挥手),不支持链路复用;并且根据不同浏览器厂商设置的限制,一般同时并发请求数量4-8个,其他请求需要等前面的请求关闭后再发送;
2、http1.1 支持keep-alive,可以进行链路复用,同一个域名的请求建立连接后,可以多次进行数据传输,不需要每次都重新建立连接;
3、http spdy 支持请求优先级;
3、http2.0 支持多路复用,据说性能有很大的提升,但因为目前暂时没有实践过,只能暂时期待一下;
这些版本更新乍一看好像跟前端关系不大,其实当性能要求比较高的时候,是可以根据不同版本进行性能优化的, e.g.:
1、如果是http1.0的话,要尽量不要切割太多的请求文件,例如代码合并压缩,雪碧图,etc.
2、如果是http1.1的话,如果需要加载的资源很多,我们可以配置多个静态资源服务器,每个域名加载4-6个静态资源;
3、如果是http spdy的话,我们可以通过设置例如js文件的defer, async属性控制资源请求的优先级;
https
http协议分为明文传输和加密传输,明文就是http协议,加密传输就是https;加密传输会更加安全,但目前https应用这么广泛的原因大部分是因为运营商的广告劫持.
而https的加密分两层,一层是通过非对称加密生成随机数,再通过随机数加密内容,整体流程如下:
client 发起请求->服务器返回证书->(client 校验证书合法性->生成随机数->通过公钥加密随机数->将随机数加密传输到服务器)->(服务器收到随机数->通过私钥解密->通过随机数加密数据->将数据返回给client)->client将本地存储随机数解密收到的内容
而这一串复杂的加密解密过程都由两端的https服务完成,并不需要我们手动做任何加密解密操作。
长连接/长轮询
一直在纠结长连接和长轮询的区别,特别是http1.1的时候很多文章介绍说支持长连接但又不太理解。后来在使用websocket(后面也会有相关介绍)通信的时候,觉得自己了解长连接和短链接的区别了:
1、 长连接就是服务端可以向客户端推送消息,而不只是被动的接受请求,返回数据;
后来又再深入了解下,其实http/websocket都是基于tcp协议实现的,长连接和连接都是tcp在实现上的区别;而http1.1所支持的长连接仍不会支持服务端主动向客户端发送消息,只是在客户端发起请求后,可以在keep-alive时间内进行不断轮询,直到有符合逻辑的数据出现 or 超时关闭连接再重新建立连接,避免客户端不断主动轮询导致的性能问题;
另外: 短连接/短轮询就是我们平时应用的ajax请求,就是建立http链路->发起请求->响应->断开,短轮询属于短链接的子集,是否属于轮询只在于服务端内部代码逻辑实现;
headers
header里面有很多属性(或者叫规范?),我主要是从前后端交互的应用去描述header的应用:
1、缓存;
2、跨域;
3、RESTful api;
4、custom header;
5、cookie;
缓存
http缓存分两种:
1、强缓存: Cache-Control, Expires;
2、协议缓存; Etg, Last-Modify;
优先级:强缓存 > 协议缓存; 全部优先级: Cache-Control > Expires > Etg > Last-Modify;
缓存设置是在服务端header设置,一般通过网关来设置缓存方式,具体实现方式可以参考我nginx的分享文章
跨域CORS
协议,域名,端口有任一不一致即跨域。跨域是一种浏览器的安全检查机制,可以很大程度上避免csrf攻击;
忘了哪位大佬说过编码的过程很多时候是在方便和安全之间做选择,跨域其实是一个很切题的例子,实际项目开发上
我们经常需要去处理跨域,原因很多:
1 我们有多个项目,可能之间有需要共享的静态资源/接口 ;
2 不同环境之间的切换;
3 前端资源,其他静态资源,服务端接口在不同的服务上;
....
那我们遇到跨域一般怎么处理呢?
1 JSONP;
2 后端指定域名不跨域;
Content-Type & RESTful api
这里通过RESTful api来讲http的应用,主要有两个原因: 1、 RESTful是目前比较流行的规范,其实好像也没有其他可以说的出名字的规范(可能每个团队也会自定义一些规范,但实践来说,目前RESTful api是比较好的选择)。 2、 RESTful api 涉及到大部分请求方式(get, post, patch, put, delete),里面会涉及到不同的方式组合Content-Type的传参方式;
传参方式
请求通过传参方式分为两种,query/body,一般情况下默认get/delete通过qeury传参,其他通过body;
1、 query:传参和解析比较简单,就是拼接url和解析url;
2、 body:需要根据约定的Content-Type进行传参和解析;
注:是不是就不能在query请求中通过body传参呢?非也,在nodejs中测试,即使通过query请求也可以通过body传参,更准确的说法,有些工具或者框架会屏蔽query请求的body传参,但实际上query请求是可以通过body传参的;
传参格式Content-Type
传参格式其实是比较容易混淆的,见过不少工作五年以上的前后端开发,仍不清楚到底怎么设置Content-Type,怎么根据Content-Type解析参数,主要是在框架层面已经帮我们解决了解析的问题(实际上框架已经为我们处理了二进制流数据,不同Content-Type的数据解析,为我们提供了可操作的json/xml等数据格式)。我从前后端角度分别说一下Content-Type的实践(前端工具axios, 服务端框架koa): 1、x-www-form-urlencoded;
// axios
axios({
method: 'post',
url: '/user/12345',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
// koa接收到的数据
//firstName='Fred'&lastName='Flintstone'
2、application/json;
// axios
axios({
method: 'post',
url: '/user/12345',
headers: {
'content-type': 'application/json'
},
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
//koa 收到数据
{
firstName: 'Fred',
lastName: 'Flintstone'
}
3、multipart/form-data
// axios
axios({
method: 'post',
url: '/user/12345',
headers: {
'content-type': 'multipart/form-data'
},
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
//koa 收到数据
----------------------------290355146280397251451667
Content-Disposition: form-data; name="firstName"
'Fred'
----------------------------290355146280397251451667
Content-Disposition: form-data; name="lastName"
'Flintstone'
----------------------------290355146280397251451667--
注: axios 里面有两种方式传参,params属于query方式, data属于body方式。
custom header;
header自定义是为了更方便做一些统一的处理,例如在header中标志客户端类型,来源,身份校验,etc. 关于身份校验常用的jwt(json web token),可以参看我另外一篇jwt的介绍文章;
cookie
实际上cookie之前广泛应用在身份校验上,但目前市场上大部分已替换为token/jwt等更安全的方式;目前已很少cookie的应用了,这里简单介绍下设置方式和可设置的属性: 1、cookie是由Set-Cookie属性进行设置;
// 设置 cookie
res.setHeader("Set-Cookie", [
"name=panda; domain=panda.com; path=/write; httpOnly=true",
`age=28; Expires=${new Date(Date.now() + 1000 * 10).toGMTString()}`,
`address=${encodeURIComponent("回龙观")}; max-age=10`
]);
2、设置的属性
1、domain 用来设置允许访问 cookie 的域;
2、path 用来设置允许访问 cookie 的路径;
3、httpOnly 用来设置是否允许浏览器中修改 cookie,如果通过浏览器修改设置过 httpOnly=true 的 cookie,则会增加一条同名 cookie,原来的 cookie 不会被修改;
4、Expires 用来设置过期时间,绝对时间,值为一个 GMT 或 UTC 格式的时间;
5、max-age 同样用来设置过期时间,相对时间,值为一个正整数,单位 s。