初始代码:
redis.NewStore用于配置redis连接,最后一个参数作为加密密钥对sessionid进行加密处理。
首先是sessions.Sessions方法返回一个httphandler中间件,所有的请求共享相同的store,即共享相同的底层存储。
接下来通过session.Get(“count”)获取session服务端信息中count键对应的值,Get方法如下:
Session方法定义如下:
下面的这个方法定义在redistore.go文件中
这段代码被session.Sessions()方法调用,方法返回sessions.Session结构体,该结构体定义如下,结构体中的name就是session的名称,Values就是session-name对应的服务端存储的键值对形式的用户自定义信息。Options是cookie选项,如Domain, Path, Expires, HttpOnly等等cookie配置选项,store是session底层存储,当前分析的是对应redis。
// Session stores the values and optional configuration for a session.
type Session struct {
// The ID of the session, generated by stores. It should not be used for
// user data.
ID string
// Values contains the user-data for the session.
Values map[interface{}]interface{}
Options *Options
IsNew bool
store Store
name string
}
这里给出实际的浏览器中的cookie帮助理解:
回到上面的Get方法,首先调用sessions.GetRegistry®方法,GetRegistry®方法如下所示:
这段代码首先调用了github.com/gorilla/context中的Get方法,判断请求中是否携带键为registryKey的键值对,Registry结构体包含一个成员request和一个映射sessions,这个映射中包含session名称和上述Session结构体的一一对应关系。也就是说,一个request请求可以对应多个session,这些session有不同的名称或者说sessionid。这个结构体可以简单理解为是一个请求的session管理模块,维护着一个request请求的不同session。这里有个小小的疑问,为啥一个request请求会有不同的session呢?我悟了,因为一个请求本来就可以有多个session-id,一个session-id形式上就是一条cookie,用户可以有多个session-id分别用来获取不同的用户信息,比如session-userinfo用于登录校验,session-articlelike用于获取用户文章点赞信息。 下面是个例子:
回到代码分析中来,
如果请求中不存在上述键值对则创建该结构体并将键值对包含到请求当中,并返回该结构体。
接下来在Get方法中调用Registry结构体的Get方法,
可以看到,这个方法首先判断session-name是否是合法的,如果不合法直接返回,接下来判断Registry中是否已经包含这个键,如果包含就直接返回对应的值,就是上面的Session结构体,通过Session.Values就能获取到session自定义信息。如果不包含这个键,就进到store.New方法。
这段代码是创建Session结构体的代码,如果request请求中携带有该name对应的cookie值,则安全解密获得session实际的ID值,接下来通过s.load(session)从redis缓存中通过该ID值即session.ID查找到对应的自定义session信息并填充到session.values映射中。
这段代码就是具体的load方法,很简单,就是从redis连接池中获取redis连接然后通过Get命令查询键值对数据,然后反序列化存储到session结构体的values字段中。
所以上面的整个过程就是读取一个键对应值的过程。思考一下,一开始用户首先发起一个get请求,流程一直走到store.New方法,这个时候浏览器发送的请求中不包含session cookie,因此直接返回一个Session结构体,其中的values成员中没有键值对。此时上述session.Get(“count”)就为nil。
用一张图来形象一点地概括上述流程:
接下来就调用session.Set(“count”, count)方法。
这个方法就是直接用户自定义的sessioninfo键值对存到sessions.Session结构体的values映射中。此时还没有持久化到Redis缓存中。
接下来调用session.Save()方法将values中的键值对持久化到redis。
s.Session()过程与上述一致,不再赘述。看一下Save方法:
调用redistore.go中的Save方法:
如果Session对应的cookie过期了则重新创建对应的cookie,并返回;如果Session对应的cookie没有过期,就首先生成随机的sessionid,然后调用s.save(session)将session结构体中的values映射中的键值对持久化到redis缓存中。然后将sessionID使用密钥加密并编码后设置到请求响应的set-cookie字段中。下面是save方法的定义。就是将session-id和序列化后的用户自定义键值对字符串存到redis set中。