我们访问某个很多网站时,第一次进入需要登陆,但进入下一个页面时就不需要再登陆了,否则这个网站的可用性就是零啦~~
在第二次访问时,我们想让服务端识别出请求来自同一个客户。要怎么做呢?其中一个方法就是用session来实现。
首先,session是存储在服务端的数据。一个请求到来时,服务端首先看请求中有没有sessionId,若没有,就新建一个session,并将sessionId传回给客户。客户第二次访问时,带上sessionId,这样服务端就可以识别出它啦~
在分布式系统中,有多台服务器,怎样保证sessionId不会有重复呢?大家可以去看一下sessionId的生成方法,它包含了jvm机器的信息及随机数等,所以不会重复。
分布式系统中,如果session由机器A生成,即存储在机器A,但下一次请求到达了机器B,那么机器B上就找不到sessionId对应的session,这时候怎么办呢?这就涉及到分布式系统下的session共享问题,大家可以看一看淘宝等的解决方案。
tomcat处理请求流程图
首先,session要有存储的地方,tomcat提供了FileStore和JDBCStore,即文件存储和数据库存储。
tomcat包括了connector和container两部分,那么由谁负责管理session呢?当然是container啦。因为,connector是负责通信和分发任务的,而container负责处理请求这一部分。
container就是一个容器,包含了多个组件(见ManagerBase),比如类加载器loader。同样地,session的管理也不是由container直接负责,而是另一个组件manager来负责。manager的主要功能有创建session、查找session、从文件或DB加载session、将session存入文件或DB等。
tomcat和manager的交互过程:
container.start(),设置完成后,调用manager.start(),开始加载session等。container.stop(),项目停止,调用manager.stop(),存储session等。container.reload(),项目重载,先调用manager.stop(),再调用manager.start().嗯。。过程看起来还是很清晰简单的。下面我们具体看一下manager的一些操作吧。
load()加载存储在文件中的session。
public void load() throws ClassNotFoundException, IOException { if (debug >= 1) log("Start: Loading persisted sessions"); // Initialize our internal data structures recycled.clear(); sessions.clear(); // Open an input stream to the specified pathname, if any //打开指定路径的文件 File file = file(); if (file == null) return; if (debug >= 1) log(sm.getString("standardManager.loading", pathname)); FileInputStream fis = null; ObjectInputStream ois = null; Loader loader = null; ClassLoader classLoader = null; try { fis = new FileInputStream(file.getAbsolutePath()); BufferedInputStream bis = new BufferedInputStream(fis); if (container != null) loader = container.getLoader(); if (loader != null) classLoader = loader.getClassLoader(); if (classLoader != null) { if (debug >= 1) log("Creating custom object input stream for class loader " + classLoader); ois = new CustomObjectInputStream(bis, classLoader); } else { if (debug >= 1) log("Creating standard object input stream"); ois = new ObjectInputStream(bis); } } catch (FileNotFoundException e) { if (debug >= 1) log("No persisted data file found"); return; } catch (IOException e) { log(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } // Load the previously unloaded active sessions // 读取文件中存储的session数据 synchronized (sessions) { try { Integer count = (Integer) ois.readObject(); int n = count.intValue(); if (debug >= 1) log("Loading " + n + " persisted sessions"); for (int i = 0; i < n; i++) { StandardSession session = new StandardSession(this); session.readObjectData(ois); session.setManager(this); sessions.put(session.getId(), session); ((StandardSession) session).activate(); } } catch (ClassNotFoundException e) { log(sm.getString("standardManager.loading.cnfe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } catch (IOException e) { log(sm.getString("standardManager.loading.ioe", e), e); if (ois != null) { try { ois.close(); } catch (IOException f) { ; } ois = null; } throw e; } finally { // Close the input stream try { if (ois != null) ois.close(); } catch (IOException f) { // ignored } // Delete the persistent storage file // 删除文件 if (file != null && file.exists() ) file.delete(); } } if (debug >= 1) log("Finish: Loading persisted sessions"); }run()调用processExpires()判断并处理session过期。
private void processExpires() { long timeNow = System.currentTimeMillis(); Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { StandardSession session = (StandardSession) sessions[i]; if (!session.isValid()) continue; // maxInactiveInterval<0,该session就一直有效 int maxInactiveInterval = session.getMaxInactiveInterval(); if (maxInactiveInterval < 0) continue; int timeIdle = // Truncate, do not round up (int) ((timeNow - session.getLastAccessedTime()) / 1000L); if (timeIdle >= maxInactiveInterval) { try { session.expire(); } catch (Throwable t) { log(sm.getString("standardManager.expireException"), t); } } } }StandardManager.stop(),停止后台处理session过期的线程,并存储session。
public void stop() throws LifecycleException { if (debug >= 1) log("Stopping"); // Validate and update our current component state if (!started) throw new LifecycleException (sm.getString("standardManager.notStarted")); lifecycle.fireLifecycleEvent(STOP_EVENT, null); started = false; // Stop the background reaper thread threadStop(); // Write out sessions try { unload(); } catch (IOException e) { log(sm.getString("standardManager.managerUnload"), e); } // Expire all active sessions Session sessions[] = findSessions(); for (int i = 0; i < sessions.length; i++) { StandardSession session = (StandardSession) sessions[i]; if (!session.isValid()) continue; try { session.expire(); } catch (Throwable t) { ; } } // Require a new random number generator if we are restarted this.random = null; }StandardManager.createSession(),创建session。
public Session createSession() { // 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(); String jvmRoute = getJvmRoute(); // @todo Move appending of jvmRoute generateSessionId()??? if (jvmRoute != null) { sessionId += '.' + jvmRoute; session.setId(sessionId); } /* synchronized (sessions) { while (sessions.get(sessionId) != null) // Guarantee uniqueness sessionId = generateSessionId(); } */ session.setId(sessionId); return (session); }createSession()是什么时候被调用呢?当我们调用request.getSession()的时候。request中有container的引用,当我们调用request.getSession(),首先判断request中是否有session;若没有,获取container.manager,调用manager.findSession()查找已有session;若没有,则可以调用manager.createSession来创建。
private HttpSession 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.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); } 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(); if (session != null) return (session.getSession()); else return (null); }HttpRequestBase、HttpRequestFacade、HttpRequestImpl的关系
HttpSession、HttpSessionFacade的关系?
sessionId什么时候返回给客户?如何返回?