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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端技术总结

附实例!图解React的生命周期及执行顺序

(1) componentWillMount()  仅在render()方法前被调用一次,如果在该方法中调用了setState方法去改变组件的状态值,那么调用r...

1.2K47
来自专栏河湾欢儿的专栏

组件&生命周期

组件使你可以将 UI 划分为一个一个独立,可复用的小部件,并可以对每个部件进行单独的设计。

921
来自专栏Aox Lei

微信公众号信息抓取方法(二)——抓取文章点赞、阅读、评论、小程序信息

上一篇文章文章将cookie信息保存到redis中, 则这一节主要是取出cookie, 并且构造方法去获取文章的点赞、阅读、评论、小程序信息, 而且不会访问文章...

734
来自专栏移动开发之家

Flutter完整开发实战详解(四、 Redux、主题、国际化)

作为系列文章的第四篇,本篇主要介绍 Flutter 中 Redux 的使用,并结合Redux 完成实时的主题切换与多语言切换功能。

1013
来自专栏GreenLeaves

Web API系列之三 基本功能实现

Web API系列之二讲解了如何搭建一个WebApi的基架,本文主要在其基础之上实现基本的功能.下面开始逐步操作: 一、配置WebApi的路由-用于配置外部如何...

1895
来自专栏学习力

《Java从入门到放弃》JSP入门篇:XMLHttpRequest的基本用法

1746
来自专栏Play & Scala 技术分享

PlayScala 2.5.x - 关于Content-Type的注意事项

3144
来自专栏日常分享

JSP/Servlet Web 学习笔记 DayTwo

   定义JSP文件中的全局属性、一个JSP页面可以包含多个page指令、除了Import以外,其他page指令定义的属性/值只能出现一次。

822
来自专栏拂晓风起

一个类似backbone路由的纯净route ( 前端路由 客户端路由 backbone路由 )

1023
来自专栏Kevin-ZhangCG

SpringMVC学习笔记之一(SpringMVC架构及与Mybatis整合)

774

扫描关注云+社区