在开发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写的,之前的版本和之后的版本实现方法都有可能不同。
写一个HttpServletRequestWrapper的扩展类RepeatableHttpServletRequest,然后在构造方法中把字节流中的数据保存到字节数组中。
public class RepeatableHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] bytes;
public RepeatableHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
bytes = IOUtils.toByteArray(request.getInputStream());
}
}
重写getInputStream()方法和getReader()方法,让这两个方法都从字节数组bytes中读取数据。这样RepeatableHttpServletRequest就是一个可重复读取的HttpServletRequest了。
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;
}
}
过滤器很简单,就是把HttpServletRequest包装成RepeatableHttpServletRequest,然后丢给后面的调用链。
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()方法,数值越小越先执行。
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 删除。