filter失误造成请求servlet两次

今天工作遇到一个问题:每次请求接口如果业务代码报错的话会再请求一次dispatchservlet的doservice方法;请求两次接口
开始怀疑是前端请求了两次,但是经过排查并不是这个原因,而且用postman测试接口也出现同样的问题:

经过分析发现是在新加了一个filter后出现的情况,于是把新加的filter去掉后测试果然不会再出现请求两次的情况
现在我们来看一下这个filter的问题:
同事在dofilter()方法中写了这样一段代码

try {
           
            logVo.setRequestBody(bodyStringBuilder.toString());
            logVo.setHost(servletRequest.getHeader("Host"));
            logVo.setIp(getIpAddress(servletRequest));
            logVo.setOperateTime(new Date());
            resolveUserIdentify(logVo);
            long start = System.currentTimeMillis();
            chain.doFilter(servletRequest, response);//交给下个filter
            logVo.setTimestamp(System.currentTimeMillis() - start);
            log.info("Request1:{}", JSON.toJSONString(logVo));
        }catch (Exception e){
            log.error("AbstractLogFilter error",e);
            long start = System.currentTimeMillis();
            chain.doFilter(servletRequest, response);//交给下个filter
            logVo.setTimestamp(System.currentTimeMillis() - start);
            log.info("Request2:{}", JSON.toJSONString(logVo));
        }

重点在try块和catch块中的chain.doFilter(servletRequest, response);
每次try中报错后这个filter会捕获异常,并且再次调用chain.doFilter(servletRequest, response);
那么如果try块中chain.doFilter(servletRequest, response)之前没有报错会怎么样呢:
我们进入tomcat的ApplicationFilterChain的doFilter()方法:

 if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }

调用了internalDoFilter(req,res):

if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null;
            try {
                filter = filterConfig.getFilter();
                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
                                          filter, request, response);

                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                            Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();

                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege
                        ("doFilter", filter, classType, args, principal);

                } else {
                    filter.doFilter(request, response, this);
                }

                support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                          filter, request, response);
            } catch (IOException | ServletException | RuntimeException e) {
                if (filter != null)
                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                              filter, request, response, e);
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                if (filter != null)
                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
                                              filter, request, response, e);
                throw new ServletException
                  (sm.getString("filterChain.filter"), e);
            }
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
        try {
            if (ApplicationDispatcher.WRAP_SAME_OBJECT) {
                lastServicedRequest.set(request);
                lastServicedResponse.set(response);
            }

            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
                                      servlet, request, response);
            if (request.isAsyncSupported()
                    && !support.getWrapper().isAsyncSupported()) {
                request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR,
                        Boolean.FALSE);
            }
            // Use potentially wrapped request from this point
            if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {

                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();
                    Object[] args = new Object[]{req, res};
                    SecurityUtil.doAsPrivilege("service",
                                               servlet,
                                               classTypeUsedInService,
                                               args,
                                               principal);
                } else {
                    servlet.service(request, response);
                }
            } else {
                servlet.service(request, response);
            }
            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
                                      servlet, request, response);
        }

重点在pos和n,来看一下这两个变量的注释,简单来说n表示chain上filter的数量,pos表示已经执行过得filter数量

    /**
     * The int which is used to maintain the current position
     * in the filter chain.
     */
    private int pos = 0;

    /**
     * The int which gives the current number of filters in the chain.
     */
    private int n = 0;

那么在try中成功执行过chain.doFilter(servletRequest,response)方法并且所有filter都执行成功后,poss=n;而这个时候业务代码报错了,filter捕获这个异常后又在catch{}块中又一次执行了chain.doFilter(servletRequest, response);这时if (pos< n)判断为flase;
代码会走到servlet.service(request, response);
又一次去调用servlet的service()方法导致业务代码执行了两次。

原来同事写这段代码的原因是try{}块中的chain.doFilter(servletRequest,response)之前可能会出现报错的情况,如果不catch会出现filter无法执行完,也就无法进入servlet的情况,try得位置不对导致了chain.doFilter(servletRequest,response)执行两次就会出现调用两次servlet的情况

你可能感兴趣的:(filter失误造成请求servlet两次)