博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
聊聊springboot session timeout参数设置
阅读量:7035 次
发布时间:2019-06-28

本文共 15927 字,大约阅读时间需要 53 分钟。

本文主要介绍下spring boot中对session timeout参数值的设置过程。

ServerProperties

spring-boot-autoconfigure-1.5.8.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/web/ServerProperties.java

@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());        }        //......    }

对应的配置如下

server.session.timeout=120

需要注意单位是秒

TomcatEmbeddedServletContainerFactory

spring-boot-1.5.8.RELEASE-sources.jar!/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java

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原生的StandardContext

可以从源码看到,如果设置小于60秒的话,则会默认取1分钟

StandardContext

tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/core/StandardContext.java

@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的StandardContext

session失效的计算

tomcat-embed-core-8.5.23-sources.jar!/org/apache/catalina/session/StandardSession.java

/**     * Return the isValid 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失效

getIdleTimeInternal

/**     * 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();        }    }

正常请求更新

Http11Processor

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方法

CoyoteAdapter

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()

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

这里就更新了两个事件

forward中更新

适合处理error直接forward的情况,比如鉴权不通过,直接forward的,这个时候还没进入到servlet的service方法

ApplicationDispatcher#recycleRequestWrapper

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,更新两个时间

小结

  • 每次的请求,都会跟新session的lastAccessedTime和thisAccessedTime,只有没有访问超过设定时间才会失效
  • server.session.timeout设定的单位是秒,但是小于60的话,会被重置为60,内部转为分钟单位来算,默认1800是30分钟

转载地址:http://wufal.baihongyu.com/

你可能感兴趣的文章
JQuery自定义插件开发(二)
查看>>
select函数
查看>>
理解二叉搜索树
查看>>
Centos 配置iptables防火墙
查看>>
多态在 Java 和 C++ 编程语言中的实现比较
查看>>
数字签名
查看>>
在Linux 双机下自己手动实现浮动ip技术
查看>>
Spring 和 Hibernate 遇到问题
查看>>
一个页面的倒计时代码
查看>>
Node.js笔记(一)项目的建立
查看>>
PullToRefreshScrollView 滑动监听 actionbar渐变
查看>>
nginx错误解决方法个人总结
查看>>
利用Solid Converter PDF与Office优化处理文档信息
查看>>
spring boot 1.3.5 PUT方法接收参数
查看>>
Java 并发之 CountDownLatch、CyclicBarrier 和 Semaphore
查看>>
./ source 以及 exec的区别
查看>>
vsftpd虚拟配置增加用户脚本
查看>>
linux下安装DNS服务器
查看>>
Ext2文件系统 inode
查看>>
Java中四种XML解析技术
查看>>