在SpringMVC框架下HttpServletRequest inputStream只能读取一次的问题

场景描述:在异常日中打印请求url和请求传入的参数

采用的方式:使用slf4j+logback管理日志,在进入controller前将参数放入MDC中,在logba.xml中使用%d  - %X{requestBody} - %X{requestUrl} - %msg%n输入日志

遇到问题:1、在获取body需采用获取requestinputStream的方式,但是流被读取后,不会进入对应的controller中,提示stream is closed。

2、通过在过滤器filter中,封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取。出现request.getParameter()无法获取参数。

解决方法:

1、增加过滤器filter,获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新request对象中

import org.apache.commons.io.IOUtils;
import org.slf4j.MDC;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;

/*@WebFilter(filterName="Servlet3Filter",urlPatterns="/*")*/
public class FilterConfig implements Filter{

        @Override
        public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            MDC.clear();
            ServletRequest requestWrapper = null;
            //获取请求中的流,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
            if(request instanceof HttpServletRequest) {
                requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
            }

            // 在chain.doFiler方法中传递新的request对象
            if(requestWrapper == null) {
                chain.doFilter(request, response);
            } else {
                Writer writer = null;
                IOUtils.copy(request.getInputStream(),writer,"utf-8");
                chain.doFilter(requestWrapper, response);
            }
        }

        @Override
        public void destroy() {

        }

}

2、RequestReaderHttpServletRequestWrapper代码如下:

import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;


public class RequestReaderHttpServletRequestWrapper extends ContentCachingRequestWrapper {

    private final byte[] body;

    /**
     * 将body取出存储起来然后再放回去,但是在request.getParameter()时数据就会丢失
     * 调用getParameterMap(),目的将参数Map从body中取出,这样后续的任何request.getParamter()都会有值
     * @param request request
     * @throws IOException io异常
     */
    public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        request.getParameterMap();//此处将body中的parameter取出来,,这样后续的任何request.getParamter()都会有值
        body = LogUtils.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
}

3、在进入controller前,将请求参数放入MDC中

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if(StringUtils.isBlank(getBodyString(request))){
    MDC.put("requestBody", "requestBody:" + JsonUtils.toJson(request.getParameterMap()));
}else{
    MDC.put("requestBody", "requestBody:" + getBodyString(request));
}
}

/**
 * 获取请求Body
 * @param request 过滤后被的request
 * @return 返回body
 */
public static String getBodyString(ServletRequest request) {
    StringBuilder sb = new StringBuilder();
    InputStream inputStream = null;
    BufferedReader reader = null;
    try {
        inputStream = request.getInputStream();
        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
        String line = "";
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
    } catch (Exception e) {
        throw new BusinessException("获取requestBody出错:" + e.getMessage());
    } finally {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
            if (reader != null) {
                reader.close();
            }
        } catch (IOException ignored) {
        }
    }
    return sb.toString();
}

4、最后在logbac.xml中增加输出

!-- ch.qos.logback.core.rolling.RollingFileAppender 异常日志输出 -->
	
		
		logs/paylog.log
		false
		
			
			%d - %X{memberId} - %X{requestId} - %X{requestBody} - %X{requestUrl} - %msg%n
			
			
		
		
		
			ERROR
		
		
		
			logs/paylog.log.%d{yyyy-MM-dd}-%i
			
			60
			
			
				1024MB
			
		
	

 

 

你可能感兴趣的:(在SpringMVC框架下HttpServletRequest inputStream只能读取一次的问题)