麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

Tomcat源碼分析——Session管理分析(下)

2019-11-14 15:11:21
字體:
來源:轉載
供稿:網友

前言

  在《TOMCAT源碼分析——session管理分析(上)》一文中我介紹了Session、Session管理器,還以StandardManager為例介紹了Session管理器的初始化與啟動,本文將接著介紹Session管理的其它內容。

Session分配

  在《TOMCAT源碼分析——請求原理分析(下)》一文的最后我們介紹了Filter的職責鏈,Tomcat接收到的請求會經過Filter職責鏈,最后交給具體的Servlet處理。以訪問http://localhost:8080/host-manager這個路徑為例,可以清楚的看到整個調用棧(如圖1所示)中的Filter的職責鏈及之后的jspServlet,最后到達org.apache.catalina.connector.Request的getSession方法。

圖1  請求調用棧

  Request的getSession方法(見代碼清單1)用于獲取當前請求對應的會話信息,如果沒有則創建一個新的Session。

代碼清單1

    public HttpSession getSession(boolean create) {        Session session = doGetSession(create);        if (session == null) {            return null;        }                return session.getSession();    }

doGetSession方法的實現見代碼清單2。

代碼清單2

    PRotected Session doGetSession(boolean create) {        // There cannot be a session if no context has been assigned yet        if (context == null)            return (null);        // Return the current session if it exists and is valid        if ((session != null) && !session.isValid())            session = null;        if (session != null)            return (session);        // Return the requested session if it exists and is valid        Manager manager = null;        if (context != null)            manager = context.getManager();        if (manager == null)            return (null);      // Sessions are not supported        if (requestedSessionId != null) {            try {                session = manager.findSession(requestedSessionId);            } catch (IOException e) {                session = null;            }            if ((session != null) && !session.isValid())                session = null;            if (session != null) {                session.access();                return (session);            }        }        // Create a new session if requested and the response is not committed        if (!create)            return (null);        if ((context != null) && (response != null) &&            context.getServletContext().getEffectiveSessionTrackingModes().                    contains(SessionTrackingMode.COOKIE) &&            response.getResponse().isCommitted()) {            throw new IllegalStateException              (sm.getString("coyoteRequest.sessionCreateCommitted"));        }        // Attempt to reuse session id if one was submitted in a cookie        // Do not reuse the session id if it is from a URL, to prevent possible        // phishing attacks        // Use the SSL session ID if one is present.         if (("/".equals(context.getSessionCookiePath())                 && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {            session = manager.createSession(getRequestedSessionId());        } else {            session = manager.createSession(null);        }        // Creating a new session cookie based on that session        if ((session != null) && (getContext() != null)               && getContext().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;    }

依據代碼清單2,整個獲取Session的步驟如下:

  1. 判斷當前Request對象是否已經存在有效的Session信息,如果存在則返回此Session,否則進入下一步;
  2. 獲取Session管理器,比如StandardManager;
  3. 從StandardManager的Session緩存中獲取Session,如果有則返回此Session,否則進入下一步;
  4. 創建Session;
  5. 創建保存Session ID的Cookie;
  6. 通過調用Session的access方法更新Session的訪問時間以及訪問次數。

  我們來著重閱讀ManagerBase實現的createSession方法,見代碼清單3。

代碼清單3

    public Session createSession(String sessionId) {                // Recycle or create a Session instance        Session session = createEmptySession();        // Initialize the properties of the new session and return it        session.setNew(true);        session.setValid(true);        session.setCreationTime(System.currentTimeMillis());        session.setMaxInactiveInterval(this.maxInactiveInterval);        if (sessionId == null) {            sessionId = generateSessionId();        }        session.setId(sessionId);        sessionCounter++;        return (session);    }

至此,Session的創建與分配就介紹這些。

Session追蹤

  HTTP是一種無連接的協議,如果一個客戶端只是單純地請求一個文件,服務器端并不需要知道一連串的請求是否來自于相同的客戶端,而且也不需要擔心客戶端是否處在連接狀態。但是這樣的通信協議使得服務器端難以判斷所連接的客戶端是否是同一個人。當進行Web程序開發時,我們必須想辦法將相關的請求結合一起,并且努力維持用戶的狀態在服務器上,這就引出了會話追蹤(session tracking)。

  Tomcat追蹤Session主要借助其ID,因此在接收到請求后應該需要拿到此請求對應的會話ID,這樣才能夠和StandardManager的緩存中維護的Session相匹配,達到Session追蹤的效果。還記得《TOMCAT源碼分析——請求原理分析(中)》一文中介紹CoyoteAdapter的service方法時調用的postParseRequest方法嗎?其中有這么一段代碼,見代碼清單4。

代碼清單4

        if (request.getServletContext().getEffectiveSessionTrackingModes()                .contains(SessionTrackingMode.URL)) {                        // Get the session ID if there was one            String sessionID = request.getPathParameter(                    ApplicationSessionCookieConfig.getSessionUriparamName(                            request.getContext()));            if (sessionID != null) {                request.setRequestedSessionId(sessionID);                request.setRequestedSessionURL(true);            }        }        // 省去中間無關代碼// Finally look for session ID in cookies and SSL session        parseSessionCookiesId(req, request);        parseSessionSslId(request);        return true;    }

 

根據代碼清單4可以看出postParseRequest方法的執行步驟如下:

  1. 如果開啟了會話跟蹤(session tracking),則需要從緩存中獲取維護的Session ID;
  2. 從請求所帶的Cookie中獲取Session ID;
  3. 如果Cookie沒有攜帶Session ID,但是開啟了會話跟蹤(session tracking),則可以從SSL中獲取Session ID;

從緩存中獲取維護的Session ID

代碼清單4中首先調用getSessionUriParamName方法(見代碼清單5)獲取Session的參數名稱。

代碼清單5

    public static String getSessionUriParamName(Context context) {                String result = getConfiguredSessionCookieName(context);                if (result == null) {            result = DEFAULT_SESSION_PARAMETER_NAME;         }                return result;     }

從代碼清單2看出,getSessionUriParamName方法首先調用getConfiguredSessionCookieName方法獲取Session的Cookie名稱,如果沒有則默認為jsessionid(常量DEFAULT_SESSION_PARAMETER_NAME的值)。回頭看代碼清單1中會以getSessionUriParamName方法返回的值作為request.getPathParameter(見代碼清單6)的參數查詢Session ID。

代碼清單6

    protected String getPathParameter(String name) {        return pathParameters.get(name);    }

 從請求所帶的Cookie中獲取Session ID

  代碼清單4中調用的parseSessionCookiesId方法(見代碼清單7)用來從Cookie中獲取Session ID。

代碼清單7

    protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {        // If session tracking via cookies has been disabled for the current        // context, don't go looking for a session ID in a cookie as a cookie        // from a parent context with a session ID may be present which would        // overwrite the valid session ID encoded in the URL        Context context = (Context) request.getMappingData().context;        if (context != null && !context.getServletContext()                .getEffectiveSessionTrackingModes().contains(                        SessionTrackingMode.COOKIE))            return;                // Parse session id from cookies        Cookies serverCookies = req.getCookies();        int count = serverCookies.getCookieCount();        if (count <= 0)            return;        String sessionCookieName =            ApplicationSessionCookieConfig.getSessionCookieName(context);        for (int i = 0; i < count; i++) {            ServerCookie scookie = serverCookies.getCookie(i);            if (scookie.getName().equals(sessionCookieName)) {                // Override anything requested in the URL                if (!request.isRequestedSessionIdFromCookie()) {                    // Accept only the first session id cookie                    convertMB(scookie.getValue());                    request.setRequestedSessionId                        (scookie.getValue().toString());                    request.setRequestedSessionCookie(true);                    request.setRequestedSessionURL(false);                    if (log.isDebugEnabled())                        log.debug(" Requested cookie session id is " +                            request.getRequestedSessionId());                } else {                    if (!request.isRequestedSessionIdValid()) {                        // Replace the session id until one is valid                        convertMB(scookie.getValue());                        request.setRequestedSessionId                            (scookie.getValue().toString());                    }                }            }        }    }

從SSL中獲取Session ID

  代碼清單4中調用的parseSessionSslId方法(見代碼清單8)用來從SSL中獲取Session ID。

代碼清單8

    protected void parseSessionSslId(Request request) {        if (request.getRequestedSessionId() == null &&                SSL_ONLY.equals(request.getServletContext()                        .getEffectiveSessionTrackingModes()) &&                        request.connector.secure) {            // TODO Is there a better way to map SSL sessions to our sesison ID?            // TODO The request.getAttribute() will cause a number of other SSL            //      attribute to be populated. Is this a performance concern?            request.setRequestedSessionId(                    request.getAttribute(SSLSupport.SESSION_ID_KEY).toString());            request.setRequestedSessionSSL(true);        }    }

Session銷毀

  在《TOMCAT源碼分析——生命周期管理》一文中我們介紹了容器的生命周期管理相關的內容,StandardEngine作為容器,其啟動過程中也會調用startInternal方法(見代碼清單9)。

代碼清單9

    @Override    protected synchronized void startInternal() throws LifecycleException {                // Log our server identification information        if(log.isInfoEnabled())            log.info( "Starting Servlet Engine: " + ServerInfo.getServerInfo());        // Standard container startup        super.startInternal();    }

StandardEngine的startInternal方法實際代理了父類ContainerBase的startInternal方法(見代碼清單10)。

代碼清單10

    @Override    protected synchronized void startInternal() throws LifecycleException {        // Start our subordinate components, if any        if ((loader != null) && (loader instanceof Lifecycle))            ((Lifecycle) loader).start();        logger = null;        getLogger();        if ((logger != null) && (logger instanceof Lifecycle))            ((Lifecycle) logger).start();        if ((manager != null) && (manager instanceof Lifecycle))            ((Lifecycle) manager).start();        if ((cluster != null) && (cluster instanceof Lifecycle))            ((Lifecycle) cluster).start();        if ((realm != null) && (realm instanceof Lifecycle))            ((Lifecycle) realm).start();        if ((resources != null) && (resources instanceof Lifecycle))            ((Lifecycle) resources).start();        // Start our child containers, if any        Container children[] = findChildren();        for (int i = 0; i < children.length; i++) {            children[i].start();        }        // Start the Valves in our pipeline (including the basic), if any        if (pipeline instanceof Lifecycle)            ((Lifecycle) pipeline).start();        setState(LifecycleState.STARTING);        // Start our thread        threadStart();    }

代碼清單10一開始對各種子容器進行了啟動(由于與本文內容關系不大,所以不多作介紹),最后會調用threadStart方法。threadStart的實現見代碼清單11。

代碼清單11

    protected void threadStart() {        if (thread != null)            return;        if (backgroundProcessorDelay <= 0)            return;        threadDone = false;        String threadName = "ContainerBackgroundProcessor[" + toString() + "]";        thread = new Thread(new ContainerBackgroundProcessor(), threadName);        thread.setDaemon(true);        thread.start();    }

threadStart方法啟動了一個后臺線程,任務為ContainerBackgroundProcessor。ContainerBackgroundProcessor的run方法中主要調用了processChildren方法,見代碼清單12。

代碼清單12

    protected class ContainerBackgroundProcessor implements Runnable {        public void run() {            while (!threadDone) {                try {                    Thread.sleep(backgroundProcessorDelay * 1000L);                } catch (InterruptedException e) {                    // Ignore                }                if (!threadDone) {                    Container parent = (Container) getMappingObject();                    ClassLoader cl =                         Thread.currentThread().getContextClassLoader();                    if (parent.getLoader() != null) {                        cl = parent.getLoader().getClassLoader();                    }                    processChildren(parent, cl);                }            }        }        protected void processChildren(Container container, ClassLoader cl) {            try {                if (container.getLoader() != null) {                    Thread.currentThread().setContextClassLoader                        (container.getLoader().getClassLoader());                }                container.backgroundProcess();            } catch (Throwable t) {                log.error("Exception invoking periodic Operation: ", t);            } finally {                Thread.currentThread().setContextClassLoader(cl);            }            Container[] children = container.findChildren();            for (int i = 0; i < children.length; i++) {                if (children[i].getBackgroundProcessorDelay() <= 0) {                    processChildren(children[i], cl);                }            }        }    }

 processChildren方法會不斷迭代StandardEngine的子容器并調用這些子容器的backgroundProcess方法。這里我們直接來看StandardEngine的孫子容器StandardManager的backgroundProcess實現,即ManagerBase的backgroundProcess方法,見代碼清單13。

代碼清單13

    public void backgroundProcess() {        count = (count + 1) % processExpiresFrequency;        if (count == 0)            processExpires();    }

backgroundProcess里實現了一個簡單的算法:

count:計數器,起始為0;

processExpiresFrequency:執行processExpires方法的頻率,默認為6。

每執行一次backgroundProcess方法,count會增加1,每當count+1與processExpiresFrequency求模等于0,則調用processExpires。簡而言之,每執行processExpiresFrequency指定次數的backgroundProcess方法,執行一次processExpires方法。processExpires的實現見代碼清單14所示。

代碼清單14

    public void processExpires() {        long timeNow = System.currentTimeMillis();        Session sessions[] = findSessions();        int expireHere = 0 ;                if(log.isDebugEnabled())            log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length);        for (int i = 0; i < sessions.length; i++) {            if (sessions[i]!=null && !sessions[i].isValid()) {                expireHere++;            }        }        long timeEnd = System.currentTimeMillis();        if(log.isDebugEnabled())             log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere);        processingTime += ( timeEnd - timeNow );    }

代碼清單14中processExpires方法的執行步驟如下:

  1. 從緩存取出所有的Session;
  2. 逐個校驗每個Session是否過期,對于已經過期的Session。

Session的標準實現是StandardSession,其isValid方法(見代碼清單15)的主要功能是判斷Session是否過期,對于過期的,則將其expiring狀態改為true。判斷過期的公式為:

( (當前時間 - Session的最后訪問時間)/1000) >= 最大訪問間隔

代碼清單15

    public boolean isValid() {        if (this.expiring) {            return true;        }        if (!this.isValid) {            return false;        }        if (ACTIVITY_CHECK && accessCount.get() > 0) {            return true;        }        if (maxInactiveInterval >= 0) {             long timeNow = System.currentTimeMillis();            int timeIdle;            if (LAST_ACCESS_AT_START) {                timeIdle = (int) ((timeNow - lastAccessedTime) / 1000L);            } else {                timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);            }            if (timeIdle >= maxInactiveInterval) {                expire(true);            }        }        return (this.isValid);    }

總結

  Tomcat對于Session的管理過程包括創建、分配、維護和跟蹤、銷毀等。

如需轉載,請標明本文作者及出處——作者:jiaan.gja,本文原創首發:博客園,原文鏈接:http://www.companysz.com/jiaan-geng/p/4920036.html 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 一区二区久久电影 | 久草在线高清视频 | 久久99网| 一级黄色毛片子 | 久草视频免费 | 久久精品一二三区白丝高潮 | 电影91| 免费放黄网站在线播放 | 日日草日日干 | 成人综合在线观看 | 久久久久免费精品 | 一区二区三区欧美在线 | 国产成人av一区二区 | 欧美视频一区二区 | 久久久一区二区三区视频 | 国产精品视频二区不卡 | 久久精品久久精品国产大片 | 国产羞羞视频 | 久章草影院| 日韩欧美中文字幕视频 | 91欧美视频 | 色综合视频网 | 日本a∨精品中文字幕在线 狠狠干精品视频 | 久久亚洲第一 | 亚洲成人黄色片 | 国产激情精品一区二区三区 | 羞羞视频免费观看入口 | 中文字幕精品在线视频 | 亚洲最新黄色网址 | 成人情欲视频在线看免费 | lutube成人福利在线观看 | 国产伦久视频免费观看视频 | av日韩一区二区三区 | 久久久久久久91 | 国产精品视频导航 | 欧美精品成人一区二区三区四区 | 亚洲成年人免费网站 | 色视频欧美 | 成人区一区二区 | 国产瑟瑟视频 | 日韩毛片网站 |