在web後臺開發中咱們常常須要存儲一些變量到session中進行暫存,最爲特殊的就是「購物車」,因爲http的無狀態特性,所以咱們須要在客戶端打上一個標記,惟一的標示客戶端並和服務端session一一對應,所以就有了經過cookie和url進行存儲或傳遞這個標示--sessionID。
sessionID是一個長的字符串,它每每默認經過cookie來保存,這個session並不持久化到硬盤而是暫存到內存中,每次請求時都會在head中帶上這個包含sessionID的cookie,服務端能夠根據該id標示出客戶端,從而訪問服務器的一片內存區;另外一種經過url方式傳遞sessionID,當cookie在客戶端被禁用時,服務端會將生成的sessionID加入到url,以此完成sessionID的傳輸,又稱url回寫。可是url回寫會有明顯的安全漏洞,當該網站被xss注入時,攻擊者就能夠經過竊取的sessionID訪問服務端的隱私數據。javascript
無心中,在snoopyXDY的文章中看到了用ETag進行兼容,大喜,並感慨於該方法的奇妙。(以前遇到過ETag在服務器集羣中同步的問題,緣由是在服務端生成ETag的方式不妥,最終解決方案就是針對請求文件的內容進行hash並base64編碼,這樣在服務端同步的前提下,請求任意服務器都會返回相同的ETag)在此處實現中,則是異步請求一個js文件,該文件會根據客戶端的相關頭部設置或獲取session,而且在服務端觸發客戶端的相關事件,完成數據的傳遞。
github地址:https://github.com/royalrover/ETag-Sessionhtml
首先,咱們訪問登錄頁面login.html,在頁面底部的script中,異步加載一個名稱爲‘eTag.js’的文件,這個文件並非靜態的,而是由服務端根據客戶端傳遞的參數進行相應處理:若是客戶端的request頭部有‘if-none-match’字段,則會在內存中查看是否有該字段對應的value(服務端用hash進行存儲各個客戶的的session),並將該value值序列化,同時觸發客戶端的‘etag-ready’事件,並將序列化的value做爲值傳入。
在這裏的實現中,有可能會存在瀏覽器對動態文件‘eTag.js’的緩存,爲了不‘eTag.js’的準確和實時,所以須要設置‘cache-control’頭部。java
router.get('/_eTag_.js',function * (next){ var ctx = this; var etag = ctx.header['if-none-match']; var cache; if(!etag) { etag = new Date().getTime() + '__etag'; } // console.log(session) if(session[etag]) { cache = session[etag]; }else { cache = { etag: etag }; } cache = JSON.stringify(cache); ctx.set('ETag',etag); ctx.set('cache-control','no-cache'); ctx.set('content-type','application/javascript'); // ctx.body = 'window._session = '+ cache + ';'; ctx.body = 'd.do("etag-ready",'+ cache +')'; // console.log('cache: '+cache) yield *next; })
在處理post請求時,node並不會解析body,所以須要咱們本身來搞定,能夠經過模塊,也能夠簡單的經過訂閱事件,在這裏我是簡單的用node原生的request對象進行偵聽。
爲了不js阻塞渲染,採用異步加載的方式獲取,但這也會形成從服務端獲取的數據不能及時被客戶端處理和渲染,爲了解決這個問題,此處採用了重量級應用必備的解決方案-Bigpipe,有服務端出發客戶端訂閱的事件,一旦服務端去到session數據,則觸發'etag-ready'事件,並在客戶端進行邏輯處理和渲染。
客戶端的邏輯以下:node
function $(n){ return document.querySelectorAll(n); } function asyncLoad(src) { var s = document.createElement('script'); s.src = src; s.async = true; document.body.appendChild(s); } function DO(){ this.cbs = []; } DO.prototype.on = function(k,cb){ if(!this.cbs[k]) { this.cbs[k] = []; } this.cbs[k].push(cb); }; DO.prototype.do = function(k,data){ if(!this.cbs[k]) return; var cbs = this.cbs[k],len = cbs.length; for(var i=0;i<len;i++){ cbs[i].call(this,data); } }; asyncLoad('/_eTag_.js'); var d = new DO(); d.on('etag-ready',function(_session){ console.log('etag-ready...'); console.log(_session); if(_session && _session.etag && !_session.usrname) { $('[name=etag]')[0].value = _session.etag; }else { $('[name=usrname]')[0].value = _session.usrname; $('[name=pwd]')[0].value = _session.pwd; $('[name=etag]')[0].value = _session.etag; } })
使用ETag方式來hack兼容性是很是棒的,幾乎全部的服務器都實現了這個機制(HTTP1.1規範),所以兼容性不是問題。因爲使用ETag加載的文件的元數據都保存在瀏覽器的緩存中,所以安全性是無法與存儲在內存中的cookie方式相比的,並且若是清空瀏覽器緩存,那麼客戶端則丟失sessionID,無法在使用session。所以這種方式也僅僅做爲cookie被禁用的一種候補方案,不推薦大規模使用。git