Web应用服务器 相关知识梳理(四)Tomcat的其他组件——Session

         由于HTTP是一种无状态协议,当用户的一次访问请求结束后,后端服务器就无法知道下一次来访问的还是不是上次访问的用户;提到Session与Cookie,二者的作用都是为了保持访问用户与后端服务器的交互状态,解决了HTTP是一种无状态协议的弊端。

  • Cookie

         在Tomcat 8中是如何调用addCookie方法将Cookie加到HTTP的Header中的???

                 

        根据上图可知真正的Cookie构建是在org.apache.catalina.connector.Response中完成的:

/**
 * Add the specified Cookie to those that will be included with
 * this Response.
 *
 * @param cookie Cookie to be added
 */
@Override
public void addCookie(final Cookie cookie) {

    // Ignore any call from an included servlet
    if (included || isCommitted()) {
        return;
    }

    cookies.add(cookie);
    
    // 此时 若未设置版本号Version 0 或 Version 1,Tomcat在最后构建HTTP响应头时也会自动将其设置为Version 1
    String header = generateCookieString(cookie);
    
    //if we reached here, no exception, cookie is valid
    // the header name is Set-Cookie for both "old" and v.1 ( RFC2109 )
    // RFC2965 is not supported by browsers and the Servlet spec
    // asks for 2109.
    addHeader("Set-Cookie", header, getContext().getCookieProcessor().getCharset());
}

        注意:所创建Cookie的NAME值不能和Set-Cookie或者Set-Cookie2属性项值相同!!!

        Tomcat中根据response.addCookie创建多个Cookie时,这些Cookie都是创建一个以NAME为Set-Cookie的

MimeHeaders,最终在请求返回时HTTP响应头将相同Header标识的Set-Cookie值进行合并,浏览器在接收HTTP返回的

数据时将分别解析每一个Header项;合并过程在org.apache.coyote.http11.Http11Processor的prepareResponse方法中进行:

int size = headers.size();
for (int i = 0; i < size; i++) {
    outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));
}
  • Session 

       当Cookie很多时,在传输的过程中,无形增加了客户端与服务端的数据传输量 以及 直接传输Cookie所带来的数据不

安全性,而Session解决了该问题;同一个客户端每次和服务端交互时,不需要每次都传回所有的Cookie值,而只要传回

一个ID,该ID由第一次访问服务端时生成,每个客户端唯一;这个ID通常是NAME为JSESSIONID的一个Cookie:就如同

宝箱 与 钥匙。按通常的了解Session的工作方式是基于Cookie的,实际上Tomcat中Session工作方式分为三种:

(一)基于URL Path Parameter,默认支持:         

          当浏览器不支持Cookie功能时,浏览器会将用户的SessionCookieName重写到用户请求的URL参数中,格式为:

/cxt/login.do;SessionCookieName=3EF0AEC40F2C6C30A0580844C0E6B2E8?count=8。

          关于SessionCookieName,可在web.xml配置:

<session-config>
    <session-timeout>30</session-timeout>
    <cookie-config>
    		<name>SessionCookieName</name>
    </cookie-config>
</session-config>

          否则默认为" JSESSIONID "。然后Request会根据这个SessionCookieName到Paramters中获取到SessionID并设置

到org.apache.catalina.connector.Request中的requestedSessionId属性中。

(二)基于Cookie,如果没有修改Context容器中的Cookies标识,默认也是支持的。

           如果客户端也支持Cookie,则Tomcat中仍会解析Cookie中的SessionID并会覆盖URL中的SessionID.

(三)基于SSL,默认不支持,只有在connector.getAttribute("SSLEnabled")为true时才支持

                          

Session在Tomcat中如何工作:

        HttpSession对象,实际上是StandardSession对象的门面类对象;通过 HttpServletRequest request.getSession

( boolean create ) 创建,并将创建后的HttpSession对象添加到org.apache.catalina.Manager的sessions容器中保存,

只要HttpSession对象存在,便可根据SessionID来获取该对象,一个requestedSessionId对应一个访问的客户端,一

个客户端对应一个 StandardSession,时序图如下:

StandardManager类如何管理StandardSession???

         Manager实现类是org.apache.catalina.session.StandardManager;StandardManager类将管理所有的Session

对象的生命周期,过期被收回,服务器关闭,被序列化到磁盘等。

         当Servlet容器重启或关闭时,StandardManager负责持久化没有过期的StandardSession对象,调用doUnload方法

将其持久化到一个以" SESSIONS.ser "为文件名的文件中;当Servlet容器重启时,即StandardManager初始化时,调用

doLoad方法会重新读取该文件,解析所有Session对象,重新保存在StandardManager的sessions集合中:

/**
 * Path name of the disk file in which active sessions are saved
 * when we stop, and from which these sessions are loaded when we start.
 * A <code>null</code> value indicates that no persistence is desired.
 * If this pathname is relative, it will be resolved against the
 * temporary working directory provided by our context, available via
 * the <code>javax.servlet.context.tempdir</code> context attribute.
 */
protected String pathname = "SESSIONS.ser";


/**
 * Load any currently active sessions that were previously unloaded
 * to the appropriate persistence mechanism, if any.  If persistence is not
 * supported, this method returns without doing anything.
 *
 * @exception ClassNotFoundException if a serialized class cannot be
 *  found during the reload
 * @exception IOException if an input/output error occurs
 */
protected void doLoad() throws ClassNotFoundException, IOException {
    if (log.isDebugEnabled()) {
        log.debug("Start: Loading persisted sessions");
    }

    // Initialize our internal data structures
    sessions.clear();

    // Open an input stream to the specified pathname, if any
    File file = file();
    if (file == null) {
        return;
    }
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("standardManager.loading", pathname));
    }
    Loader loader = null;
    ClassLoader classLoader = null;
    Log logger = null;
    try (FileInputStream fis = new FileInputStream(file.getAbsolutePath());
         BufferedInputStream bis = new BufferedInputStream(fis)) {
        Context c = getContext();
        loader = c.getLoader();
        logger = c.getLogger();
        if (loader != null) {
            classLoader = loader.getClassLoader();
        }
        if (classLoader == null) {
            classLoader = getClass().getClassLoader();
        }

        // Load the previously unloaded active sessions
        synchronized (sessions) {
            try (ObjectInputStream ois = new CustomObjectInputStream(bis, classLoader, logger,
                    getSessionAttributeValueClassNamePattern(),
                    getWarnOnSessionAttributeFilterFailure())) {
                Integer count = (Integer) ois.readObject();
                int n = count.intValue();
                if (log.isDebugEnabled())
                    log.debug("Loading " + n + " persisted sessions");
                for (int i = 0; i < n; i++) {
                    StandardSession session = getNewSession();
                    session.readObjectData(ois);
                    session.setManager(this);
                    sessions.put(session.getIdInternal(), session);
                    session.activate();
                    if (!session.isValidInternal()) {
                        // If session is already invalid,
                        // expire session to prevent memory leak.
                        session.setValid(true);
                        session.expire();
                    }
                    sessionCounter++;
                }
            } finally {
                // Delete the persistent storage file
                if (file.exists()) {
                    file.delete();
                }
            }
        }
    } catch (FileNotFoundException e) {
        if (log.isDebugEnabled()) {
            log.debug("No persisted data file found");
        }
        return;
    }

    if (log.isDebugEnabled()) {
        log.debug("Finish: Loading persisted sessions");
    }
}


/**
 * Save any currently active sessions in the appropriate persistence
 * mechanism, if any.  If persistence is not supported, this method
 * returns without doing anything.
 *
 * @exception IOException if an input/output error occurs
 */
protected void doUnload() throws IOException {

    if (log.isDebugEnabled())
        log.debug(sm.getString("standardManager.unloading.debug"));

    if (sessions.isEmpty()) {
        log.debug(sm.getString("standardManager.unloading.nosessions"));
        return; // nothing to do
    }

    // Open an output stream to the specified pathname, if any
    File file = file();
    if (file == null) {
        return;
    }
    if (log.isDebugEnabled()) {
        log.debug(sm.getString("standardManager.unloading", pathname));
    }

    // Keep a note of sessions that are expired
    ArrayList<StandardSession> list = new ArrayList<>();

    try (FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            ObjectOutputStream oos = new ObjectOutputStream(bos)) {

        synchronized (sessions) {
            if (log.isDebugEnabled()) {
                log.debug("Unloading " + sessions.size() + " sessions");
            }
            // Write the number of active sessions, followed by the details
            oos.writeObject(Integer.valueOf(sessions.size()));
            Iterator<Session> elements = sessions.values().iterator();
            while (elements.hasNext()) {
                StandardSession session =
                    (StandardSession) elements.next();
                list.add(session);
                session.passivate();
                session.writeObjectData(oos);
            }
        }
    }

    // Expire all the sessions we just wrote
    if (log.isDebugEnabled()) {
        log.debug("Expiring " + list.size() + " persisted sessions");
    }
    Iterator<StandardSession> expires = list.iterator();
    while (expires.hasNext()) {
        StandardSession session = expires.next();
        try {
            session.expire(false);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
        } finally {
            session.recycle();
        }
    }

    if (log.isDebugEnabled()) {
        log.debug("Unloading complete");
    }
}


/**
 * Return a File object representing the pathname to our
 * persistence file, if any.
 * @return the file
 */
protected File file() {
    if (pathname == null || pathname.length() == 0) {
        return null;
    }
    File file = new File(pathname);
    if (!file.isAbsolute()) {
        Context context = getContext();
        ServletContext servletContext = context.getServletContext();
        File tempdir = (File) servletContext.getAttribute(ServletContext.TEMPDIR);
        if (tempdir != null) {
            file = new File(tempdir, pathname);
        }
    }
    return file;
}

StandardSession对象并不是永远存在的,防止内存空间消耗殆尽,会为每个Session对象设置有效时间???

           Tomcat中默认有效时间是60s,由(maxInactiveInterval属性控制),超过60s会过期,在后台线程

backgroundProcess方法中检查每个StandardSession是否过期;

           request.getSession( boolean create )该方法调用时也会检查Session对象是否过期,如果过期,会重新创建一个

StandardSession,但是以前设置的Session值会全部消失,所以如果session.getAttribute( )获取不到以前的值请勿大惊

小怪。

     注意:

           request.getSession(true):若存在会话则返回该会话,否则新建一个会话。

           request.getSession(false):若存在会话则返回该会话,否则返回NULL。

如何自定义封装HttpSession对象???

     在应用的web.xml中配置一个SessionFilter,用于在请求到达MVC框架之前封装HttpServletRequest和

HttpServletResponse对象,并创建自定义InnerHttpSession,并将其设置到request及response对象中。此时通过

request.getSession(boolean create)获取的便是自定义的HttpSession对象,并将个别重要的Session通过拦截response的

addCookies保留到Cookie中留作备份。时序图如下:

猜你喜欢

转载自blog.csdn.net/qq_39028580/article/details/81081779