解决Request请求中流不能读两次的问题

背景

写了个拦截器,会拦截请求并获取请求参数来进行鉴权,由于鉴权方案:所有参数参与加密签名的生成
所以以流获取请求参数。
这导致流读取之后,后面无法再次读取。

原因及解决方案参考:https://blog.csdn.net/weixin_43848040/article/details/115483405

原因:

HttpServletRequest的输入流只能读取一次的原因
当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承与InputStream。

InputStream的read()方法内部有一个position,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()返回-1,表示已经读取完了如果想要重新读取,则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所有就能重头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。

InputStream默认不实现reset(),并且markSupported()默认也是返回false

我们再来看看ServletInputStream,可以看到该类并没有重写mark(),reset()以及markSupported()方法

解决

我们可以把流读取出来后用容器存起来,后面就可以多次利用了。JavaEE提供了一个HttpServletRequestWrapper类,它是一个http请求包装器,基于装饰者模式实现类HttpServletRequest界面。

继承HttpServletRequestWrapper,将请求体中的流copy一份,可以重写getinputStream()和getReader()方法,或自定义方法供外部使用。


import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;


@Slf4j
public class MAPIHttpServletRequestWrapper extends HttpServletRequestWrapper {
    //存储body数据的容器
    private final byte[] body;

    public MAPIHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 将body数据存储起来
        body = getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }


    /**
     * 获取请求Body
     *
     * @param request request
     * @return String
     */
    public String getBodyString(final HttpServletRequest request) {
        try {
            return inputStream2String(request.getInputStream());
        } catch (IOException e) {
            log.error("WrappedHttpServletRequest-getBodyString报错",e);
            throw new RuntimeException(e);
        }
    }
    /**
     * 将inputStream里的数据读取出来并转换成字符串
     *
     * @param inputStream inputStream
     * @return String
     */
    private String inputStream2String(InputStream inputStream) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            log.error("WrappedHttpServletRequest-inputStream2String报错1", e);
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("WrappedHttpServletRequest-inputStream2String报错2", e);
                }
            }
        }

        return sb.toString();
    }

    /**
     * 将inputStream里的数据读取出来并转换成Map
     *
     * @param in
     * @return Map<String,Object>
     */
    public static Map<String, Object> getBodyMap(InputStream in) {
        String param = null;
        BufferedReader streamReader = null;
        try {
            streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
            StringBuilder responseStrBuilder = new StringBuilder();
            String inputStr;
            while ((inputStr = streamReader.readLine()) != null) {
                responseStrBuilder.append(inputStr);
            }
            JSONObject jsonObject = JSONObject.parseObject(responseStrBuilder.toString(), JSONObject.class);
            if (jsonObject == null) {
                return new HashMap<String,Object>();
            }
            param = jsonObject.toJSONString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (streamReader != null){
                try {
                    streamReader.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return JSONObject.parseObject(param, Map.class);
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);

        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
}

过滤器

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;

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


// TODO 确认urlPatterns配置
@Slf4j
@WebFilter(urlPatterns = {"/*"})
@Order(1)
public class RequestFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        log.info("Filter Begin....");
        ServletRequest requestWrapper = null;
        if(request instanceof HttpServletRequest) {
            requestWrapper = new MAPIHttpServletRequestWrapper((HttpServletRequest) request);
        }
        //判断是否需要包装
        if(requestWrapper == null) {
            // 直接放行请求
            chain.doFilter(request, response);
        } else {
            // 构建RequestWrapper请求包装,
            //然后chain.doFilter(requestWrapper, response);
            //将包装后的请求传递给下一个Filter或Servlet。
            chain.doFilter(requestWrapper, response);
        }
        UserHolder.removeUser();
        log.info("Filter end....");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }

}

方法二:StreamUtils来实现流的copy


import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

/**
 * 
 * 由于一些签名、请求头预处理需要读一次流,会导致请求异常,所以需要包装一下。
 */
public class RepeatableRequestBodyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //getInputStream只能调用一次 解决方法,copy一份
        filterChain.doFilter(new RepeatableRequestBodyWrapper(request), response);
    }

    public static class RepeatableRequestBodyWrapper extends HttpServletRequestWrapper {
        /**
         * 将流保存下来
         */
        private byte[] requestBody;

        public RepeatableRequestBodyWrapper(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(), StandardCharsets.UTF_8));
        }
    }
}
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值