tomcat原始碼解讀六 tomcat中的session生命歷程
session的作用是在一次會話中(從開啟瀏覽器到關閉瀏覽器同當前伺服器的交流)當客戶端第一次請求session物件時候,伺服器會為客戶端建立一個session,並將通過特殊演算法算出一個session的ID,用來標識該session物件,當瀏覽器下次(session繼續有效時)請求別的資源的時候,瀏覽器會sessionID放置到請求頭中,伺服器接收到請求後就得到該請求的sessionID,伺服器根據當前sessionId找到對應的session例項。
1.1 UML關係圖
1.2 Session的獲取api session的建立與tomcat請求沒有什麼很大的直接關係,主要是在進行servlet處理(jsp最終也是被編譯成servlet)來獲取,獲取方式如下:
/獲取此次會話的session
//如果引數為true表明當沒有獲取到對應的session例項會自己建立一個,且預設為真
HttpSession session = request.getSession(true);
HttpSession session1 = request.getSession();
//如果引數為false表明當沒有獲取到對應的session例項則會返回空
HttpSession session2 = request.getSession(false);
1.3 sessionId的獲取 這裡是在request請求已經解析了頭部的情況下,根據配置檔案獲取相應的引數最終得到sessionId的值,這個值得優先順序是URL>cookie 最終這個值將會註冊到request屬性中去
/** * 這段程式碼的意義:向request中注入requestedSessionId並設定其是來與URL Cookie 還是SSL * 具體判斷是通過requestedSessionURL和requestedSessionSSL這些布林型別 * 另一個作用是在下文的重定向過程決定是否需要將sessionCookieName給加入進去以;XXX=XXXXXX形式 * 在域名泛解析過程中針對訪問不同的二級域名,sessionId是預設不共享的 * */ String sessionID; if (request.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) { //根據當前sessionCookieName從request的引數中獲取相應sessionId, sessionID = request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext())); //如果sessionId不為空,將其注入request的requestedSessionId屬性 if (sessionID != null) { request.setRequestedSessionId(sessionID); //獲取解析到說明請求是從URL中解析出來 request.setRequestedSessionURL(true); } } //在cookies和SSL中尋找sessionId,如果requestedSessionId不存在,則直接注入 parseSessionCookiesId(request); parseSessionSslId(request); sessionID = request.getRequestedSessionId();
這裡會有個問題,在URL中都是以k,v的形式存在,那麼這個k是來自於哪個地方,一下程式碼展示:
* 獲取配置的sessionCookieName
* 第一種是配置Web應用的時候 Context標籤下
* 1 <Context path='' docBase='ROOT' sessionCookiePath='/' sessionCookieName='' />
* 2 <session-config>
* <cookie-config>
* <name id="sessionId">sessionName</name>
* </cookie-config>
* </session-config>
* */
private static String getConfiguredSessionCookieName(Context context) {
// Priority is:
// 1. Cookie name defined in context
// 2. Cookie name configured for app
// 3. Default defined by spec
if (context != null) {
//獲取sessionCookieName,這個來自於解析自己的Context標籤
String cookieName = context.getSessionCookieName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
//獲取定義在應用的中的web.xml session-config/cookie-config
SessionCookieConfig scc = context.getServletContext().getSessionCookieConfig();
cookieName = scc.getName();
if (cookieName != null && cookieName.length() > 0) {
return cookieName;
}
}
return null;
}
根據程式碼可以看出k可以是在配置Context應用的時候新增,也可以是在web.xml配置,這樣就可以獲取對應的sessionId。那麼這個sessionId使使用者自己產生還是怎麼來的?一般直接在URL上新增,或者可以通過過濾器等方式將請求進行處理,由於缺少具體開發環境所以不能夠很全面的解述.針對在URL上處理會有一個問題,就是重定向,這樣不必擔心,因為在CoyoteAdapter.java中對重定向處理會獲取URL中是否存在,如果存在則直接新增, 程式碼如下:
MessageBytes redirectPathMB = request.getMappingData().redirectPath;
if (!redirectPathMB.isNull()) {
String redirectPath = URLEncoder.DEFAULT.encode(redirectPathMB.toString(), "UTF-8");
String query = request.getQueryString();
//如果SessionId是從URL中解析出來的,則直接新增到URL上面
if (request.isRequestedSessionIdFromURL()) {
redirectPath = redirectPath + ";" + SessionConfig.getSessionUriParamName(
request.getContext()) +
"=" + request.getRequestedSessionId();
}
//新增引數
if (query != null) {
redirectPath = redirectPath + "?" + query;
}
response.sendRedirect(redirectPath);
request.getContext().logAccess(request, response, 0, true);
return false;
}
1.4 session的例項化過程
session的例項化是在具體的Servlet方法中,呼叫getSession的API之後,首先是利用門面模式獲取到真正的Connector/Request,而後其方法如下:
/**返回與當前請求相關的session*/
@Override
public HttpSession getSession(boolean create) {
//建立session的核心方法
Session session = doGetSession(create);
if (session == null) {
return null;
}
return session.getSession();
}
在這個方法中首先呼叫doGetSession在這個過程中我們建立了HttpSession(利用了門面模式)然後將其作為StandardSession的控制代碼,最終返回的是StandardSession例項,利用其getSession獲取對應的HttpSession即我們所需要的session, doGetSession的方法如下
protected Session doGetSession(boolean create) {
//獲取與當前請求對應的Context
Context context = getContext();
if (context == null) {
return (null);
}
/**
* 如果存在session並且可利用則直接返回,如果不可利用則將session置為空
* 不可利用是在request的recycle中設定為不可利用
*/
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return (session);
}
//獲取會話管理器
Manager manager = context.getManager();
if (manager == null) {
return (null); // Sessions are not supported
}
if (requestedSessionId != null) {
try {
//根據sessionId從會話管理器中找到對應session
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
session.access();
return (session);
}
}
//session為false表示如果沒有獲取到對應session則直接返回空
if (!create) {
return (null);
}
if (response != null && context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE)
&& response.getResponse().isCommitted()) {
throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
//獲取客戶端提供的sessionId
String sessionId = getRequestedSessionId();
if (requestedSessionSSL) {
//在server.xml檔案中配置sessionCookiePath="/",並且該sessionId來自於cookie
} else if (("/".equals(context.getSessionCookiePath()) && isRequestedSessionIdFromCookie())) {
if (context.getValidateClientProvidedNewSessionId()) {
boolean found = false;
/**
* 找到當前主機下所有的web應用獲取其會話管理器
* 從對應會話管理器中找若找到相應sessionId不為空,則跳出迴圈
*
* 這樣做的目的是可能在不同web應用中sessionId需要保持相同
* 多個web應用構成一個整體的專案
*/
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) {
}
}
}
//如果沒有發現則sessionId置為空,表明當前sessionId沒有被任何會話管理器使用
if (!found) {
sessionId = null;
}
}
} else {
sessionId = null;
}
//建立一個sessionId
session = manager.createSession(sessionId);
//將session新增到cookie中去 利用Set-Cookie將其新增到HTTP首部
if (session != null && context.getServletContext().getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.COOKIE)) {
Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
session.access();
return session;
}