1. 程式人生 > 其它 >tomcat原始碼解讀六 tomcat中的session生命歷程

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;
}