SpringBoot请求体中的流只能读取一次的问题HttpServletRequest的流只能读取一次的原因

问题场景:在项目开发过程中需要记录用户的操作行为,即用户请求的url和相关url中带有的请求体参数,在springboot中只能在拦截器中读取了一次,在controller获取不到参数。

经过代码排查,发现ServletInputStream的流只能读取一次,从而影响到controller层接受request内容。

解决方案:

1、声明BodyReaderHttpServletRequestWrapper类继承HttpServletRequestWrapp将请求体中的流copy一份,重写getInputStream()和getReader()方法供外部使用

package com.hffss.config;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.springframework.util.StreamUtils;

/**
 * 从请求体中获取参数请求包装类
 * @author: Mr.FangLei
 * @Date: 2022/4/14 15:31
 */
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] requestBody = null;//用于将流保存下来

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);

        return new ServletInputStream() {

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

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

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

2、自定义MyFilter继承Filter,在Filter中对request对象用包装后的request替换(可重复读取流的请求对象构造完成,但是需要在拦截器中获取,需要将包装后的请求对象放在拦截器中,由于Filter在interceptor之前执行,可以通过Filter进行实现)

package com.hffss.config;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * @author: Mr.FangLei
 * @Date: 2022/4/14 15:31
 */

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

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("开始");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
        }
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            System.out.println("------------------------------请求报文----------------------------------");
            System.out.println(getParamsFromRequestBody((HttpServletRequest) requestWrapper));
            System.out.println("------------------------------请求报文----------------------------------");
            chain.doFilter(requestWrapper, response);
        }
    }

    /* *
     * 获取请求体内容
     * @return
     * @throws IOException
     */
    private String getParamsFromRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader br = null;
        String listString = "";
        try {
            br = request.getReader();
            String str = "";
            while ((str = br.readLine()) != null) {
                listString += str;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return listString;
    }

    @Override
    public void destroy() {
        System.out.println("destroy");
    }
}

3、在拦截器中获取请求体

package com.hffss.interceptor;

import com.hffss.entity.RequestLog;
import com.hffss.thread.SaveRequestLogTask;
import com.hffss.utils.IpUtil;
import com.hffss.utils.RequestUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutorService;

@Slf4j
public class LogInterceptor implements HandlerInterceptor {

    // 请求日志实体标识
    private static final String LOGGER_ENTITY = "_logger_entity";

    @Resource(name = "taskExecutor")
    private ExecutorService taskExecutor;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String param = "";
        if (isJson(request)) {
            // param = new RequestUtils().getRequestJsonString(request);
            param = getParamsFromRequestBody(request);
        }
        RequestLog requestLog = new RequestLog();
        Integer userId = RequestUtils.getUserId(request);
        String uri = request.getRequestURI();
        requestLog.setUserId(userId);
        requestLog.setReqIp(IpUtil.getIpAddress(request));
        requestLog.setReqParam(param);
        requestLog.setReqUri(uri);
        requestLog.setReqTime(new Date());
        request.setAttribute(LOGGER_ENTITY, requestLog);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        RequestLog requestLog = (RequestLog) request.getAttribute(LOGGER_ENTITY);
        requestLog.setRespTime(new Date());
        requestLog.setTimeUsed(requestLog.getRespTime().getTime() - requestLog.getReqTime().getTime());
        taskExecutor.execute(new SaveRequestLogTask(requestLog));
    }

    private boolean isJson(HttpServletRequest request) {
        if (request.getContentType() != null) {
            return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
        }
        return false;
    }

    /**
     * 获取请求体内容
     * @return
     * @throws IOException
     */
    private String getParamsFromRequestBody(HttpServletRequest request) throws IOException {
        BufferedReader reader = request.getReader();
        StringBuilder builder = new StringBuilder();
        try {
            String line = null;
            while((line = reader.readLine()) != null) {
                builder.append(line);
            }
            return builder.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}

4、(重点)在Application启动类中需要注入Bean

package com.hffss;

import com.hffss.config.MyFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.util.unit.DataSize;
import org.springframework.web.client.RestTemplate;

import javax.servlet.MultipartConfigElement;
import java.time.Duration;

@SpringBootApplication
@EnableFeignClients
@EnableAsync
@EnableScheduling
@EnableJpaAuditing
@EnableDiscoveryClient
public class RiskCommonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RiskCommonApplication.class, args);
    }

    /**
     * 文件上传配置
     *
     * @return
     */
    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        // 文件最大
        factory.setMaxFileSize(DataSize.ofKilobytes(1024000)); // KB,MB
        // 设置总上传数据总大小
        factory.setMaxRequestSize(DataSize.ofKilobytes(10240000));
        return factory.createMultipartConfig();
    }

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.setConnectTimeout(Duration.ofMillis(600000)).setReadTimeout(Duration.ofMillis(600000)).build();
    }

    @Bean
    public FilterRegistrationBean<MyFilter> Filters() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<MyFilter>();
        registrationBean.setFilter(new MyFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setName("MyFilter");
        return registrationBean;
    }
}
FilterRegistrationBean方法中,registrationBean.setName("MyFilter");中的MyFilter要与自定义的MyFilter,注解@WebFilter(urlPatterns = "/*",filterName = "MyFilter")中的filterName保持一致否则不生效
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值