在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