亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

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

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.49028c.com/jiaan-geng/p/4920036.html 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
理论片在线不卡免费观看| 81精品国产乱码久久久久久| 久久人人爽亚洲精品天堂| 77777少妇光屁股久久一区| 成人午夜在线视频一区| 6080yy精品一区二区三区| 亚洲一区二区三区视频| 日韩精品日韩在线观看| 久久躁日日躁aaaaxxxx| 亚洲人成在线播放| 日韩高清不卡av| 成人激情视频在线| 精品一区二区电影| 精品偷拍一区二区三区在线看| 午夜精品国产精品大乳美女| 亚洲女人被黑人巨大进入| 午夜精品久久久久久99热| 伊人久久久久久久久久久久久| 色悠悠国产精品| 日韩电影大全免费观看2023年上| 精品久久久久久中文字幕| 亚洲国产精品yw在线观看| 91高清免费在线观看| 色综合老司机第九色激情| 亚洲天堂成人在线视频| 91中文字幕在线观看| 国内精品久久久| 欧美在线性爱视频| 欧美一区二区三区免费观看| 欧美wwwxxxx| 欧美成人激情在线| 成人免费淫片aa视频免费| 欧美成人精品激情在线观看| 有码中文亚洲精品| 久久精品91久久香蕉加勒比| 久久精品视频一| 欧美国产日产韩国视频| 亚洲欧美另类在线观看| 热久久这里只有精品| 98精品国产自产在线观看| 国产精品久久久久av| 中文欧美日本在线资源| 97精品国产97久久久久久春色| 亚洲香蕉成视频在线观看| 91精品成人久久| 久久夜精品香蕉| 国产精品网红直播| 日韩在线观看免费高清完整版| 国产精品一二三在线| 亚洲成人国产精品| 亚洲女人被黑人巨大进入al| 欧美高清在线视频观看不卡| 97视频在线观看免费| 日本道色综合久久影院| 欧美人成在线视频| 国产中文欧美精品| 欧美精品在线观看91| 欧美电影免费观看| 欧美国产日本高清在线| 久久免费国产精品1| 欧美一区二区三区……| 久久国产精品视频| 国产一区二区免费| 91手机视频在线观看| 精品网站999www| 国产成人综合精品在线| 91青草视频久久| 欧美专区日韩视频| 久久精品国产精品| 日本精品久久久久影院| 人体精品一二三区| 亚洲欧洲激情在线| 亚洲欧美日韩天堂一区二区| 欧美大片在线看免费观看| 精品中文视频在线| 精品日韩中文字幕| 精品国产一区二区三区四区在线观看| 亚洲精品videossex少妇| 久久精品国产久精国产一老狼| 日韩电影免费观看在线观看| 久久久久国产精品免费网站| 成人一区二区电影| 九九热99久久久国产盗摄| 日韩小视频在线观看| 国产一区二区黑人欧美xxxx| 草民午夜欧美限制a级福利片| 国产成人精品免费久久久久| 性亚洲最疯狂xxxx高清| 日韩精品视频中文在线观看| 5252色成人免费视频| 亚洲天堂成人在线| 亚洲国产女人aaa毛片在线| 亚洲精选中文字幕| 91在线视频一区| 国产91色在线播放| 日韩高清电影好看的电视剧电影| 亚洲国产精品中文| 91亚洲va在线va天堂va国| 亚洲成人久久电影| 亚洲影视九九影院在线观看| 国产精品一久久香蕉国产线看观看| 精品magnet| 久久成人亚洲精品| 亚洲国语精品自产拍在线观看| 久久99精品视频一区97| 亚洲电影在线看| 精品人伦一区二区三区蜜桃网站| 国产精品丝袜久久久久久高清| 久久露脸国产精品| 欧美日韩高清在线观看| 中文字幕亚洲综合久久筱田步美| 欧美中文字幕精品| 欧美日韩国产一区二区| 色偷偷偷亚洲综合网另类| 国产精品扒开腿做爽爽爽视频| 亚洲午夜性刺激影院| 亚洲国产另类 国产精品国产免费| 欧美激情乱人伦一区| 国产成人97精品免费看片| 久久久久久久久久国产精品| 清纯唯美日韩制服另类| 久久777国产线看观看精品| 91tv亚洲精品香蕉国产一区7ujn| 日韩欧美在线视频日韩欧美在线视频| 91久久综合亚洲鲁鲁五月天| 久久免费精品日本久久中文字幕| 亚洲色图五月天| 国产成人+综合亚洲+天堂| 日韩的一区二区| 97香蕉超级碰碰久久免费的优势| 韩日精品中文字幕| 中文字幕日韩电影| 亚洲精品www久久久| 91在线观看免费高清| 亚洲色图25p| 91在线直播亚洲| 色综合久久悠悠| 久久精品国产精品亚洲| 中文字幕亚洲精品| 亚洲精品福利视频| 国语自产精品视频在免费| 国产午夜精品免费一区二区三区| 91高清免费在线观看| 91精品在线国产| 插插插亚洲综合网| 97精品国产97久久久久久| 国产精品盗摄久久久| 欧美成年人视频| 久久久人成影片一区二区三区观看| 日韩电影免费在线观看中文字幕| 日韩小视频在线| 欧美午夜激情小视频| 中日韩美女免费视频网站在线观看| 国产成人福利网站| 欧美成人免费在线观看| 亚洲国产精品成人精品| 亚洲精品aⅴ中文字幕乱码| 国产精品电影网站| 日韩亚洲第一页| 精品久久久久久久久中文字幕| 亚洲国产成人久久| 欧美高清在线观看| 成人免费观看49www在线观看| 日韩av在线一区二区|