【java web】解决流读完一次就不能再次获取body数据的问题

问题来自我工作业务上的需求:前端请求时需要将json用RSA算法加密,数据经过后端过滤器进行自动解密,这样做的好处是以后不需要在每一个方法里都手动解密一次,增加代码的简洁性、可维护性。

但这样一来便会面临一个问题:http的request请求的输入流在过滤器中就已经被读取了(因为需要读取并解密request body 里被前端加密了的json数据),流只能被读取一次,这样一来数据便传不进controller里,导致接下来的业务无法进行。

于是我上网找了一些资料并成功解决了这个问题,基本思路是封装原生的HttpServletRequest请求,将其输入流里的数据保存在字节数组里,最后重写getInputStream方法,使其之后每次读取数据都是从字节数组里读取的。

第一步:写一个Request包装类BodyReaderHttpServletRequestWrapper

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {

    private byte[] body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = HttpHelper.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();
            }

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

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

            @Override
            public void setReadListener(ReadListener readListener) {

            }

        };
    }

    public void setInputStream(byte[] body){
        this.body = body;
    }

里面涉及一个HttpHelper类,顺便也贴出来

public class HttpHelper {

    /**
     * 获取请求Body
     * @param request
     * @return
     */
    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 (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
    
}

第二步:编写包装Filter

这个filter类可以将经过的原生Request请求自动包装成BodyReaderHttpServletRequestWrapper

/**
 * desc : 用于包装原生request, 解决流读完一次就不能再次获取body数据的问题
 * Created by Lon on 2018/3/9.
 */
public class RequestWrapperFilter implements Filter{

    private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapperFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @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(null == requestWrapper) {
            LOGGER.error("包装request失败!将返回原来的request");
            chain.doFilter(request, response);
        } else {
            LOGGER.info("包装request成功");
            chain.doFilter(requestWrapper, response);
        }

    }

    @Override
    public void destroy() {

    }
}

第三步:在web.xml上配置过滤器

  <filter>
    <filter-name>RequestWrapperFilter</filter-name>
    <filter-class>com.kx.security.filter.RequestWrapperFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>RequestWrapperFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

这里要注意的是这个包装过滤器可能要写在web.xml配置文件里某些过滤器的前面,比如解密过滤器,否则被解密过滤器先读取流的话,包装过滤器就读取不了流了。

至此一个简单的解决方法就完成啦


猜你喜欢

转载自blog.csdn.net/u013295276/article/details/79626286