本文主要介紹下spring boot中對session timeout參數值的設置過程。java
spring-boot-autoconfigure-1.5.8.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/web/ServerProperties.javaweb
@Override public void customize(ConfigurableEmbeddedServletContainer container) { if (getPort() != null) { container.setPort(getPort()); } if (getAddress() != null) { container.setAddress(getAddress()); } if (getContextPath() != null) { container.setContextPath(getContextPath()); } if (getDisplayName() != null) { container.setDisplayName(getDisplayName()); } if (getSession().getTimeout() != null) { container.setSessionTimeout(getSession().getTimeout()); } //...... }
對應的配置以下spring
server.session.timeout=120
須要注意單位是秒apache
spring-boot-1.5.8.RELEASE-sources.jar!/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.javatomcat
protected void configureContext(Context context, ServletContextInitializer[] initializers) { TomcatStarter starter = new TomcatStarter(initializers); if (context instanceof TomcatEmbeddedContext) { // Should be true ((TomcatEmbeddedContext) context).setStarter(starter); } context.addServletContainerInitializer(starter, NO_CLASSES); for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { context.addLifecycleListener(lifecycleListener); } for (Valve valve : this.contextValves) { context.getPipeline().addValve(valve); } for (ErrorPage errorPage : getErrorPages()) { new TomcatErrorPage(errorPage).addToContext(context); } for (MimeMappings.Mapping mapping : getMimeMappings()) { context.addMimeMapping(mapping.getExtension(), mapping.getMimeType()); } configureSession(context); for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { customizer.customize(context); } } private void configureSession(Context context) { long sessionTimeout = getSessionTimeoutInMinutes(); context.setSessionTimeout((int) sessionTimeout); if (isPersistSession()) { Manager manager = context.getManager(); if (manager == null) { manager = new StandardManager(); context.setManager(manager); } configurePersistSession(manager); } else { context.addLifecycleListener(new DisablePersistSessionListener()); } } private long getSessionTimeoutInMinutes() { long sessionTimeout = getSessionTimeout(); if (sessionTimeout > 0) { sessionTimeout = Math.max(TimeUnit.SECONDS.toMinutes(sessionTimeout), 1L); } return sessionTimeout; }
這裏要注意一下,它內部轉成分鐘,而後設置給tomcat原生的StandardContextsession
能夠從源碼看到,若是設置小於60秒的話,則會默認取1分鐘app
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/core/StandardContext.javaless
@Override public void setSessionTimeout(int timeout) { int oldSessionTimeout = this.sessionTimeout; /* * SRV.13.4 ("Deployment Descriptor"): * If the timeout is 0 or less, the container ensures the default * behaviour of sessions is never to time out. */ this.sessionTimeout = (timeout == 0) ? -1 : timeout; support.firePropertyChange("sessionTimeout", oldSessionTimeout, this.sessionTimeout); }
這一步就是設置給原生的tomcat的StandardContextsocket
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/session/StandardSession.javaasync
/** * Return the <code>isValid</code> flag for this session. */ @Override public boolean isValid() { if (!this.isValid) { return false; } if (this.expiring) { return true; } if (ACTIVITY_CHECK && accessCount.get() > 0) { return true; } if (maxInactiveInterval > 0) { int timeIdle = (int) (getIdleTimeInternal() / 1000L); if (timeIdle >= maxInactiveInterval) { expire(true); } } return this.isValid; }
這裏會去計算timeIdle,而後經過timeIdle的值跟設定的session timeout比較,超出則設置session失效
/** * Return the idle time from last client access time without invalidation check * @see #getIdleTime() */ @Override public long getIdleTimeInternal() { long timeNow = System.currentTimeMillis(); long timeIdle; if (LAST_ACCESS_AT_START) { timeIdle = timeNow - lastAccessedTime; } else { timeIdle = timeNow - thisAccessedTime; } return timeIdle; }
維護了兩個變量,一個是lastAccessedTime,一個是thisAccessedTime
這個是在這個方法中更新
/** * End the access. */ @Override public void endAccess() { isNew = false; /** * The servlet spec mandates to ignore request handling time * in lastAccessedTime. */ if (LAST_ACCESS_AT_START) { this.lastAccessedTime = this.thisAccessedTime; this.thisAccessedTime = System.currentTimeMillis(); } else { this.thisAccessedTime = System.currentTimeMillis(); this.lastAccessedTime = this.thisAccessedTime; } if (ACTIVITY_CHECK) { accessCount.decrementAndGet(); } }
tomcat-embed-core-8.5.23-sources.jar!/org/apache/coyote/http11/Http11Processor.java
@Override public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException { //...... while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null && sendfileState == SendfileState.DONE && !endpoint.isPaused()) { // ...... // Process the request in the adapter if (!getErrorState().isError()) { try { rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); getAdapter().service(request, response); // Handle when the response was committed before a serious // error occurred. Throwing a ServletException should both // set the status to 500 and set the errorException. // If we fail here, then the response is likely already // committed, so we can't try and set headers. if(keepAlive && !getErrorState().isError() && !isAsync() && statusDropsConnection(response.getStatus())) { setErrorState(ErrorState.CLOSE_CLEAN, null); } } catch (InterruptedIOException e) { setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e); } catch (HeadersTooLargeException e) { log.error(sm.getString("http11processor.request.process"), e); // The response should not have been committed but check it // anyway to be safe if (response.isCommitted()) { setErrorState(ErrorState.CLOSE_NOW, e); } else { response.reset(); response.setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, e); response.setHeader("Connection", "close"); // TODO: Remove } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("http11processor.request.process"), t); // 500 - Internal Server Error response.setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, t); getAdapter().log(request, response, 0); } } // Finish the handling of the request rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT); if (!isAsync()) { // If this is an async request then the request ends when it has // been completed. The AsyncContext is responsible for calling // endRequest() in that case. endRequest(); } //...... } //...... }
這裏的service方法在getErrorState().isError()爲false的時候,會調用adapter的service方法
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/connector/CoyoteAdapter.java
@Override public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) throws Exception { Request request = (Request) req.getNote(ADAPTER_NOTES); Response response = (Response) res.getNote(ADAPTER_NOTES); //... try { // Parse and set Catalina and configuration specific // request parameters postParseSuccess = postParseRequest(req, request, res, response); if (postParseSuccess) { //check valves if we support async request.setAsyncSupported( connector.getService().getContainer().getPipeline().isAsyncSupported()); // Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response); } if (request.isAsync()) { async = true; ReadListener readListener = req.getReadListener(); if (readListener != null && request.isFinished()) { // Possible the all data may have been read during service() // method so this needs to be checked here ClassLoader oldCL = null; try { oldCL = request.getContext().bind(false, null); if (req.sendAllDataReadEvent()) { req.getReadListener().onAllDataRead(); } } finally { request.getContext().unbind(false, oldCL); } } Throwable throwable = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION); // If an async request was started, is not going to end once // this container thread finishes and an error occurred, trigger // the async error process if (!request.isAsyncCompleting() && throwable != null) { request.getAsyncContextInternal().setErrorState(throwable, true); } } else { request.finishRequest(); response.finishResponse(); } } catch (IOException e) { // Ignore } finally { //...... // Recycle the wrapper request and response if (!async) { request.recycle(); response.recycle(); } } }
會在finally裏頭調用request.recycle()
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/connector/Request.java
裏頭的方法會調用recycleSessionInfo
protected void recycleSessionInfo() { if (session != null) { try { session.endAccess(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.warn(sm.getString("coyoteRequest.sessionEndAccessFail"), t); } } session = null; requestedSessionCookie = false; requestedSessionId = null; requestedSessionURL = false; requestedSessionSSL = false; }
這裏就更新了兩個事件
適合處理error直接forward的狀況,好比鑑權不經過,直接forward的,這個時候還沒進入到servlet的service方法
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/core/ApplicationDispatcher.java
private void invoke(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException { //...... // Get the FilterChain Here ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); // Call the service() method for the allocated servlet instance try { // for includes/forwards if ((servlet != null) && (filterChain != null)) { filterChain.doFilter(request, response); } // Servlet Service Method is called by the FilterChain } catch (ClientAbortException e) { ioException = e; } catch (IOException e) { wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e); ioException = e; } catch (UnavailableException e) { wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e); servletException = e; wrapper.unavailable(e); } catch (ServletException e) { Throwable rootCause = StandardWrapper.getRootCause(e); if (!(rootCause instanceof ClientAbortException)) { wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), rootCause); } servletException = e; } catch (RuntimeException e) { wrapper.getLogger().error(sm.getString("applicationDispatcher.serviceException", wrapper.getName()), e); runtimeException = e; } // Release the filter chain (if any) for this request try { if (filterChain != null) filterChain.release(); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); wrapper.getLogger().error(sm.getString("standardWrapper.releaseFilters", wrapper.getName()), e); // FIXME: Exception handling needs to be similar to what is in the StandardWrapperValue } // Deallocate the allocated servlet instance try { if (servlet != null) { wrapper.deallocate(servlet); } } catch (ServletException e) { wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e); servletException = e; } catch (Throwable e) { ExceptionUtils.handleThrowable(e); wrapper.getLogger().error(sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e); servletException = new ServletException (sm.getString("applicationDispatcher.deallocateException", wrapper.getName()), e); } // Reset the old context class loader context.unbind(false, oldCCL); // Unwrap request/response if needed // See Bugzilla 30949 unwrapRequest(state); unwrapResponse(state); // Recycle request if necessary (also BZ 30949) recycleRequestWrapper(state); // ...... }
執行完servlet以後(無論成功仍是失敗),會調用recycleRequestWrapper
private void recycleRequestWrapper(State state) { if (state.wrapRequest instanceof ApplicationHttpRequest) { ((ApplicationHttpRequest) state.wrapRequest).recycle(); } }
tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/core/ApplicationHttpRequest.java
/** * Recycle this request */ public void recycle() { if (session != null) { session.endAccess(); } }
這裏會調用endAccess,更新兩個時間