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

<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

在这些信息中

  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会是主流的选择

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容