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

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

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

遇到问题: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 异常日志输出 -->
	<appender name="pay_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
		<!--文件路径默认catalina同级目录-->
		<file>logs/paylog.log</file>
		<Append>false</Append>
		<encoder charset="UTF-8">
			<!--%X{memberId} memberId是在MDC中put进去的数据 如未put任何数据则显示空格-->
			<pattern>%d - %X{memberId} - %X{requestId} - %X{requestBody} - %X{requestUrl} - %msg%n</pattern>
			<!--日志缓存,默认值8192 但appender异常或者系统崩溃时可能导致日志丢失,异常日志不建议使用-->
			<!--<immediateFlush>false</immediateFlush>-->
		</encoder>
		<!--定义日志级别-->
		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
			<level>ERROR</level>
		</filter>
		<!--按天生成日志-->
		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
			<fileNamePattern>logs/paylog.log.%d{yyyy-MM-dd}-%i</fileNamePattern>
			<!--日志保留最长天数-->
			<maxHistory>60</maxHistory>
			<!--当天日志超过10MB则生成新的日志日志格式为paylog.2018-11-02-0.log 该属性不能删掉-->
			<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
				<MaxFileSize>1024MB</MaxFileSize>
			</timeBasedFileNamingAndTriggeringPolicy>
		</rollingPolicy>
	</appender>

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值