作者|jaychen
原文|https://segmentfault.com/a/1190000004682473
为什么websocket会有子协议
由于websocket 本身的协议对于数据格式来说,不是特别的清晰明了,ws可以传输text,blob,binary等等其他格式. 这样对于安全性和开发性能来说,友好度很低。所以,为了解决这个问题, subprotocols 出现了. 在使用时,client和server都需要配置一样的subprotocols. 例如:
服务端需要将subprotocols发送过去, 在handshakes的过程中,server 会识别subprotocols. 如果,server端也有相同的子协议存在, 那么连接成功. 如果不存在则会触发error, 连接就被断开了.
websocket 协议内容
websocket 是有HyBi Working Group 提议并创建的。 主要的内容就是 一张表.
相比TCP来说, 真的是简单~ 其实一句话就可以说完.
Figure 17-1. WebSocket frame: 2–14 bytes payload
具体内容是:
- 第一个比特(FIN) 表明, 该frame 是否信息的最后一个. 因为信息可以分多个frame包传送. 但最终客户端接收的是整个数据
- opcode(4bit)--操作码, 表示传送frame的类型 比如text(1)|| binary(2)
- Mask 比特位表示该数据是否是从 client => server.
- Extended length 用来表示payload 的长度
- Masking key 用来加密有效值
- Payload 就是传输的数据
websocket 能否跨域?
首先,答案是。 但,网上有两部分内容:
WebSocket is subject to the same-origin policyWebSocket is not subject to the same-origin policy
看到这里我也是醉了. 事实上websocket 是可以跨域的。 但是为了安全起见, 我们通常利用CORS 进行 域名保护.
即,设置如下的相应头:
Access-Control-Allow-Origin: http://example.com
这时, 只有http://example.com 能够进行跨域请求. 其他的都会deny.
那什么是CORS呢?
how does CORS work
CORS 是Cross-Origin Resource Sharing--跨域资源分享. CORS 是W3C 规范中 一项很重要的spec. 一开始,ajax 收到 the same origin policy 的限制 奈何不得。 结果出来了JSONP 等 阿猫阿狗. 这让ajax很不安呀~ 但是,W3C 大手一挥, 亲, 我给你开个buff. 结果CORS 就出来了。 CORS 就是用来帮助AJAX 进行跨域的。 而且支持性也超级好. IE8 啊,亲~ 但是IE 是使用XDomainRequest 发送的.(真丑的一逼) 所以,这里安利一下Nicholas Zakas大神写的一个函数.(我把英文改为中文了)
然后, 就可以直接,xhr.send(body). 那CORS其实就完成了.
但,withCredentials
是什么意思呢?
CORS中的withCredentials
该属性就是用来表明,你的request的时候,是否带上你的cookie. 默认情况下是不带的. 如果你要发送cookie给server的话, 就需要将withCredentials设置为true了.
xhr.withCredentials = true;
但是,server并不是随便就能接受并返回新的cookie给你的。 在server端,还需要设置.
Access-Control-Allow-Credentials: true
这样server才能返回新的cookie给你. 不过,这还有一个问题,就是cookie还是遵循same-origin policy的。 所以, 你无法使用document.cookie去访问他. 他的CRUD(增删查改)只能由 server控制.
CORS 的preflight 验证
CORS的preflight request, 应该算是CORS中里面 巨坑的一个。 因为在使用CORS 的时候, 有时候我命名只发送一次请求,但是,结果出来了两个。 有时候又只有一个, 这时候, 我就想问,还有谁能不懵逼. 这里,我们就需要区分一下. preflight request的作用到底是什么。 preflight request 是为了, 更好节省宽带而设计的. 因为CORS 要求的网络质量更高, 而且 花费的时间也更多. 万一, 你发送一个PUT 请求(这个不常见吧). 但是服务端又不支持, 那么你这次的 请求是失败了, 浪费资源还不说,关键用户不能忍呀~ 所以, 这里我们就需要区分,什么是简单请求, 什么是比较复杂的请求 简单请求 简单请求的内容其实就两块, 一块是method 一块是Header
- Method
- GET
- POST
- Header
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID //这是SSE的请求头
- Content-Type ,但只有一下头才能算简单
比如, 我使用上面定义好的函数createCORSRequest. 来发送一个简单请求
我们来看一下,只发送一次简单请求时,请求头和相应头各是什么.(剔除无关的Headers)
上面就是一个简单的CORS 头的交互。 另外,说明一个Access-Control-Allow-Origin
,该头是必不可少的.
本来在XHR中, 一般可以通过xhr.getResponseHeader()来获取相关的相应头。 但是 在CORS中一般可以获得如下几个简单的Header:
- Cache-Control
- Content-Language
- Content-Type
- Expires
- ETag
- Last-Modified
- Pragma
如果你想暴露更多的头给用户的话就可以使用,Access-Control-Expose-Headers
来进行设置. 多个值用','分隔.
那发送两次请求是什么情况呢?
我们如果请求的数据是application/json的话,就会发送两次请求.
第一次,我们通常叫做preflight req. 他其实并没有发送任何 data过去. 只是将本次需要发送的请求头发送过去, 用来验证该次CORS请求是否有效. 上面的请求头就有:
Access-Control-Request-Method
就是用来表明,该次请求的方法.
请求内没有任何附加的数据.
如果该次preflight req 服务器可以处理,那么服务器就会正常返回, 如下的几个头.
说明一下里面的头
- Access-Control-Allow-Methods: 指明服务器支持的方法
- Access-Control-Max-Age: 表明该次preflight req 最长的生存周期
- Access-Control-Allow-Headers: 是否支持你自定义的头. 比如: Custom-Header
这里,主要要看一下Access-Control-Max-Age
. 这和preflight另外一个机制有很大的关系. 因为preflight 已经多发了一次请求, 如果每次发送json格式的ajax的话, 那我不是每次都需要验证一次吗?
当然不是. preflight req 有自己的一套机制. 通过设置Max-Age 来表示该次prefilght req 的有效时间。 在该有效时间之内, 后面如果有其他复杂ajax 的跨域请求的话,就不需要进行两次发送验证了.
而且,第二次的请求头和相应头 还可以减少不少重复的Header.
第二次继续验证
ok~ 最后上一张 Monsur Hossain大神话的CORS server 的运作流程图=>
发展图谱
不多说了, 上图~
fetch 补充
fetch 相关补充,可以查阅览 前端 fetch 通信