先了解 Session,Cookie,JSESSIONID
JSESSIONID 是一个唯一标识号, 用来标识服务器端的 Session, 也用来标识客户端的 Cookie, 客户端和服务器端通过这个 JSESSIONID 来一一对应这里需要说明的是 Cookie 已经包含 JSESSIONID 了, 可以理解为 JSESSIONID 是 Cookie 里的一个属性让我假设一次客户端连接来说明我对个这三个概念的理解
HTTP 连接本身是无状态的, 即前一次发起的连接跟后一次没有任何关系, 是两次独立的连接请求
但是互联网访问基本上都是需要有状态的, 即服务器需要知道两次连接请求是不是同一个人访问的如你在浏览淘宝的时候, 把一个东西加入购物车, 再点开另一个商品页面的时候希望在这个页面里面的购物车还有上次添加进购物车的商品也就是说淘宝服务器会知道这两次访问是同一个客户端访问的
客户端第一次请求到服务器的连接, 这个连接是没有附带任何东西的, 没有 Cookie, 更没有 JSESSIONID 服务器端接收到请求后, 会检查这次请求有没有传过来 JSESSIONID 或者 Cookie, 如果没有 JSESSIONID 或 Cookie, 服务端会创建一个 Session, 并生成一个与该 Session 相关联的 JSESSIONID 发给客户端, 客户端会保存这个 JSESSIONID, 并生成一个与该 JSESSIONID 关联的 Cookie, 第二次请求的时候, 会把该 Cookie(包含 JSESSIONID)一起发送给服务器端, 这次服务器发现这个请求有了 Cookie, 便从中取出 JSESSIONID, 然后根据这个 JSESSIONID 找到对应的 Session, 这样便把 Http 的无状态连接变成了有状态的连接但是有时候浏览器 (即客户端) 会禁用 Cookie, 我们知道 Cookie 是通过 HTTP 请求头部的一个 cookie 字段传过去的, 如果禁用, 便得不到这个值, JSESSIONID 也不能通过 Cookie 传入服务器端
当然我们还有其他的解决办法, url 重写和隐藏表单, url 重写就是把 JSESSIONID 附带在 url 后面传过去隐藏表单是在表单提交的时候传入一个隐藏字段 JSESSIONID 这两种方式都能把 JSESSIONID 传过去
下面来看 Tomcat 是怎么实现以上流程的连接请求会交给 HttpProcessor 的 process 方法处理, 在此方法有这么几句
- parseConnection(socket);
- parseRequest(input, output);// 解析请求行, 如果有 jessionid, 会在方法里面解析 jessionid
- if (!request.getRequest().getProtocol()
- .startsWith("HTTP/0"))
- parseHeaders(input);// 解析请求头部, 如果有 cookie 字段, 在方法里面会解析 cookie,
下面看 parseRequest 方法里面是怎么解析 jessionid 的, 这种解析方式是针对 url 重写的
parseRequest 方法:
- int semicolon = uri.indexOf(match);//match 是; JSESSIONID=, 即在请求行查找字段 JSESSIONID
- if (semicolon >= 0) { // 如果有 JSESSIONID 字段, 表示不是第一次访问
- String rest = uri.substring(semicolon + match.length());
- int semicolon2 = rest.indexOf(';');
- if (semicolon2 >= 0) {
- request.setRequestedSessionId(rest.substring(0, semicolon2));// 设置 sessionid
- rest = rest.substring(semicolon2);
- } else {
- request.setRequestedSessionId(rest);
- rest = "";
- }
- request.setRequestedSessionURL(true);
- uri = uri.substring(0, semicolon) + rest;
- if (debug >= 1)
- log("Requested URL session id is" +
- ((HttpServletRequest) request.getRequest())
- .getRequestedSessionId());
- } else { // 如果请求行没有 JSESSIONID 字段, 表示是第一次访问
- request.setRequestedSessionId(null);
- request.setRequestedSessionURL(false);
- }
代码没什么说的, 看 url 有没有 JSESSIONID, 有就设置 request 的 sessionid, 没有就设置为 null 有再看 parseHeaders 方法
- .....
- ....else if (header.equals(DefaultHeaders.COOKIE_NAME)) { //COOKIE_NAME 的值是 cookie
- Cookie cookies[] = RequestUtil.parseCookieHeader(value);
- for (int i = 0; i < cookies.length; i++) {
- if (cookies[i].getName().equals
- (Globals.SESSION_COOKIE_NAME)) {
- // Override anything requested in the URL
- if (!request.isRequestedSessionIdFromCookie()) {
- // Accept only the first session id cookie
- request.setRequestedSessionId
- (cookies[i].getValue());// 设置 sessionid
- request.setRequestedSessionCookie(true);
- request.setRequestedSessionURL(false);
- if (debug >= 1)
- log("Requested cookie session id is" +
- ((HttpServletRequest) request.getRequest())
- .getRequestedSessionId());
- }
- }
- if (debug >= 1)
- log("Adding cookie" + cookies[i].getName() + "=" +
- cookies[i].getValue());
- request.addCookie(cookies[i]);
- }
- }
主要就是从 http 请求头部的字段 cookie 得到 JSESSIONID 并设置到 reqeust 的 sessionid, 没有就不设置
这样客户端的 JSESSIONID(cookie)就传到 tomcat,tomcat 把 JSESSIONID 的值赋给 request 了
这个 request 在 Tomcat 的唯一性就标识了
我们知道, Session 只对应用有用, 两个应用的 Session 一般不能共用, 在 Tomcat 一个 Context 代表一个应用, 所以一个应用应该有一套自己的 Session,Tomcat 使用 Manager 来管理各个应用的 Session,Manager 也是一个组件, 跟 Context 是一一对应的关系
Manager 的标准实现是 StandardManager, 由它统一管理 Context 的 Session 对象(标准实现是 StandardSession), 能够猜想, StandardManager 一定能够创建 Session 对象和根据 JSESSIONID 从跟它关联的应用中查找 Session 对象事实上 StandardManager 确实有这样的方法, 但是 StandardManager 本身没有这两个方法, 它的父类 ManagerBase 有这两个方法
ManagerBase 类的 findSession 和 createSession()方法
- public Session findSession(String id) throws IOException {
- if (id == null) return (null);
- synchronized(sessions) {
- Session session = (Session) sessions.get(id); // 根据 sessionid(即 < span style="font-family: Arial;">JSESSIONID</span>)查找 session 对象
- return (session);
- }
- }
- public Session createSession() { // 创建 session 对象
- // Recycle or create a Session instance
- Session session = null;
- synchronized(recycled) {
- int size = recycled.size();
- if (size > 0) {
- session = (Session) recycled.get(size - 1);
- recycled.remove(size - 1);
- }
- }
- if (session != null) session.setManager(this);
- else session = new StandardSession(this);
- // Initialize the properties of the new session and return it
- session.setNew(true);
- session.setValid(true);
- session.setCreationTime(System.currentTimeMillis());
- session.setMaxInactiveInterval(this.maxInactiveInterval);
- String sessionId = generateSessionId(); // 使用 md5 算法生成 sessionId
- String jvmRoute = getJvmRoute();
- // @todo Move appending of jvmRoute generateSessionId()???
- if (jvmRoute != null) {
- sessionId += '.' + jvmRoute;
- session.setId(sessionId);
- }
- session.setId(sessionId);
- return (session);
- }
以上是 StandardManager 的管理 Session 的两个重要方法
这里有一个问题, Session 是在什么时候生成的?
仔细想想, 我们编写 servlet 的时候, 如果需要 Session, 会使用 request.getSession(), 这个方法最后会调用到 HttpRequestBase 的 getSession()方法
所以这里有个重要的点: Session 并不是在客户端第一次访问就会在服务器端生成, 而是在服务器端 (一般是 servlet 里) 使用 request 调用 getSession 方法才生成的但是默认情况下, jsp 页面会调用 request.getSession(), 即 jsp 页面的这个属性 <%@ page session="true" %> 默认是 true 的, 编译成 servlet 后会调用 request.getSession()所以只要访问 jsp 页面, 一般是会在服务器端创建 session 的但是在 servlet 里就需要显示的调用 getSession(), 当然是在要用 session 的情况下面看这个 getSession()方法
HttpRequestBase.getSession()
调用 ---------------
HttpRequestBase.getSession(boolean create)
调用 ----------------
- HttpRequestBase.doGetSession(boolean create){
- 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.getSession());
- // 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);// 这里调用 StandardManager 的 findSession 方法查找是否存在 Session 对象
- } catch (IOException e) {
- session = null;
- }
- if ((session != null) && !session.isValid())
- session = null;
- if (session != null) {
- return (session.getSession());
- }
- }
- // Create a new session if requested and the response is not committed
- if (!create)
- return (null);
- if ((context != null) && (response != null) &&
- context.getCookies() &&
- response.getResponse().isCommitted()) {
- throw new IllegalStateException
- (sm.getString("httpRequestBase.createCommitted"));
- }
- session = manager.createSession();// 这里调用 StandardManager 的创建 Session 对象
- if (session != null)
- return (session.getSession());
- else
- return (null);
- }
来源: http://www.jianshu.com/p/dd6b8c35f644