1.session会话追踪原理
客户端第一次请求服务端,服务端会创建一个session对象并且存储下来,之后会将session的唯一标识sessionId设置到响应头中传给客户端
客户端之后请求就会在cookie中携带第一次请求后服务端传过来的sessionId,服务端能通过客户端传过来的sessionId获取之前创建的session从而实现会话追踪
2.session的获取
session在服务端能通过request.getSession()获取
可以传参一个布尔值,request.getSession(true/false), true代表当根据sessionId未获取到session的时候会创建一个新的session,false当未获取到session时不会创建直接返回null。不传参这种情况默认true
/**
* @return the session associated with this Request, creating one
* if necessary.
*/
@Override
public HttpSession getSession() {
Session session = doGetSession(true);
if (session == null) {
return null;
}
return session.getSession();
}
/**
* @return the session associated with this Request, creating one
* if necessary and requested.
*
* @param create Create a new session if one does not exist
*/
@Override
public HttpSession getSession(boolean create) {
Session session = doGetSession(create);
if (session == null) {
return null;
}
return session.getSession();
}
3. 源码讲解
3.1 sessionId的获取
org.apache.catalina.connector.CoyoteAdapter#postParseRequest
, 获取到后会将sessionId设置到request的requestedSessionId属性中,之后用字段表示这个sessionId是通过url、cookie、ssl哪一种方式获取的
3.2 session的创建
session的创建首先通过doGetSession方法再通过getSession方法获取,下面来详细分析
org.apache.catalina.connector.Request#doGetSession
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
Context context = getContext();
if (context == null) {
return null;
}
// Return the current session if it exists and is valid
// 若session未验证,则重置为null
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return session;
}
// Return the requested session if it exists and is valid
// 获取manager,此为创建session的管理器
Manager manager = context.getManager();
if (manager == null) {
return null; // Sessions are not supported
}
if (requestedSessionId != null) {
try {
// manager中维护了一个 protected Map<String, Session> sessions = new ConcurrentHashMap<>(); 缓存
// 尝试根据sessionId从缓存中获取session
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
// 是否验证
if ((session != null) && !session.isValid()) {
session = null;
}
// 不为空的话访问次数加1,返回
if (session != null) {
session.access();
return session;
}
}
// Create a new session if requested and the response is not committed
// 若create=false,则不创建新的session直接返回null
if (!create) {
return null;
}
boolean trackModesIncludesCookie =
context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
// 响应已提交不支持获取session
if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Re-use session IDs provided by the client in very limited
// circumstances.
String sessionId = getRequestedSessionId();
if (requestedSessionSSL) {
// If the session ID has been obtained from the SSL handshake then
// use it.
} else if (("/".equals(context.getSessionCookiePath()) // 一般不设置getSessionCookiePath为null
&& isRequestedSessionIdFromCookie())) {
/* This is the common(ish) use case: using the same session ID with
* multiple web applications on the same host. Typically this is
* used by Portlet implementations. It only works if sessions are
* tracked via cookies. The cookie must have a path of "/" else it
* won't be provided for requests to all web applications.
*
* Any session ID provided by the client should be for a session
* that already exists somewhere on the host. Check if the context
* is configured for this to be confirmed.
*/
if (context.getValidateClientProvidedNewSessionId()) {
boolean found = false;
for (Container container : getHost().findChildren()) {
Manager m = ((Context) container).getManager();
if (m != null) {
try {
if (m.findSession(sessionId) != null) {
found = true;
break;
}
} catch (IOException e) {
// Ignore. Problems with this manager will be
// handled elsewhere.
}
}
}
if (!found) {
sessionId = null;
}
}
} else {
// 逻辑进这里
sessionId = null;
}
// 调用manager创建一个新的session,参数为null
session = manager.createSession(sessionId);
// Creating a new session cookie based on that session
if (session != null && trackModesIncludesCookie) {
// 创建一个cookie 类似 JSESSIONID=AD726E115176BC2F4B1EF5F469F04603; Path=/zxq; HttpOnly
Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
// 添加响应,header为Set-Cookie
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
// 设置访问时间、访问次数加1
session.access();
return session;
}
org.apache.catalina.session.ManagerBase#createSession
@Override
public Session createSession(String sessionId) {
// 判断活跃session数据是否大于最大值、否则拒绝创建session
if ((maxActiveSessions >= 0) &&
(getActiveSessions() >= maxActiveSessions)) {
rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("managerBase.createSession.ise"),
maxActiveSessions);
}
// Recycle or create a Session instance
// 创建一个StandardSession实例
Session session = createEmptySession();
// Initialize the properties of the new session and return it
session.setNew(true);
session.setValid(true);
session.setCreationTime(System.currentTimeMillis());
// 设置session的过期时间
session.setMaxInactiveInterval(getContext().getSessionTimeout() * 60);
String id = sessionId;
if (id == null) {
// id为空的话会调用manager的sessionIdGenerator生成器生成一个新的sessionId
id = generateSessionId();
}
// 设置sessionId,同时会将sessionId和session添加到上面提到的缓存中供后续获取
session.setId(id);
// manager管理的session数加1
sessionCounter++;
SessionTiming timing = new SessionTiming(session.getCreationTime(), 0);
synchronized (sessionCreationTiming) {
sessionCreationTiming.add(timing);
sessionCreationTiming.poll();
}
return session;
}
发起一次请求响应如下,可以看到红框圈起来的就是服务端发送给客户端的sessionId,它是设置通过set-cookie设置在响应头中的,下一次请求客户端就会在cookie中带着该sessionId了
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280127.html