WebSocket
webSocket是H5发布的一种新的协议,可以看做是一种独立的建立在TCP协议上的新协议,能够运用在客户端和服务器端,通过基于事件的方式,赋予浏览器实时通信的能力,也就是说服务端和客户端可以同时发送并响应请求,webSockets的目标是在一个单独的持久连接上提供全双工,双向通信,在Javascript中创建了webSOCket之后,会有一个HTTP请求发送到浏览器以发起连接,在取得服务器响应后,建立的连接会使用HTTP升级重HTTP协议交换为WebCocket协议,也就是说,只有支持WebSocket协议的服务器才能正常工作
由于WebSocket使用了自定义的协议,所以URL的模式也会略有不同。未加密的连接不再是http://,而是ws://;加密的连接也不是https://,而是wss://
WebSocket协议发送的数据包很小,因此非常适合移动应用,而且WebSocket只需要在建立连接的时候发送一次Headers
WebSocket也存在着一些缺陷,它的制定协议的事件比制定JavaScript API的时间还要长,并且存在一定的一致性和安全性的问题
下面我们来看一下WebSocket的使用
<script>
function WebSocket(){
var ws;
ws=new WebSocket("ws://localhost:5000");
ws.onopen=function(){//建立WebSocket连接时触发这个事件
console.log("客户端已经建立连接")
}
ws.send()//客户端向服务器发送数据
ws.onmessage=function(e){//有数据从客户端传来时触发,e.data数传过来的数据
console.log("这是服务端传过来的数据"+e.data)
}
ws.onclose=function(){//WebSocket连接断开时触发
console.log("已经和服务器断开连接")
}
ws.onerror=function(e){//WebSocket连接出现错误时触发,e.data是错误信息
console.log("发生错误,错误信息为"+e.data)
}
ws.close()//关闭WebSocket连接
}
</script>
同样,在WebSocket中,浏览器也会向服务器预先发送请求头
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
在这些信息中
- Origin
表明发送的请求源,也就是客户端的url地址 - Upgrade:websocket
Connection: Upgrade
表明发送的请求是WebScoket连接请求 - Sec-WebSocket-Key
这个是一个Base64 encode的值,这个是浏览器随机生成的,用来验证服务器是不是一个WebSocket的连接 - Sec-WebSocket-Protocol
这个是用户第一的字符串,用来区分同URL下不同服务器所需要的协议 - Sec-WebSocket-Version
表明WebSocket的版本号
服务端会返回 - HTTP/1.1 101 Switching Protocols
表示成功建立链接 - Upgrade:websocket
Connection: Upgrade
表示建立的链接是WebSocket连接 - Sec-WebSocket-Key
表示服务端经过确认加密后的的秘钥 - Sec-WebSocket-Protocol:chat
表示服务器最终使用的协议
WebSocket不受同源策略的限制,WebSocket的使用的局限性主要是由于浏览器的不兼容,IE10+以上才支持该方法,在不支持的浏览器中我们需要使用长连接的方法来模拟这个效果
CORS
CORS是一个W3C的标准,全称是跨域资源共享(Cross-origin resource sharing),它允许客户端向跨域的服务器发送XMLHttpRequest请求,从而克服了Ajax受到的同源策略的使用限制
CORS需要浏览器和服务器的同时支持,目前所有现代浏览器都支持CORS,IE10+以上都版本都支持CORS,整个CORS的过程都是浏览器完成的,不需要用户参与,对于开发者来说,CORS的请求和Ajax的请求没有区别,代码完全一样,浏览会自动检测,发现如果是使用Ajax请求跨域,就会自动添加一些附加的头信息,或者一次附加的请求
因此,实现CORS的关键在于服务器,只要服务器实现了CORS的接口,就可以实现跨源通信
浏览器将CORS分为了两类:简单请求和非简单请求
-
简单请求
只要同时满足一下两大条件,就属于简单请求- 请求的方式是POST,GET,HEAD
- HTTP的头信息不超过以下几种字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded
、multipart/form-data
、text/plain
凡是不满足以上条件的,都属于非简单请求
对于简单请求,浏览器在发送Ajax时发现是跨源并且是简单请求,就会自动在头信息中添加一个Origin字段,该字段用来说明这次请求的来源(协议+域名+端口),服务器会根据这个值来判断是否同意这次请求
如果服务器判断这次请求的源不在许可的范围之内,会返回一个正常的HTTP响应,但浏览器发现,这个回应的头信息没有包含 Access—Control—Allow— Origin字段,就会抛出一个错误,提示我们出现错误,这个错误需要我们通过XMLHttpRequest的onerror函数来捕获,注意,这种错误我们无法通过状态码识别,因为HTTP回应的状态码可能是200
如果Origin指定的域名在服务器的可允许的范围之内,服务器返回的响应,会多出几个头信息字段Access-Control-Allow-Origin: http://api.bob.com Access-Control-Allow-Credentials:true Access-Control-Expose-Headers:FooBar Content-Type:text-html;carset=utf-8
上面的的头信息中,有三个与CORS的请求相关的字段,都是以Access-Control-开头
- Access-Control-Allow-Origin
这个字段返回的值可能为Origin发送的请求的源,或者是一个"*"(表示允许任意的域名请求) - Access-Control-Allow-Credentials
该字段为可选值,它的值为一个布尔值,它的值只能为true,表示是否允许发送Coolie,默认情况下,CORS是不会发送Cookie的,设置为true表示服务器明确许可接收服务的Cookie,如果服务器设置不接收的话只需要删除掉该字段 - Access-Control-Expose-Headers
该字段可选。CORS请求时,XMLHttpRequest
对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
。如果想拿到其他字段,就必须在Access-Control-Expose-Headers
里面指定。上面的例子指定,getResponseHeader('FooBar')
可以返回FooBar
字段的值
上面我们已经提到了,在CORS中是默认不发送Cookie的,如果要发送Cookie,需要服务器设置Access-Control-Allow-Credentials的值为true,另一方面,我们在客户端需要设置Ajax中打开withCredentials的属性
var xhr=new XMLHttpRequest(); xhr.withCredentials=true;
否则,浏览器不会处理和发送Cookie,但是有些浏览器是默认发送Cookie的,我们在不需要发送的时候,将其值设为false,来将其关闭
需要注意的是,如果要接收Cookie的话,Access-Control-Allow-Origin的值不能设置为"*",必须是明确指定的,与请求的也面一致的域名,同时,Cookie仍然遵循同源策略,只有服务器域名设置的Cookie才会上传,其它域名的Cookie并不会上传,而且我们的网页也无法读取服务器下的Cookie -
非简单请求
非简单请求一般是对服务器有特殊要求的请求,例如PUT,DELETE,或者Content-Type字段的类型为 application/json
非简单请求的CORS请求,在正式通信之前,会增加一次HTTP查询请求,称为" 预检"请求
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用那些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会正式发出XMLHttpRequest请求,否则就报错,下面是一段JavaScript的代码:var url = 'http://api.alice.com/cors'; var xhr = new XMLHttpRequest(); xhr.open('PUT', url, true); xhr.setRequestHeader('X-Custom-Header', 'value'); xhr.send();
上面的代码中,HTTP请求的方法时PUT,并且发送了一个自定义的 头信息X-Custom-Header;
浏览器在发现这是一个非简单的请求之后,会自动发送一个预检的请求,要求服务器确认可以这样进行请求,下面是这个预检请求的HTTP头信息OPTIONS/cors HTTP/1.1 Origin:http://api.bob.com Access-Control-Request-Method:PUT Access-Control-Request—Headers:X-Custom-Headers Host:api.alice.com Accept-Language:en-US Connection:keep-alive User— Agent:Mozilla/5.0...
预检请求使用的方式是 OPTIONS,表示这个请求是用来询问的。头信息里,关键字是Oringin,表示请求来自哪个源
预检请求中还包括两个特殊的字段- Access-Control-Request-Method
该字段用来表示浏览器的CORS用哪个方法来请求数据,我们例子中用到的是PUT方法 - Access-Control-Request-Headers
该字段用来表示客户端会额外发送一个自定义的请求头,我们例子中使用的是 X-Custom-Headers
服务器在收到预检请求以后,会检查Origin,Access-Control-Request-Method以及Access-Control-Request_Headers字段,在确认允许跨域请求数据后,就会做出回应
HTTP/1.1 200 OK Data:Mon,01 Dec 2016 01:15:39 GMT Server:Apache/2.0.61(Unix) Access-Control-Allow-Origin:http://api.bob.com Access-Control-Allow-Methods:GET,POST,PUT Access-Control-Allow-H eaders:X-Custom-Headers Content-Type:text/html;charset=utf-8 Access-Control-Max-Age:1728000 Content-Encoding:gzip Content-Length:0 Keep-Alive:timeout=2,max=100 Connection:Keep-Alive Content-Type:text/plain
在上面的HTTP回应中,关键的是Access-Control-Allow-Origin表示允许http://api.bob.com请求数据,该字段也可以设置为“*”,表示允许任意跨源获取数据
其中有几个关键的字段- Access-Control-Allow-Methods
代表服务器所允许的跨源请求的方法,在这里返回的是支持的所有的方法,而不是单个方法,这是为了避免出现多次的预检请求 - Access-Control-Allow-Headers
表示 如果浏览器请求包含了Access-Control-Request-Headers,服务器会返回 Access- Control-Allow-Headers,它会返回所有服务器支持的头字段 - Access-Control-Request- Credentials
与简单请求相同,表示是否发送Cookie - Access-Control-Max-Age
该字段是可选字段,用来指定本次预检请求的有效时间,单位为秒,上面的结果为1728000秒,也就是20天,说明允许缓存该条回应20天,在此期间不需要发送额外的预检请求
如果服务器正常回应后,浏览器与简单请求一样返回信息
如果浏览器否定了预检请求,会返回一个正常的HTTP响应,但是没有任何CORS的头信息字段,这时浏览器就会认定,服务器不同意跨源请求,会发送一个错误,可以通过onerror事件来获取,会打印出如下的报错信息
XMLHttpRequest cannot load http://api.alice.com. Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin
- Access-Control-Request-Method
与jsonP相比,CORS的跨域更加安全和灵活,缺点是浏览器兼容问题,不过未来CORS会是主流的选择