前端通訊:SSE設計方案(二)--- 服務器推送技術的實踐以及一些應用場景的demo(包括在線及時聊天系統以及線上緩存更新,代碼熱修復案例)

  距離上一篇博客,這篇文章的發佈大概過了整整三個月。下面廢話很少說了,直接進入主題。html

上一篇博客介紹了基礎的純概念,這篇文章純粹偏技術實踐,須要理解一些玩意的。技術介紹前端

  • 客戶端基礎類庫代碼 -- SSE.js和ajax.1.7.js    客戶端建立鏈接和定義監聽的代碼 以及結合ajax完成雙工通道,完成雙向均可推送
  • node -- SSE_server.js      node服務器代碼,處理請求和消息推送
  • nginx     做爲測試服務器進行瀏覽器和局域網測試

官方文檔和設計方案node

SSE.js 代碼改動(比較上一個版本)nginx

 1   // 拋出對象
 2   var output = {
 3     create:function (options) {
 4       var param = tool.initParam(options),sendData = '';
 5 
 6       if (param.data){
 7         tool.each(param.data, function (item, index) {
 8           sendData += (index + "=" + item + "&")
 9         });
10         sendData = sendData.slice(0, -1);
11       }
12 
13       var es = new EventSource(param.url+'?'+sendData);
14 
15       es.addEventListener('open',function (e) {
16         param.openEvent(e)
17       });
18 
19       es.addEventListener('message',function (e) {
20         param.messageEvent(e)
21       });
22 
23       es.addEventListener('error',function (e) {
24         param.errorEvent(e)
25         es.close()  // 關閉鏈接
26       });
27 
28       // 建立用戶自定義事件
29       if (param.customEvent.length > 0){
30         tool.each(param.customEvent,function (item) {
31           es.addEventListener(item.name,item.callback);
32         })
33       }
34     }

 

改動:git

針對上一個版本,增長了數據驗證以及一些參數的傳遞。雖然開啓withCredentials能夠傳遞信息的憑據,好比cookie,可是畢竟一個url,也是能夠用url帶參傳遞過去的,因此額外增長了這個玩意。so,這樣咱們就能夠在非ie系列的瀏覽器上痛快的翱翔了。github

PS:上面代碼爲建立的核心代碼,其餘代碼能夠到github上看,或者直接npm安裝好了看。ajax

 

客戶端的準備工做搞好了,下一步就是服務器的工做了,由於一個技術確定是多個技術配合才能完成的,不懂服務器的前端,不是好前端,這是我一項承認的事情。chrome

 

下面爲簡易的node測試代碼,沒有那麼追求精簡,爲了測試搞的。npm

  1 var http = require("http");
  2 var url=require('url');
  3 var qs=require('querystring');//解析參數的庫
  4 var sendObject = {}, count = 0;
  5 var gerry = {}
  6 
  7 // 向除本身的全部人推送信息
  8 function sendAll(data) {
  9   for (let index in sendObject){
 10     if (data.name !== index){
 11       sendObject[index].write("retry: " + data.retry + "\n");
 12       if (data.event){
 13         sendObject[index].write("event: " + data.event + "\n");
 14       }
 15 
 16       let sengData = {
 17         author:data.name,
 18         data:data.data
 19       }
 20       sendObject[index].write("data: " + JSON.stringify(sengData) + "\n\n");
 21     }
 22   }
 23 }
 24 
 25 // 聊天系統的demo推送測試
 26 http.createServer(function (req, res) {
 27 
 28     var arg1=url.parse(req.url,true).query;
 29     console.log(arg1.name);
 30     res.setHeader('Access-Control-Allow-Origin', '*');
 31     res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
 32     res.setHeader('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
 33     res.writeHead(200, {
 34         "Content-Type": "text/event-stream",
 35         "Cache-Control": "no-cache",
 36         "Connection": "keep-alive"
 37     });
 38 
 39     res.setTimeout(5000,()=>{
 40         res.write(":this is test")
 41     })
 42 
 43 
 44 
 45     sendObject[arg1.name] = res;
 46 }).listen(8074);
 47 
 48 // ajax雙工執行發送機制
 49 http.createServer(function (req, res) {
 50     res.setHeader('Access-Control-Allow-Origin', '*');
 51     res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
 52     res.setHeader('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
 53     res.writeHead(200,{
 54         "content-type":"application/json; charset=utf-8"
 55     });
 56   var arr = []
 57     new Promise(function(x,xx){
 58       req.on("data",function(data){
 59         arr.push(data);
 60       });
 61 
 62       req.on("end",function(){
 63         var data= Buffer.concat(arr).toString(),ret;
 64         try{
 65           ret = JSON.parse(data);
 66           x(ret)
 67         }catch(err){}
 68       })
 69   }).then(function (req) {
 70       var data = {
 71         retry: 10000,
 72         name: req.name,
 73         data: req.message,
 74         event:req.event
 75       }
 76       sendAll(data)
 77       // res.write(Buffer.concat(arr).toString())
 78 
 79     })
 80   res.end()
 81 }).listen(8075)
 82 
 83 
 84 var cacheUpdate = {}
 85 
 86 // 緩存更新和線上正在使用的代碼熱修復和強制用戶從新請求去拉取最新代碼
 87 http.createServer(function (req, res) {
 88 
 89   var arg1=url.parse(req.url,true).query;
 90   console.log(arg1.name);
 91   res.setHeader('Access-Control-Allow-Origin', '*');
 92   res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
 93   res.setHeader('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
 94   res.writeHead(200, {
 95     "Content-Type": "text/event-stream",
 96     "Cache-Control": "no-cache",
 97     "Connection": "keep-alive"
 98   });
 99 
100   res.setTimeout(5000,()=>{
101     res.write(":this is test")
102   })
103   cacheUpdate[arg1.name] = res;
104 }).listen(8076);
105 
106 // 觸發推送的動做
107 http.createServer(function (req, res) {
108   res.setHeader('Access-Control-Allow-Origin', '*');
109   res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
110   res.setHeader('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
111   res.writeHead(200,{
112     "content-type":"application/json; charset=utf-8"
113   });
114   var arr = []
115   new Promise(function(x,xx){
116     req.on("data",function(data){
117       arr.push(data);
118     });
119 
120     req.on("end",function(){
121       var data= Buffer.concat(arr).toString(),ret;
122       try{
123         ret = JSON.parse(data);
124         x(ret)
125       }catch(err){}
126     })
127   }).then(function (req) {
128     var data = {
129       retry: 10000,
130       name: req.name,
131       data: req.message,
132       event:req.event
133     }
134 
135     cacheUpdate[data.name].write("retry: " + data.retry + "\n");
136     cacheUpdate[data.name].write("event: " + data.event + "\n");
137     cacheUpdate[data.name].write("data: " + data.data + "\n\n");
138 
139   })
140   res.end()
141 }).listen(8077)
node服務器測試代碼

 

最重要的環節開始了,下面就是個人show time。json

1.  聊天系統的demo

  介紹:既然服務器有了主動推送給客戶端的能力,那麼最重要的基礎場景就是交流。因此這個聊天系統demo應運而生

  使用的類庫:ajax類庫+sse類庫(這裏的ajax隨便你們用什麼通訊類庫,我這裏使用的是我本身第一階段研究的類庫)

  demo查看:https://github.com/GerryIsWarrior/SSE/blob/master/js-demo/index_chat.html

 

測試(模擬了2我的,只要服務器吃的消,多少人均可以):

 

總結: 這個聊天系統的demo,建立了2個用戶,用左邊用戶進行發送,監控右邊用戶的控制檯的network,能夠查看到通訊的請求,在左邊用戶每次發完消息以後,右邊用戶每次都接受到數據,而後處理到聊天系統界面。其實sse後端底層也相似於長鏈接,只是多了一個服務器能夠反向推的動做。

 

2.   線上客戶端緩存的更新demo

  介紹:由於前端瀏覽器的機制,將全部用的數據請求過來,沒有請求的頁面也能跑起來。因此對於作前端優化的同窗就明白一件事,但願用戶能更多的緩存命中咱們存儲在用戶瀏覽器中的一些數據,這樣對於第一次加載以後,若是一些緩存數據沒動的data,直接取出來,渲染到用戶瀏覽器中。這樣給用戶的感受就是秒開的感受(你們能夠參考京東網站的首頁優化,首屏啊,二次打開等等)

  使用類庫:SSE.js+原生js+postMan去觸發推送

  demo查看:https://github.com/GerryIsWarrior/SSE/blob/master/js-demo/index_cache.html

 

測試(模擬localStory緩存用戶數據更新)

 

總結:測試頁面,首先我在代碼中固定寫了一個dom的我是基礎緩存的數據,存到localStory中。而後對於這個打開咱們的網頁在線上的用戶,我使用了postman往個人藉口裏發送數據,觸發推送的動做,好比個人demo中就是發送了一個‘我試更新過的混存數據’,而後推送到客戶端,而後客戶端接收到這個event,而後更新緩存。這樣就完成了線上緩存更新的方式。這樣避免了讓用戶再刷一次頁面(從新請求頁面數據,比對緩存版本啥的),畢竟用戶,纔是咱們的上帝。

 

3.   線上代碼熱修復以及生產版本更新提示用戶從新拉取頁面

  介紹:對於如今更多的spa來講,瀏覽器下載結束了代碼,你使用的代碼就是瀏覽器下載的,若是這塊前端代碼是有問題的,那麼生產跑得代碼都是有問題代碼,除非用戶手動刷新頁面,從新請求一個正確的生產代碼。

  demo查看:https://github.com/GerryIsWarrior/SSE/blob/master/js-demo/index_hotRepair.html

 

測試(模擬一個線上案例):

 

總結:這個案例就是這樣,以前咱們寫死了線上代碼要打8折,因此咱們最後計算金額的時候就是按照8折去計算的。可是產品過來講,不該該這樣啊,這TM不是打9折嗎?這樣公司會虧損嚴重的。而後你們都矇蔽,修正發佈。而後發現用戶的頁面仍是8折,用戶沒刷新。尷尬.....

so,經過正在線上的推送,來進行代碼的熱修復。好比我推送了一個9折的信息,而後客戶端接受處理了,哪怕正在線上的用戶都按正確的9折來計算。挽回了公司的損失。

PS:若是線上改動太多太多了,很差進行局部熱修復方案,只能讓正在用的用戶強制更新,拉新數據。

就像上圖同樣,用戶點肯定就從新刷新頁面,拉取新代碼。

 

瀏覽器的測試兼容結果:chrome,safair,opera,firefox均可以跑

 

 

結語:這是sse技術研究的第二階段,能夠在非IE系列的瀏覽器上都跑起來了,並且針對不一樣場景的操做和優化都有不少的想法。每一個想法真正寫成一個成熟的東西,都能讓前端變得更好。第三階段,暫時定爲SSE的收尾階段,解決一些兼容性問題和之後用戶提出的新問題。其實,搞前端這麼久了,我感受針對每一個技術自己,都不是什麼很牛逼和好玩的技術,可是把許多單個的技術融合起來,這樣就會發現新的大陸同樣,能創造奇蹟。

github地址: https://github.com/GerryIsWarrior/SSE   若是感受好,或者能解決你的問題,能夠點個star

 

最近感悟最深的一句話:我可能推進不了前端技術的進步,可是我可讓前端技術變得更美好.....共勉

相關文章
相關標籤/搜索