JavaScript 通信 / CORS、WebSocket

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的使用


同样,在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

在这些信息中

  1. Origin
    表明发送的请求源,也就是客户端的url地址
  2. Upgrade:websocket
    Connection: Upgrade
    表明发送的请求是WebScoket连接请求
  3. Sec-WebSocket-Key
    这个是一个Base64 encode的值,这个是浏览器随机生成的,用来验证服务器是不是一个WebSocket的连接
  4. Sec-WebSocket-Protocol
    这个是用户第一的字符串,用来区分同URL下不同服务器所需要的协议
  5. Sec-WebSocket-Version
    表明WebSocket的版本号
    服务端会返回
  6. HTTP/1.1 101 Switching Protocols
    表示成功建立链接
  7. Upgrade:websocket
    Connection: Upgrade
    表示建立的链接是WebSocket连接
  8. Sec-WebSocket-Key
    表示服务端经过确认加密后的的秘钥
  9. 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-urlencodedmultipart/form-datatext/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-开头

    1. Access-Control-Allow-Origin
      这个字段返回的值可能为Origin发送的请求的源,或者是一个"*"(表示允许任意的域名请求)
    2. Access-Control-Allow-Credentials
      该字段为可选值,它的值为一个布尔值,它的值只能为true,表示是否允许发送Coolie,默认情况下,CORS是不会发送Cookie的,设置为true表示服务器明确许可接收服务的Cookie,如果服务器设置不接收的话只需要删除掉该字段
    3. Access-Control-Expose-Headers
      该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在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,表示请求来自哪个源
    预检请求中还包括两个特殊的字段

    1. Access-Control-Request-Method
      该字段用来表示浏览器的CORS用哪个方法来请求数据,我们例子中用到的是PUT方法
    2. 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请求数据,该字段也可以设置为“*”,表示允许任意跨源获取数据
    其中有几个关键的字段

    1. Access-Control-Allow-Methods
      代表服务器所允许的跨源请求的方法,在这里返回的是支持的所有的方法,而不是单个方法,这是为了避免出现多次的预检请求
    2. Access-Control-Allow-Headers
      表示 如果浏览器请求包含了Access-Control-Request-Headers,服务器会返回 Access- Control-Allow-Headers,它会返回所有服务器支持的头字段
    3. Access-Control-Request- Credentials
      与简单请求相同,表示是否发送Cookie
    4. 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
    

与jsonP相比,CORS的跨域更加安全和灵活,缺点是浏览器兼容问题,不过未来CORS会是主流的选择

你可能感兴趣的:(JavaScript 通信 / CORS、WebSocket)