前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何重复读取HttpServletRequest的HTTP请求体数据

如何重复读取HttpServletRequest的HTTP请求体数据

原创
作者头像
leaforbook
发布2019-03-05 15:27:33
5.8K0
发布2019-03-05 15:27:33
举报
文章被收录于专栏:Spring CloudSpring Cloud

在开发Java web项目的时候,经常会用到Spring MVC的注解@RequestBody,用于读取HTTP请求体。有时候又要在业务代码里面读取HTTP请求体。有时候又需要一些拦截器或过滤器,比如,根据请求体中的数据,判断该用户有没有权限处理该数据,这时候拦截器也需要读取HTTP请求体。如果你同时遇到这些场景,你就会发现会报错。什么原因呢?因为所有读取HTTP请求体的操作,最终都要调用HttpServletRequest的getInputStream()方法和getReader()方法,而这两个方法总共只能被调用一次,第二次调用就会报错,原因是数据是从网络字节流里面读取的,字节流被读了一次之后,就没有数据了。那么如何重复读取HttpServletRequest携带的HTTP请求体数据呢?

其实思路很简单:第一步,读取HttpServletRequest的字节流的数据,保存到一个字节数组bytes;第二步,重写getInputStream()方法和getReader()方法,让这两个方法都从字节数组bytes中读取数据,返回给调用者;第三步,写个过滤器,让HTTP请求一进入系统,就执行第一步和第二步,然后后面都用重写的HttpServletRequest对象。这样,就可以重复读取HttpServletRequest携带的HTTP请求体数据了。


本文代码案例都是基于Servlet3.0写的,之前的版本和之后的版本实现方法都有可能不同。

1.读取字节流数据到字节数组

写一个HttpServletRequestWrapper的扩展类RepeatableHttpServletRequest,然后在构造方法中把字节流中的数据保存到字节数组中。

代码语言:txt
复制
public class RepeatableHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] bytes;
    public RepeatableHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        bytes = IOUtils.toByteArray(request.getInputStream());
    }
}

2.重写getInputStream()方法和getReader()方法

重写getInputStream()方法和getReader()方法,让这两个方法都从字节数组bytes中读取数据。这样RepeatableHttpServletRequest就是一个可重复读取的HttpServletRequest了。

代码语言:txt
复制
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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class RepeatableHttpServletRequest extends HttpServletRequestWrapper {
    private final byte[] bytes;
    public RepeatableHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        bytes = IOUtils.toByteArray(request.getInputStream());
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new ServletInputStream() {
            private int lastIndexRetrieved = -1;
            private ReadListener readListener = null;
            @Override
            public boolean isFinished() {
                return (lastIndexRetrieved == bytes.length-1);
            }
            @Override
            public boolean isReady() {
                // This implementation will never block
                // We also never need to call the readListener from this method, as this method will never return false
                return isFinished();
            }
            @Override
            public void setReadListener(ReadListener readListener) {
                this.readListener = readListener;
                if (!isFinished()) {
                    try {
                        readListener.onDataAvailable();
                    } catch (IOException e) {
                        readListener.onError(e);
                    }
                } else {
                    try {
                        readListener.onAllDataRead();
                    } catch (IOException e) {
                        readListener.onError(e);
                    }
                }
            }
            @Override
            public int read() throws IOException {
                int i;
                if (!isFinished()) {
                    i = bytes[lastIndexRetrieved+1];
                    lastIndexRetrieved++;
                    if (isFinished() && (readListener != null)) {
                        try {
                            readListener.onAllDataRead();
                        } catch (IOException ex) {
                            readListener.onError(ex);
                            throw ex;
                        }
                    }
                    return i;
                } else {
                    return -1;
                }
            }
        };
    }
    @Override
    public BufferedReader getReader() throws IOException {
        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
        BufferedReader temp = new BufferedReader(new InputStreamReader(is));
        return temp;
    }
}

3.写过滤器,把HttpServletRequest替换成RepeatableHttpServletRequest

过滤器很简单,就是把HttpServletRequest包装成RepeatableHttpServletRequest,然后丢给后面的调用链。

代码语言:txt
复制
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class RepeatableFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = new RepeatableHttpServletRequest((HttpServletRequest)servletRequest);
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }

}

然后利用过滤器配置,把RepeatableFilter设置成第一个调用的过滤器。注意FilterRegistrationBean的setOrder()方法,数值越小越先执行。

代码语言:txt
复制
package com.leaforbook.common.configuration;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;

@Configuration
public class FilterRegistrationConfiguration {
    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(repeatableFilter());
        registration.addUrlPatterns("/*");
        registration.setName("repeatableFilter");
        registration.setOrder(1);
        return registration;
    }

    @Bean
    public Filter repeatableFilter() {
        return new repeatableFilter();
    }
}

到现在为止,HttpServletRequest就可以随意地读取多次了,要实现一些无业务侵入的功能也容易多了。至于这些功能怎么实现,超出本文范畴,不做讨论。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.读取字节流数据到字节数组
  • 2.重写getInputStream()方法和getReader()方法
  • 3.写过滤器,把HttpServletRequest替换成RepeatableHttpServletRequest
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档