客户端第一次请求服务端,服务端会创建一个session对象并且存储下来,之后会将session的唯一标识sessionId设置到响应头中传给客户端
客户端之后请求就会在cookie中携带第一次请求后服务端传过来的sessionId,服务端能通过客户端传过来的sessionId获取之前创建的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(); }
org.apache.catalina.connector.CoyoteAdapter#postParseRequest
, 获取到后会将sessionId设置到request的requestedSessionId属性中,之后用字段表示这个sessionId是通过url、cookie、ssl哪一种方式获取的
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了