在幾年前,天空一聲巨響,ajax 閃亮登場. 前端寶寶們如獲至寶~ 已經表單送出神馬的, 真的太 心累了. 有了ajax之後, 網頁的性能可大幅提升,告别重新整理,告别如水的流量. 不過,長江後浪推前浪,一代更比一代強. 由于ajax被同域限制着, 導緻, 多伺服器配置,雲服務資源的存儲 沒辦法充分利用. 是以,業界想到另外一種方法--JSONP. JSONP實際上和ajax沒有半點關系,唯一相同的就是都是異步執行,而且JSONP完美解決了CD(cross domain)問題. 科技就是第一生産力, web發展so fast. 以前追求就是靜态網頁,顯示資訊而已。 現在,正朝着web2.0,webapp前進。 以前的單向交流 已經不能滿足 需求了。 怎麼辦呢? 改呗~ 是以,緊接着SSE,websocket 誕生了. 至今為止, 前端通信方式算是告一段落。 這裡我們将圍繞上述的幾種通信方式進行,簡單的介紹. 以下是幾個技術的順序.
ajax
JSOP
SSE
websocket
ok~ 進入主題吧~
相信這個應該不用過多的講解了吧. 差不多就4步:
建立xhr對象
監聽請求
設定回調
設定參數
發送xhr
獲得資料執行回調
這裡,我就直接上代碼了.
這樣差不多就完成了一個ajax的簡單模型。當然,我們也可以使用jquery提供的$.ajax函數, 隻是他裡面做了更多的相容性和功能性.
JSONP 就是 JSON with Padding... 我真的不知道這個名字的含義到時有什麼卵用... 一開始在使用JSONP時, 就是使用jquery的$.ajax函數就可以了. 但,這造成了一個很不好的impression. 總是讓我們以為,JSONP 和 ajax有什麼關聯似的. 而,事實上,他們兩個是完全不同的機制. xhr原理大家已經很清楚了,就是完完全全的異步操作. 但JSONP的原理是什麼呢?
JSONP 其實是和< script> 标簽 有很大的關系. JSONP最大的優勢就是實作異步跨域的作用, 他到底是怎麼做到的呢? 其實, JSONP就是利用script的 src屬性,實作跨域的功能.
talk is cheap, show the code
上面的寫法有點不符合前端風味. 說明一下, 其實processJSON,其實就相當于一個回調函數而已. 在script--src裡面的内容我們來瞧一瞧. 使用jsoncallback 來指定回調函數名字, 并且傳入一些參數
name = jimmy
age = 18
這就是前端發送JSONP的全部. 那應該怎麼執行呢?或者說,傳回的内容是什麼呢? 很簡單, 根據jsoncallback裡面指定的函數名--processJSON. 在傳回的js裡面使用processJSON(data); 來執行. 伺服器端傳回的js内容.
然後,浏覽器收到後,直接執行即可. 這裡,我們來模拟一下伺服器端蓋怎樣執行一個JSONP的函數.
ok~ 上面基本上就可以完成一個簡單的JSONP函數執行。 當然,express 4.x 裡面也有相關的JSONP 操作。 有興趣的同學可以看一看.
then, 我們可以模拟一下實在的JSONP請求.上面是直接将script寫死在html内部, 這樣造成的結果可能會阻塞頁面的加載. 是以,我們需要以另外一種方式進行,使用異步添加script方法.
上面就是一個精簡版的JSONP了。 另外,也推薦使用jquery的getJSON和$.ajax進行請求. 先看一下getJSON
這裡,我們需要關注一下url裡面中<code>callback=?</code>裡的<code>?</code>的内涵. jquery使用自動生成數的方式, 省去了我們給回調命名的困擾。 其實,最後<code>?</code>會被一串字元代替,比如: <code>json23153123</code>. 這個就代表你的回到函數名. 不過,還是推薦使用<code>$.ajax</code>,因為你一不小心就有可能忘掉最後的?. 使用$.ajax發送jsonp
這樣,我們就可以利用jquery很簡單的發送jsonp了.
ajax和JSONP 都是 client-fetch的操作. 但是有時候, 我們更需要伺服器主動給我們發資訊. 比如,現在的APP應用,完全可以實作伺服器發送, 然後Client再處理. 而,SSE就是幫助我們向webapp靠近. SSE 全稱就是 Server-Sent Events. 中譯 為 <code>伺服器推送</code>.
他的技術并不是很難,和websocket不同,他依賴原生的HTTP,是以對于開發者來說更好了解。 比如,在nodeJS, 隻要我不執行res.end(),并且一定時間持續發送資訊的話,那麼該連接配接就會持續打開(keep-alive). 其實通俗來說,就是一個長連接配接. 是以,以前我們通常使用ajax,iframe長輪詢來代替他.但是這樣有個缺點就是, 可操控性弱, 錯誤率高。 是以,正對于這點W3C, 覺得需要在用戶端另外指定一個機制--能夠保證伺服器推送, 實作連接配接的keep-alive,操作簡單... 在這樣背景下SSE誕生了. 但SSE和AJAX具體的差別在什麼地方呢?
資料類型不同: SSE 隻能接受 type/event-stream 類型. AJAX 可以接受任意類型
結束機制不同: 雖然使用AJAX長輪詢也可以實作這樣的效果, 但是, 伺服器端(nodeJS)必須在一定時間内執行res.end()才行. 而SSE, 隻需要執行res.write() 即可.
先看一個client端, 一個比較簡單的demo
SSE主要就是建立一個EventSource對象. 裡面的參數就是發送的路由, 不過目前還不支援CORS,是以也被限制在同源政策下. 在傳回的source裡面包含了,需要處理的一切資訊.SSE也是通過事件驅動的,如上面demo所述. 這裡,SSE通常有一下幾類重要的事件. |eventName|effect| |:---|:---| |open|當連接配接打開時觸發| |message|當有資料發送時觸發, 在event對象内包含了相關資料| |error|當發生錯誤時觸發|
上面幾個方法比較重要的還是message方法. message主要用來進行資訊的接受, 回調中的event 包含了傳回的相關資料. event包含的内容 |property|effect| |:---|:---| |data|伺服器端傳回的資料| |origin|伺服器端URL的域名部分,有protocol,hostname,port| |lastEventId|用來指定目前資料的序号.主要用來斷線重連時資料的有效性|
上文說過,SSE 是以event-stream格式進行傳輸的. 但具體内容是怎樣的呢?
上面就是一個簡單的demo. 每一段資料我們稱之為事件, 每一個事件經過空行分隔. :前面是資料類型,後面是資料. 通常的類型有:
空類型: 表示注釋,在處理是會預設被删除.比如:<code>this is a comment.</code>
event: 聲明該事件類型,比如message.
data: 最重要的一個類型, 表示傳輸的資料。可以為string格式或者JSON格式. 比如:<code>data: {"username": "bobby"}</code>
id: 其實就是lastEventId. 用來表明該次事件在整個流中的序号
retry: 用來表明浏覽器斷開再次連接配接之前等待的事件(不常用)
其實上面最重要的兩個字段就是data,id. 是以,我們一般擷取的話就可以使用 <code>event.data</code>和<code>event.lastEventId.</code> 上文說道, 每一段内容是通過換行實作的, 那伺服器端應該怎麼實作, 寫入的操作呢? 同樣, 這裡以nodeJS 為例:
通過使用'\n\n'進行兩次換行操作--即,産生空行即可.
伺服器端不僅可以傳回指定資料,還可以傳回指定事件.不過預設情況下都是message事件, 但我們也可以指定事件. 比如
這裡出發的就是 myevent事件。 即, 這就是觸發自定義事件的方式. 在front-end 我們可以使用addEventListener 來進行監聽.
由于使用的是HTTP協定,是以對于服務端基本上沒什麼太大的改變. 唯一注意的就是, 發送資料使用res.write()即可,斷開的時候使用res.end();
Ok~ 這裡有一個demo, 大家可以打開控制台看一下. 會發現,有一個連接配接一直處于Content-Download狀态. 該連接配接就是一個SSE。
相容性
目前SSE,在市面上大受歡迎, 不過總有一個SB, 離經叛道... 居然連edge都不支援. 偶爾去翻了一下,還在underConsideration. 結果底下的評論基本都是xxxx. 有空可以去看看, 逼逼MS程式員.
websocket 不同于其他的HTTP協定,他是獨立于HTTP存在的另外一種通信協定。比如,像這樣的一個路徑ws://websocket.example.com/,就是一個websocket 通信. 通常的實時通信并不會傳輸大量的内容, 是以,對于HTTP協定那種,進行連接配接時需要傳遞,cookie和request Headers來說, 這種方式的通信協定,會造成一定的時延(latency). websocket通信協定就是在這樣的背景下誕生了, 他與SSE,ajax polling不同的是--雙向通信.
我們來看一個簡單的websocket demo
可以說上面就是一個健全的websocket 通信了. 和SSE一樣,我們需要建立一個WebSocket對象, 裡面的參數指定連接配接的路由. 而且,他也是事件驅動的. 常見的事件監聽有. |event|effect| |:---|:---| |open|當ws連接配接建立時觸發| |message|當有資訊到來時觸發| |error|當連接配接發生錯誤時觸發| |close|當連接配接斷開時觸發|
另外,websocket 最大的特點就是可以雙向通信。這裡可以使用.<code>ws.send()</code>方法發送資料, 不過隻能發送String和二進制. 這裡,我們通常call 資料叫做<code>Frames</code>. 他是資料發送的最小單元.包含資料的長度和資料内容. 下面就是幾種常用的發送方式
另外還可以使用binaryType指定傳輸的資料格式,不過一般都用不上,就不說了. 不過需要提醒的是, send方法,一般在open和message的回調函數中調用.
同理,和SSE差不多, 通過監聽message事件,來接受server發送回來的資料. 接受其實就是通過<code>event.data</code>來擷取. 不過, 需要和server端商量好data的類型.
那server端應該怎樣處理websocket通信呢? websocket雖然是另外一種協定,不過底層還是封裝了TCP通信, 是以使用nodeJS的net子產品,基本就可以滿足,不過裡面需要設定很多的頭. 這裡推薦使用ws子產品.
簡單的websocket demo
可以參考treeHouse 編寫的WSdemo
由于websocket 本身的協定對于資料格式來說,不是特别的清晰明了,ws可以傳輸text,blob,binary等等其他格式. 這樣對于安全性和開發性能來說,友好度很低。是以,為了解決這個問題, subprotocols 出現了. 在使用時,client和server都需要配置一樣的subprotocols. 例如:
服務端需要将subprotocols發送過去, 在handshakes的過程中,server 會識别subprotocols. 如果,server端也有相同的子協定存在, 那麼連接配接成功. 如果不存在則會觸發error, 連接配接就被斷開了.
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 is subject to the same-origin policy WebSocket is not subject to the same-origin policy
看到這裡我也是醉了. 事實上websocket 是可以跨域的。 但是為了安全起見, 我們通常利用CORS 進行 域名保護.
即,設定如下的相應頭: <code>Access-Control-Allow-Origin: http://example.com</code>這時, 隻有http://example.com 能夠進行跨域請求. 其他的都會deny.
那什麼是CORS呢?
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是什麼意思呢?
該屬性就是用來表明,你的request的時候,是否帶上你的cookie. 預設情況下是不帶的. 如果你要發送cookie給server的話, 就需要将withCredentials設定為true了. <code>xhr.withCredentials = true;</code>但是,server并不是随便就能接受并傳回新的cookie給你的。 在server端,還需要設定. <code>Access-Control-Allow-Credentials: true</code>這樣server才能傳回新的cookie給你. 不過,這還有一個問題,就是cookie還是遵循same-origin policy的。 是以, 你無法使用去通路他. 他的CRUD(增删查改)隻能由 server控制.
CORS的preflight request, 應該算是CORS中裡面 巨坑的一個。 因為在使用CORS 的時候, 有時候我命名隻發送一次請求,但是,結果出來了兩個。 有時候又隻有一個, 這時候, 我就想問,還有誰能不懵逼. 這裡,我們就需要區分一下. preflight request的作用到底是什麼。 preflight request 是為了, 更好節省寬帶而設計的. 因為CORS 要求的網絡品質更高, 而且 花費的時間也更多. 萬一, 你發送一個PUT 請求(這個不常見吧). 但是服務端又不支援, 那麼你這次的 請求是失敗了, 浪費資源還不說,關鍵使用者不能忍呀~ 是以, 這裡我們就需要區分,什麼是簡單請求, 什麼是比較複雜的請求 簡單請求 簡單請求的内容其實就兩塊, 一塊是method 一塊是Header
Method
GET
POST
Header
Accept
Accept-Language
Content-Language
Last-Event-ID //這是SSE的請求頭
Content-Type ,但隻有一下頭才能算簡單 application/x-www-form-urlencoded
multipart/form-data
text/plain
比如, 我使用上面定義好的函數createCORSRequest. 來發送一個簡單請求
我們來看一下,隻發送一次簡單請求時,請求頭和相應頭各是什麼.(剔除無關的Headers)
上面就是一個簡單的CORS 頭的互動。 另外,說明一個Access-Control-Allow-Origin,該頭是必不可少的. 本來在XHR中, 一般可以通過xhr.getResponseHeader()來擷取相關的相應頭。 但是 在CORS中一般可以獲得如下幾個簡單的Header:
Cache-Control
Content-Type
Expires
ETag
Last-Modified
Pragma
如果你想暴露更多的頭給使用者的話就可以使用,<code>Access-Control-Expose-Headers</code>來進行設定. 多個值用','分隔. 那發送兩次請求是什麼情況呢? 我們如果請求的資料是application/json的話,就會發送兩次請求.
第一次,我們通常叫做preflight req. 他其實并沒有發送任何 data過去. 隻是将本次需要發送的請求頭發送過去, 用來驗證該次CORS請求是否有效. 上面的請求頭就有:
<code>Access-Control-Request-Method</code>就是用來表明,該次請求的方法. 請求内沒有任何附加的資料. 如果該次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 的運作流程圖=>
看不清的話,請建立一個标簽頁看,放大就能看見了.
不多說了, 上圖~
原文連結:http://www.ivweb.io/topic/5915c353869edc1f59d6ba0f