浅谈Session及Netty实现
1. 浅谈Session
1.1 什么是Session
session称之为会话,可以看做是客户端和服务器对话的介质。通过session赋予每个客户端的sessionId,服务端可以分辨各个不同的客户端。

所以说,session实际上时一种会话机制,具体来说在服务端是一种数据结构,而它的运行依赖于sessionId;而sessionId需要存储到客户端的cookie当中,并且在每次请求提交时都将cookie置于请求头当中。
这样,服务器就能从请求头中获取到sessionId,并通过该ID获取到session记录(由服务端来维护,一般存储到内存某数据结构当中),从而分辨出相应的客户端。
1.2 与cookie区别
通过上面的几段话可以很明白的看出:
session在服务器端,而cookie在客户端(例如浏览器);session的运行依赖sessionId,而sessionId是存在cookie中的;- 维持一个会话的核心就是客户端的唯一标识,即
sessionId。
2. Netty 实现
2.1 维护Session
Netty中默认不提供session的实现以及对应的POJO或者接口类,我们需要自己来定义HttpSession类,用于存储一些数据:
public class HttpSession {
private String id;
private Map<String, Object> attributes = new HashMap<>();
public HttpSession(String id) {
this.id = id;
}
public Object getAttribute(String name) {
return attributes.get(name);
}
public void addAttribute(String name, Object value) {
attributes.put(name, value);
}
public String getId() {
return id;
}
}
之后,我们需要建立session id与HttpSession的映射关系,我们通过一个session管理类SessionManager来维护这种映射关系:
public class SessionManager {
private static final HashMap<String, HttpSession> sessionMap = new HashMap<>();
/**
* 注册Session,并返回新注册的HttpSession对象
* @return
*/
public static HttpSession addSession() {
String sessionId = getSessionId();
synchronized (sessionMap) {
HttpSession session = new HttpSession(sessionId);
sessionMap.put(sessionId, session);
return session;
}
}
/**
* 判断当前服务端是否有该 session id 的记录
*/
public static boolean containsSession(String sessionId){
synchronized (sessionMap) {
return sessionMap.containsKey(sessionId);
}
}
public static HttpSession getSession(String sessionId) {
return sessionMap.get(sessionId);
}
private static String getSessionId() {
return UUID.randomUUID().toString().replace("-", "");
}
}
2.2 Http处理器
Netty为cookie提供了抽象的接口类Cookie,这使得我们可以很简单地操作Cookie:
// 注意的是该包路径,io.netty.handler.codec.http下的Cookie接口已经被弃用
package io.netty.handler.codec.http.cookie;
public interface Cookie extends Comparable<Cookie> {
String name();
String value();
String path();
long maxAge();
void setValue(String value);
void setPath(String path);
void setMaxAge(long maxAge);
...
}
同时还为Cookie和cookie字符串提供编解码器:
ServerCookieEncoder:编码器,将Cookie对象编码为cookie字符串;ServerCookieDecoder:解码器,将cookie字符串解码为Cookie对象。
举个例子:
// ServerCookieDecoder
JSESSIONID=123 -> Cookie{key=JSESSIONID, value="123"}
// ServerCookieEncoder
Cookie{key=JSESSIONID, value="456"} -> JSESSIONID=456
有了cookie的抽象接口和与字符串的编解码器,我们就能很容易的操作数据。具体的操作流程在HttpRequestHandler接口请求处理的channelRead0操作:
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final String CLIENT_COOKIE_NAME = "JSESSIONID";
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
// 构造 FullHttpResponse 对象
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1 ,
HttpResponseStatus.OK,
Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8));
// 如果该客户端该服务端中不存在 Session记录,将会注册新的Session
// 并通过 set-cookie 的响应头让客户端保存的sessionId
if (!hasSessionId(request)) {
HttpSession session = SessionManager.addSession();
// 创建 Cookie 对象,并通过 ServerCookieEncoder 编码为cookie字符串
Cookie cookie = new DefaultCookie(CLIENT_COOKIE_NAME, session.getId());
cookie.setPath("/");
String cookieStr = ServerCookieEncoder.STRICT.encode(cookie);
response.headers().set(HttpHeaderNames.SET_COOKIE, cookieStr);
}
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
/**
* 判断该客户端在服务端中是否有 Session 记录
*/
private boolean hasSessionId(FullHttpRequest request) {
// 从客户端请求头中得到cookie字符串
String cookieStr = request.headers().get("Cookie");
if (cookieStr != null) {
// 通过 ServerCookieDecoder 将cookie字符串解码为 Cookie 对象
Set<Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieStr);
for (Cookie cookie : cookies) {
if (cookie.name().equals(CLIENT_COOKIE_NAME) &&
SessionManager.containsSession(cookie.value())) {
HttpSession session = SessionManager.getSession(cookie.value());
System.out.println(session);
return true;
}
}
}
return false;
}
}
需要注意的是,在判断该客户端在服务端中是否有 Session记录时,需要保证两个条件:
cookie中具有键为JSESSIONID的值;- 服务器中(即
SessionManager中)需要有sessionId对应的session。
默认情况下,客户端的cookie为空:

当向服务器发起一次情况之后,客户端将保存服务器响应头中设置的session id:

这样,我们就很简单地实现了session功能。如果作为一个合格的框架,还需要创建自己的HTTPRequest对象,而不是直接将FullHttpRequest暴露给客户,并且需要将HttpSession注入其中:
public class HttpRequest {
// 其他的一些属性...
private HttpSession session;
public HttpSession getSession() {
return session;
}
}
- 本文标签: Java Spring Boot
- 本文链接: https://blog.xiaomiqiu.com/article/24
- 版权声明: 本文由刺球原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权
