浅谈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)》许可协议授权