使用过几种Web App开发语言和框架,都会接触到Session的概念。即使是一个简单站点访问计数的功能,也常常使用Session来实现的。其他常用的领域还有购物车,登录用户等。但是,对Session一直是一知半解,知其然而不知其所以然。

在认真的研究了HTTP协议,以及nodejs开发栈的express和express-session后,我终于比较有把握深入浅出的说清楚Session了,也算是满足了多年来开发过程中,常常浮现的对Session的好奇心吧。

本文使用nodejs v9.5.0作为技术验证工具。阅读本文前需要了解基础的HTTP知识和Cookie知识。详细需要参考rfc6265,或者阅读《HTTP小书》的最后一章。

会话的概念

用户在网站的一组相互关联的的请求和响应,就是一次会话。简而言之是这样的:

  1. 会话 = 一组访问
  2. 访问 = 一次请求和响应

比如一个最简单的nodejs HTTP程序:

var http = require('http')
http.createServer(function(req,res){
res.end('hello')
}).listen(3000)

每个请求都会进入到此处理函数:function(req,res){res.end('hello') },在此函数内获得请求,处理响应,完成后发给客户端,就是一次访问。通过浏览器的developer tools,可以看到此次会话的请求内容和响应内容。

以站点计数应用为案例来说明的话,就是这些来自于同样访问者的多次访问,都可以获得当前站点的访问计数。

引入会话

我们从一个案例开始引入会话的概念。当我们需要访问站点计数一类的功能时,我们希望用户访问此站点时:

  1. 第一次访问时,显示你的访问次数为1
  2. 以后每次访问时,访问计数加1

此种情况下,我们需要有一个地方存储当前计数,这样才能在同一个客户在此访问时,可以取出当前计数,加一后返回给客户。当然也因此需要识别此用户(浏览器),为每个用户单独计数。就是说,不同的用户访问时,需要去取对应用户的当前计数。

识别客户的问题,常用的方法就是使用Cookie。Cookie是HTTP协议的一部分。HTTP可以通过头字段Set-Cookie为来访客户做一个标记,这个标记常常就是一个ID,下一次访问此站点时,HTTP会通过Cookie头字段,发送此ID到站点,由此站点知道此客户的身份和这个身份关联的状态信息,比如当前访问计数,或者此身份当前的购物车的内容等等。

识别了客户后,就可以在Web服务器内,为此客户建立它的独特的状态信息。

实现一个会话

基于nodejs HTTP模块,我们实现一个极为简单的Session服务。只是为了展示概念,而不是为了实用的目的。此服务可以实现一个共享于同一站点的多次访问的req.session变量,此变量为一个对象,可以在此变量内写入新的成员,或者修改现存的成员变量的值,每次访问后会保存req.session,以便下次访问可以得到当前的值:

var http = require('http')
var sessionkey = "sessionkey3"
http.createServer(function(req,res){
if (req.url =="/"){
session(req,res)
req.session.count = (req.session.count+1) || 1
res.end('hi'+req.session.count)
}else
res.end('')
}).listen(3000)
console.log('listen on 3000')
function session(req,res){
if (req.session)
return
var answer ,id
if(isSessionOk(req)){
id = getCookie(req)
answer = getSessionById(id)
}else{
answer= {}
id = createSession(answer)
setCookie(res,id)
}
req.session = answer
res.on('finish', function() {
saveSession(id,req.session)
});
}
function hasCookie(req){
return (getCookie(req)!='')
}
function getCookie(req){
try{
var c = req.headers['cookie']
var arr = c.split(';')
for (var i = 0; i < arr.length; i++) {
var kv = arr[i]
var a = kv.split('=')
if (a[0].trim() == sessionkey)
return a[1]
}
}catch(error){
return ''
}
return ''
}
function setCookie(res,id){
res.setHeader("set-cookie",sessionkey +"="+id)
}
var sessions = {}
var sid = 0
function getSessionById(sid){
return sessions[sid]
}
function getSessionByReq(req){
var sid = getCookie(req)
return sessions[sid]
}
function createSession(session){
sessions[sid++,session]
return sid
}
function saveSession(sid,session){
sessions[sid] = session
}
function isSessionOk(req){
return hasCookie(req) && getSessionByReq(req) !== undefined
}

程序代码比较简单,读者可以保持它到index.js,然后执行此程序,验证概念:

node index.js

然后,启动chrome,访问站点localhost:3000,然后多次刷新,你可以看到每次刷新,返回的访问次数逐步累加。在打开另一个浏览器,比如safari,在此访问此站点,你会发现返回的访问计数从1开始,另外计数。因为是两个不同的浏览器内器,这就保证的它们是不同的访问客户,在站点内的代码,会区别两者,分别记录它们的状态信息。

代码使用了HTTP Cookie,基本算法很简单:

  1. 如果Session没有准备好,那么创建一个Session,得到Session的ID,把此ID通过Set-Cookie发送给浏览器。浏览器会在下一次访问此站点时,发送此ID。
  2. 如果Session已经准备好了,也就是说,浏览器通过Cookie发来了ID,并且通过此ID,可以在站点内获取到Session
  3. 把创建或者获取的Session赋值给req对象
  4. 在请求处理函数生命周期内,可以获取和修改Session对象
  5. 在请求处理完后,保存此Session变量

可能大家看到sessionkey这个变量,感觉有些莫名其妙。原因是每次cookie发送,同样的站点可能有多个框架需要使用此cookie头字段,比如php,aspx,jsp等都是需要使用了,为了好像不要冲突,大家各自使用cookie头字段内各自的key/value对即可。比如php的key默认是phpsessid,express-session默认的是connect.sid。

总结

此代码演示了最基础的Session的概念,但是远远不是一个可用的模块,想要真实世界中使用的Session模块,可以考虑express-session。

实现一个真正可以的会话,还需要考虑很多问题:

  1. 本文中使用是Session Id其实就是一个自增的整数。这会导致客户端欺骗,黑客可以猜到SessionID,使用伪造的SessionID获得服务器内对应的状态数据,或者伪造登录从而获得更高权限。真实的产品,一般是创建一个保证唯一的,不易猜测出来的字符串。
  2. 本文中的Session每次end后会必然保存,而不管此Session是否修改。实际的产品,是需要考虑此优化的。同时,也需要考虑到Session的失效期,到了失效期就会销毁,因为有些客户可能来一次两次也后再也不来访问了,没有必要为他们保存状态信息,如果再来了,不妨重新创建会话即可。
  3. 本文的Session保存在内存中,一旦重启,所有会话都会丢失。实际产品中,是需要支持持久化的保存的,比如保存到mysql数据库内,redis内,mongodb内等等。因此需要数据持久化的多提供者的方案。

Web Session 浅入浅出(山东数漫江湖)的更多相关文章

  1. 重新学习MySQL数据库2:『浅入浅出』MySQL 和 InnoDB

    重新学习Mysql数据库2:『浅入浅出』MySQL 和 InnoDB 作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.P ...

  2. 『浅入浅出』MySQL 和 InnoDB

    作为一名开发人员,在日常的工作中会难以避免地接触到数据库,无论是基于文件的 sqlite 还是工程上使用非常广泛的 MySQL.PostgreSQL,但是一直以来也没有对数据库有一个非常清晰并且成体系 ...

  3. Spring浅入浅出——不吹牛逼不装逼

    Spring浅入浅出——不吹牛逼不装逼 前言: 今天决定要开始总结框架了,虽然以前总结过两篇,但是思维是变化的,而且也没有什么规定说总结过的东西就不能再总结了,是吧.这次总结我命名为浅入浅出,主要在于 ...

  4. Spring注解浅入浅出——不吹牛逼不装逼

    Spring注解浅入浅出——不吹牛逼不装逼 前情提要 上文书咱们说了<Spring浅入浅出>,对Spring的核心思想看过上篇的朋友应该已经掌握了,此篇用上篇铺垫,引入注解,继续深入学习. ...

  5. Spring MVC浅入浅出——不吹牛逼不装逼

    Spring MVC浅入浅出——不吹牛逼不装逼 前言 上文书说了Spring相关的知识,对Spring来了个浅入浅出,大家应该了解到,Spring在三层架构中主做Service层,那还有Web层,也就 ...

  6. 浅入浅出EmguCv(三)EmguCv打开指定视频

    打开视频的思路跟打开图片的思路是一样的,只不过视频是由一帧帧图片组成,因此,打开视频的处理程序有一个连续的获取图片并逐帧显示的处理过程.GUI同<浅入浅出EmguCv(二)EmguCv打开指定图 ...

  7. 浅入浅出EmguCv(一)OpenCv与EmguCv

    最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...

  8. 浅入深出之Java集合框架(上)

    Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  9. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  10. 浅入深出之Java集合框架(下)

    Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...

随机推荐

  1. 项目uml

    [团队信息] 团队项目: 小葵日记--主打记录与分享模式的日记app 队名:日不落战队 队员信息及贡献分比例: 短学号 名 本次作业博客链接 此次作业任务 贡献分配 备注 501 安琪 http:// ...

  2. lintcode-149-买卖股票的最佳时机

    149-买卖股票的最佳时机 假设有一个数组,它的第i个元素是一支给定的股票在第i天的价格.如果你最多只允许完成一次交易(例如,一次买卖股票),设计一个算法来找出最大利润. 样例 给出一个数组样例 [3 ...

  3. C#中Console.ReadLine()和Console.Read()有何区别?

    Console.Read 表示从控制台读取字符串,不换行. Console.ReadLine 表示从控制台读取字符串后进行换行. Console.Read() Console.ReadLine()方法 ...

  4. asp.net mvc4 使用分部视图来刷新数据库

    前几天研究SSE,用浏览器做侦听后台数据库数据变化,如果有更新,就即时通过浏览器,使用SSE效果果然OK,侦听数据库有更新时马上会向浏览器通知有新数据,我还在浏览器里放了短音提示,但遇到一个问题,发出 ...

  5. 【bzoj4300】绝世好题 dp

    题目描述 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len). 输入 输入文件共2行. 第一行包括一个整数n. 第二行包括n个 ...

  6. Java语言有哪些特点?

    1)简单 如果同学们学过C++语言,就会感觉Java眼熟,因为Java中许多基本语句的语法和C++是一样的,像常用的循环语句.控制语句等和C++几乎相同.需要注意的是,Java和C++时两种完全不同的 ...

  7. [洛谷P4777]【模板】扩展中国剩余定理(EXCRT)

    题目大意:给你一些关于$x$的方程组:$$\begin{cases}x\equiv a_1\pmod{mod_1}\\x\equiv a_2\pmod{mod_2}\\\vdots\\x\equiv ...

  8. [Leetcode] search in rotated sorted array 搜索旋转有序数组

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e.,0 1 2 4 5 6 7might ...

  9. BZOJ [Ctsc2002] Award 颁奖典礼 解题报告

    [Ctsc2002] Award 颁奖典礼 Description IOI2002的颁奖典礼将在YONG-IN Hall隆重举行.人们在经历了充满梦幻的世界杯之后变得更加富于情趣.为了使颁奖典礼更具魅 ...

  10. 那些常用的JS命令

    window.location.reload()刷新当前页面. parent.location.reload()刷新父亲对象(用于框架) opener.location.reload()刷新父窗口对象 ...