一个关于 net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 的BUG,在chrome与ajax请求的情况下

BUG的环境,WEB服务器是TOMCAT8.5, 框架是SPRING MVC 3.2.4 ,开发环境IDEA 2020,JDK1.8 ; 这是个老项目改造,我也帮同事定位的。

1. 异常情况描述:

       通过chrome浏览器或ajax请求时,chrome能接收到服务段返回的数据,ajax请求直接抛network error的错误信息。网上主要又两种说法:

一种说法是返回JSON格式数据不完整导致。

另一种说法是 Transfer-Encoding:chunked 块传输有BUG。

2. BUG处理过程:

      处理过程一波三折,一开始总是各种怀疑与验证。认为前端同事的问题,认为请求头的问题,认为返回头的问题。然后搜索相同的BUG情况,网i上说的两种情况也不符合。但是那天晚上在加时用 BING搜英文确实有搜到说,Tomcat 8对Spring MVC的异步处理有BUG。接着排除了前端的问题才把注意力集中在后端上来,我知道 Spring MVC DispatcherServlet 的请求时异步处理的,DispatcherServlet 的 doDispath 的部分源码:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = processedRequest != request;

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest, false);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						String requestUri = urlPathHelper.getRequestUri(request);
						logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				try {
					// Actually invoke the handler.
					mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				}
				finally {
					if (asyncManager.isConcurrentHandlingStarted()) {
						return;
					}
				}

				applyDefaultViewName(request, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				return;
			}
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}

代码中 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 就是获取处理异步请求的对象。并且发现请求过来后TOMCAT 确实有异常:

java.lang.IllegalStateException: Cannot create a session after the response has been committed
    at org.apache.catalina.connector.Requisest.doGetSession(Request.java:3034)
    at org.apache.catalina.connector.Request.getSession(Request.java:2468)
    at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:896)
    at org.apache.catalina.connector.RequestFacade.getSession(RequestFacade.java:908)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)

报异常也很明确,说response 已经是提交的状态,所以不能创建session 了,根据异常的字面意思,就是非法的状态异常。这个异常会抛到 DispatcherServlet 里,所以导致前端报了 ERR_INCOMPLETE_CHUNKED_ENCODING  的异常。而这个创建 session 是项目的 拦截器调用的,代码如下:

 @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object arg2, ModelAndView view) throws Exception {
        Conf CONF = (Conf) request.getSession().getServletContext().getAttribute("APP_CONF");
        if (CONF == null) {
            CONF = confService.findConf();
            request.getSession().getServletContext().setAttribute("APP_CONF", CONF);
        }
    }

在 request.getSession() 需要创建 session 对象,上面的异常这么清晰,一开始是没有的,而是在项目中把Tomcat 8的源代码引入项目,然后找到抛出异常的代码。代码如下:org.apache.catalina.connector.Request.doGetSession 中部分代码:

 if (!create) {
            return (null);
        }
        if (response != null
                && context.getServletContext()
                        .getEffectiveSessionTrackingModes()
                        .contains(SessionTrackingMode.COOKIE)
                && response.getResponse().isCommitted()) {
            throw new IllegalStateException(
                    sm.getString("coyoteRequest.sessionCreateCommitted"));
        }

找到了异常,这里涉及到,Spring MVC 框架的 HandlerInterceptor 拦截器的接口处理过程,显然 preHandle()方法是在Handle之前处理的,如果要是在这里调用  request.getSession()  应该是可以的,结果的确是这样处理的,当然也可以将写在 postHandle()的移动到 preHandle()中,我不清楚原来同仁为何这么写。

所以在  preHandle()中 加如下的代码:

 public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object obj) throws Exception {
        response.addHeader("Access-Control-Allow-Origin","*");
        HttpSession httpSession = request.getSession();
        return true;
    }

这样可以避免在 postHandle()中去创建 session 。BUG到这里已经处理成功了。

3. BUG原理与总结:

       这种BUG其实不容易发生,只有在请求的过程中 HandlerInterceptor  postHandle()之前一直没有创建 session 时才发生。BUG原理其实 Spring MVC 请求做异步处理引起的

还有就是处理这种不常见的BUG主要还是需要源码 DEBUG 进去看每一变量的情况,

还有就是用比较方法去排除怀疑,比如让前端去请求一个原来可以正常使用的API接口,如果成功了,说明是现在的后端服务接口有BUG了。

相关文章

学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习...
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面...
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生...
Can’t connect to local MySQL server through socket \'/v...
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 ...
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服...