美文网首页
Shiro支持Servlet3.0的异步请求

Shiro支持Servlet3.0的异步请求

作者: 空挡 | 来源:发表于2018-09-26 23:55 被阅读0次

    Servlet3.0开始支持Request的异步处理,所谓异步处理就是服务端在收到请求之后,并不是直接开始调用业务代码开始存取数据等耗时操作,而是交给后端的线程池来处理,这样请求接收线程就可以继续接收新进来的请求。等线程池处理完之后,通过AsyncContext回调来返回数据给客户端。
    异步请求使用场景:

    • 文件上传等耗时请求,可以采用异步处理,不至于因为同时多个人在上传文件而耗光web容器的线程,影响其它快速请求
    • 高并发服务器,对于高并发服务器,异步化必然选择,不但请求进来异步处理。在业务线程池调用其它服务的时候也需要异步,防止耗时操作耗光线程数,我们借用一张图来看一下:


      转载自拿铁咖啡公众号

    Spring MVC异步支持

    Spring MVC中已经对异步请求做了封装,只要在controller的方法中返回一个Callable或者DeferredResult就可以了,比如下面的例子:

    @RestController
    public class AsyncRequestController {
    
        @GetMapping("/async")
        public Callable<UserDto> doAsync(){
            return ()->{
                Thread.sleep(5000);
                return (UserDto)SecurityUtils.getSubject().getPrincipal();
            };
        }
    }
    

    对于异步请求的线程池初始化,可以在重写WebMvcConfigurationSupportconfigureAsyncSupport()方法。

    @Configuration
    public class WebConfiguration extends WebMvcConfigurationSupport{
        @Override
        protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
             //设置一个3个线程的线程池来处理异步请求
            configurer.setTaskExecutor(new ConcurrentTaskExecutor(Executors.newFixedThreadPool(3)));
            //异步请求处理超时为30秒
            configurer.setDefaultTimeout(30000);
        }
    }
    

    Shiro针对异步请求的配置

    还是以上次的shiro实现jwt认证授权的项目为例(传送门:https://www.jianshu.com/p/0b1131be7ace),如果我们按照原来的配置,请求上面的异步controller会出现下面的错误:

    2018-09-24 00:26:31.659 [ERROR][http-nio-8080-exec-2]:o.a.c.c.C.[.[localhost].[/].[dispatcherServlet] [log:182] Servlet.service() for servlet [dispatcherServlet] threw exception
    org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton.  This is an invalid application configuration.
        at org.apache.shiro.SecurityUtils.getSecurityManager(SecurityUtils.java:123)
        at org.apache.shiro.subject.Subject$Builder.<init>(Subject.java:626)
        at org.apache.shiro.SecurityUtils.getSubject(SecurityUtils.java:56)
        at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSubject(ShiroHttpServletRequest.java:89)
        at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getSubjectPrincipal(ShiroHttpServletRequest.java:94)
        at org.apache.shiro.web.servlet.ShiroHttpServletRequest.getUserPrincipal(ShiroHttpServletRequest.java:112)
        at org.springframework.web.servlet.FrameworkServlet.getUsernameForRequest(FrameworkServlet.java:1093)
        at org.springframework.web.servlet.FrameworkServlet.publishRequestHandledEvent(FrameworkServlet.java:1078)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:866)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:635)
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:851)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
        at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:728)
        at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:649)
        at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:612)
        at org.apache.catalina.core.AsyncContextImpl$AsyncRunnable.run(AsyncContextImpl.java:567)
        at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:353)
    

    上面错误的原因是我们的shiro filter没有对AsyncContext中的Request没有做拦截,造成SecurityManager为空。解决这个问题,只要在注册shiro Filter的地方做如下配置:

    @Bean
        public FilterRegistrationBean<Filter> filterRegistrationBean(SecurityManager securityManager,UserService userService) throws Exception{
            FilterRegistrationBean<Filter> filterRegistration = new FilterRegistrationBean<Filter>();
            filterRegistration.setFilter((Filter)shiroFilter(securityManager, userService).getObject());
            filterRegistration.addInitParameter("targetFilterLifecycle", "true");
            filterRegistration.setAsyncSupported(true);
            filterRegistration.setEnabled(true);
            //这里添加一下对DispatcherType.ASYNC的支持就可以了
            filterRegistration.setDispatcherTypes(DispatcherType.REQUEST,DispatcherType.ASYNC);
    
            return filterRegistration;
        }
    

    Filter配置
    在SpringMVC的实现中,对于Filter在进入异步请求之前会过一遍,异步请求之后又会过一遍。但是对于shiro这种鉴权的Filter,其实第二遍是没有必要的。所以我们需要在Filter中针对第二次过Filter的情况跳过。实现方式就是第一次进Filter的时候在request的Attribute中加一个属性,这样第二次进来的时候就会发现这个属性不为空,直接跳过,这也是servlet中OncePerRequestFilter的实现逻辑。以JwtAuthFilter为例:

    public class JwtAuthFilter extends AuthenticatingFilter {
        @Override
        protected void postHandle(ServletRequest request, ServletResponse response){
            //设置一个标记位
            request.setAttribute("jwtShiroFilter.FILTERED", true);
        }
    
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            //判断是不是第二次进入,是则直接返回
            Boolean afterFiltered = (Boolean)(request.getAttribute("jwtShiroFilter.FILTERED"));
            if( BooleanUtils.isTrue(afterFiltered))
                return true;
            ...
        }
    }
    

    同样的RolesFilter也添加类似的逻辑,具体请看源代码:https://github.com/chilexun/springboot-demo/tree/master/shiro-jwt-demo

    [参考资料]
    异步化,高并发大杀器 作者: 拿铁咖啡

    相关文章

      网友评论

          本文标题:Shiro支持Servlet3.0的异步请求

          本文链接:https://www.haomeiwen.com/subject/vocsoftx.html