Session

    web网站基于HTTP协议来做业务交互,HTTP协议本身是一个无状态的协议,但实际情况是日常很多业务逻辑都需要记录用户的行为信息。举个例子:

一个购物网站,顾客会在商品浏览页面选取需要的各类商品,放置到虚拟的购物车内。下单的时候需要跳转到结算页面,在这一串连续的用户行为中,至关重要的一点是记录当前的购物车信息,而session机制可以恰当的处理此类问题。

    网站通常会在用户第一次登录的时候生成一个Session ID, Session ID不同于用户ID, 用户ID始终唯一,但是同一个用户每次登录获取的Session ID可能不一致。Session ID由服务器端生成,通过HTTP协议头返回给客户端,这样可以极大的降低可能导致的安全问题(其他劫持暂且不说)。因为浏览器拿到的只是一个token,而与此相关的数据存储都在服务器端完成。服务端在设置session ID的同时会指定改session id的过期时间,以及该session id试用的请求路径(path)。了解了session的大致原理,下面结合代码来了解大致的使用过程。

从cookie说起

上述使用案例得益于cookie这一伟大发明,一般来说cookie的处理分为如下几个步骤:
+ 1. 用户打开网页
+ 2. 服务端生成cookie数据返回给浏览器
+ 3. 浏览器将cookie保存
+ 4. 之后每次加载该页面,浏览器都把cookie数据发送给服务器

cookies的基本数据格式

标准的 HTTP协议有一个字段Set-Cookie用于标示cookie数据。服务器通过该字段告知客户端cookie数据。设置cookie数据的同时支持给该数据指定一些配置信息:
- Path: 表示该cookie数据影响到的路径,当前访问的url不满足该匹配时,不发送该cookie数据。
- Expires: 该cookies数据的过期时间(具体的时间点),如果不设置该选项,在浏览器关闭时该cookie丢失。该字段是一个UTC格式的字符串。
- Max-Age: 告知浏览器cookie多久之后过期,可以解决客户端与服务器时间不一致导致过期时间不准确的问题, 在express里时间单位是毫秒。
- HttpOnly: 告知浏览器该cookie不允许通过浏览器API的形式去修改,比如说通过document.cookie修改。在document.cookie不可见。
- Secure: 当设置该值为true时,cookie数据在HTTP协议下无效,只有在HTTPS协议下才有效。
- domain: domain属性指定了该cookie所属的域名(Domain), 比如说设置domain=example.com,那么浏览器在给www.example.com, www.abc.example.com发送HTTP请求时都会带上该cookie。如果服务器没有设置该属性那么浏览器默认该cookie从属于www.example.com域名。说到这里,一定会想,要是我把domain设置为其他网站,这样我是不是可以轻易更改其他网站的cookie数据了?浏览器有个策略,允许foo.example.com的服务器设置domainexample.com或者foo.example.com。但是不允许设置domainbar.example.com ETC。同时考虑到安全方面的因素,浏览器拒绝只给domain设置公共(public suffixes),比如说设置为com, co.uk是不被允许的。


Set-Cookie格式定义
>
Set-Cookie: name=value; Path:/; Domin=.domin.com;Secure=true

第一部分表示设置的cookie键/值,后面跟着几个配置参数。

Express/Connect有一个cookieParser中间件专门负责cookieParser的解析。其实现原理如下:



function parserCookie(cookie, options) {
   var obj = {};
   var pairs = cookies.split(/; /);
   var decode = opt.decode || decodeURIComponent;

   pairs.forEach(function(pair) {
      var eq_idx = pair.indexOf('=');
      var key = pair.substr(0, eq_idx).trim();
      var val = pair.substr(eq_idx, pair.length).trim();
       try {
           obj[key] = decode(val);
        }
        catch(err) {
           obj[key] = val;
        }
   });

   return obj;
}

function parseSignCookie(cookies, secret) {
  var keys = Object.keys(cookies);
  var dec = null;
  var ret = Object.create(null);
  var val = null

  for(var i = 0; i < keys.length; i++) {
     var k = keys[i];
     var v = cookies[k];
     dec = unsign(v, secret);
    ret[k] = dec;
  }
  return ret;
}

function parseJSONCookie(cookie) {
   var keys = Object.keys(cookie);
   var key;
   var val;

   for(var i = 0; i < cookie.length; i++) {
      val = cookie[i];
      key = keys[i];
      obj[key] = JSON.parse(val);
   }
}


var cookieParser = function(secret, opts) {
function(req, res, next) {
    if(req.cookies) return;

    var cookies = req.headers.cookies;
    req.cookies = {};
    req.signCookies = {};

   //把name=value格式的字符串解析为一个json对象
    req.cookies = parserCookie(cookies, options);

    if(secret) {
      //解析加密过的cookie字段
      req.signCookies = parseSignCookie(req.cookies, secret);
      //解析stringify过的JSON对象
      req.signCookies = parseJSONCookie(req.signCookies);
    }

    req.cookies = parseJSONCookies(req.cookies);
}
}

Express 给不一样的cookie字段值打上不一样的标签,如j:表示该值是一个JSON对象stringify后的值,s:表示该字段是经过加密的。默认把加密与未加密的字段分别放在req.signCookies, req.cookies


Express提供了一个res.cookie接口来设置Cookie数据。来看看Express的处理代码:


res.cookie = function (name, value, options) {
  var opts = merge({}, options);
  var secret = this.req.secret;
  var signed = opts.signed;

  if (signed && !secret) {
    throw new Error('cookieParser("secret") required for signed cookies');
  }

  var val = typeof value === 'object'
    ? 'j:' + JSON.stringify(value)
    : String(value);

  if (signed) {
    val = 's:' + sign(val, secret);
  }

  if ('maxAge' in opts) {
    opts.expires = new Date(Date.now() + opts.maxAge);
    opts.maxAge /= 1000;
  }

  if (opts.path == null) {
    opts.path = '/';
  }

  this.append('Set-Cookie', cookie.serialize(name, String(val), opts));

  return this;
};

//序列化cookie以及配置信息
function serialize(name, val, options) {  
  var enc = opt.encode || encode;  
  var pairs = [name + '=' + enc(val)];  
  if (null != opt.maxAge) {    
    var maxAge = opt.maxAge - 0;    
    if (isNaN(maxAge)) 
      throw new Error('maxAge should be a Number');    
    pairs.push('Max-Age=' + maxAge);  
  }  
  if (opt.domain) pairs.push('Domain=' + opt.domain);  
  if (opt.path) pairs.push('Path=' + opt.path);  
  if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString());  
  if (opt.httpOnly) pairs.push('HttpOnly');  
  if (opt.secure) pairs.push('Secure');  return pairs.join('; ');
}

//设置到HTTP应答协议头
res.append = function append(field, val) {
  var prev = this.get(field);
  var value = val;

  if (prev) {
    // concat the new and prev vals
    value = Array.isArray(prev) ? prev.concat(val)
      : Array.isArray(val) ? [prev].concat(val)
      : [prev, val];
  }

  return this.set(field, value);
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值