java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java request流获取一次

解决Java项目中request流只能获取一次的问题

作者:嗑嗑嗑瓜子的猫

Java项目开发中可能存在以下几种情况,你需要在拦截器中统一拦截请求和你项目里可能需要搞一个统一的异常处理器,这两种情况是比较常见的,本文将给大家介绍如何解决Java项目中request流只能获取一次的问题,需要的朋友可以参考下

问题描述

Java项目开发中可能存在以下几种情况:

1、你需要在拦截器中统一拦截请求,拿到请求中的参数,来做统一的判断处理或者其他操作。

那问题就来了,由于request输入流的数据只能读取一次,所以你在拦截器中你读取了输入流的数据,当请求进入后边的Controller时,输入流中已经没有数据了,导致获取不到数据。

2、你项目里可能需要搞一个统一的异常处理器,然后想在异常处理器中把发生异常的接口地址,方法名,以及请求的参数记录到日志里或者直接发送给你配置的告警系统,比如发送给钉钉群通知。这种情况下,因为你前边controller已经获取过一次request输入流了,在后边的异常处理器里你还想再从request输入流中拿到请求参数等信息,所以也会出现request流只能读取一次的错误。

以上两种情况是开发中比较常见的,当然除此之外,别的场景下你可能也会遇到request流只能读取一次的错误,所以今天就来讲一下如果遇到这种情况该怎么解决。

产生原因

1、一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;

2、InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

解决方法

一、引入依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.7</version>
</dependency>

二、自定义Wrapper类来包装HttpServletRequest

我们需要写一个自定义包装类,并继承HttpServletRequestWrapper

import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
/**
 * @描述 包装HttpServletRequest
 *          MyServletRequestWrapper + RequestReplaceFilter 的作用是:
 *              解决异常处理器中拿post请求的json参数时,报request流只能读一次的错
 *              原因是 request.getReader() 和 request.getInputStream() 都是只能调用一次
 *              所以我这里要使用 HttpServletRequestWrapper 来实现自定义的 MyServletRequestWrapper包装类
 *              把request里的 body 保存在 MyServletRequestWrapper中, 并且重写 getInputStream()方法
 *              然后所有的request都在RequestReplaceFilter中被转换成了我自定义的HttpServletRequestWrapper
 *              然后获取 body时就都是调用 MyServletRequestWrapper中的 getBody()方法了
 * @创建人 caoju
 */
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
 
    private final byte[] body;
 
    public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = IOUtils.toByteArray(super.getInputStream());
    }
 
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
 
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new RequestBodyCachingInputStream(body);
    }
 
    private class RequestBodyCachingInputStream extends ServletInputStream {
        private byte[] body;
        private int lastIndexRetrieved = -1;
        private ReadListener listener;
 
        public RequestBodyCachingInputStream(byte[] body) {
            this.body = body;
        }
 
        @Override
        public int read() throws IOException {
            if (isFinished()) {
                return -1;
            }
            int i = body[lastIndexRetrieved + 1];
            lastIndexRetrieved++;
            if (isFinished() && listener != null) {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                    throw e;
                }
            }
            return i;
        }
 
        @Override
        public boolean isFinished() {
            return lastIndexRetrieved == body.length - 1;
        }
 
        @Override
        public boolean isReady() {
            return isFinished();
        }
 
        @Override
        public void setReadListener(ReadListener listener) {
            if (listener == null) {
                throw new IllegalArgumentException("listener cann not be null");
            }
            if (this.listener != null) {
                throw new IllegalArgumentException("listener has been set");
            }
            this.listener = listener;
            if (!isFinished()) {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                }
            } else {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                }
            }
        }
 
        @Override
        public int available() throws IOException {
            return body.length - lastIndexRetrieved - 1;
        }
 
        @Override
        public void close() throws IOException {
            lastIndexRetrieved = body.length - 1;
            body = null;
        }
    }
}

三、创建过滤器,通过过滤器包装原有的request对象

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @描述
 * @创建人 caoju
 */
@Component
public class RequestReplaceFilter extends OncePerRequestFilter {
 
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        if (!(request instanceof MyServletRequestWrapper)) {
            request = new MyServletRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
    
        /*//如果有文件上传的业务场景,需要用下面的代码进行处理,不然文件上传的流会有问题
        String contentType = request.getContentType();
        //如果contentType是空
        //或者contentType是多媒体的上传类型则忽略,不进行包装,直接return
        if (contentType == null) {
            filterChain.doFilter(request, response);
            return;
        }else if(request.getContentType().startsWith("multipart/")){
            filterChain.doFilter(request, response);
            return;
        }else if (!(request instanceof MyServletRequestWrapper)) {
            request = new MyServletRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
        */
    }
}

通过以上几步,我们就实现了把request里的 body 保存在 MyServletRequestWrapper中的效果

就可以在整个请求链路中任何地方去重复的获取request流了

四、使用案例

配置好之后,就可以在整个请求链路中任何地方去重复的获取request流了。

比如,你可以在请求刚进来时,在过滤器或者拦截器里拿到request对象,再拿到request对象的流数据,去做一些事情,或者你也可以在请求即将结束时,在统一的异常处理器中拿到request对象,拿到request对象流数据里请求的json参数;等等等等,还有其他很多你想使用的场景,都可以这么做。

下面是在代码中利用RequestContextHolder获取request对象,拿到request对象后就可以获取请求方式、请求url、以及请求参数这些数据了。如果你在某些地方也有需要打印记录请求方式、请求url、请求参数的这些需求,那可以直接复制粘贴我下边的代码就ok了

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        String mode = "";
        String methodUrl = "";
        String param = "";
        if (requestAttributes != null) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            //请求方式
            mode = request.getMethod();
            //方法URL
            methodUrl = request.getRequestURI();
            if(mode.equals(HttpMethod.GET.name())){
                param = request.getQueryString();
            }
            if(mode.equals(HttpMethod.POST.name())){
                param = getJsonRequest(request);
            }
        }
    /**
     * 获取Request中的JSON字符串
     * @param request
     * @return
     * @throws IOException
     */
    public static String getJsonRequest(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader();) {
            char[] buff = new char[1024];
            int len;
            while ((len = reader.read(buff)) != -1) {
                sb.append(buff, 0, len);
            }
        } catch (IOException e) {
            log.error("POST请求参数获取异常", e);
        }
        return sb.toString();
    }

ok,到这里解决request流只能获取一次的问题就搞定了

希望对你有所帮助

以上就是解决Java项目中request流只能获取一次的问题的详细内容,更多关于Java request流获取一次的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文