Request的Body中的数据只能读取一次
读取数据
首先需要针对来自第三方的所有数据进行备份,在进行业务流程前,获取其入参并储存在数据库中。
这里采用了拦截器的方式,在业务执行前获取Request其中的入参。
public static JSONObject handlerData(HttpServletRequest request) throws IOException, JSONException {
StringBuffer sb = new StringBuffer();
InputStream is = request.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String s = "";
while ((s = br.readLine()) != null) {
sb.append(s);
}
if (sb.toString().length() <= 0) {
return null;
} else {
return JSONObject.parseObject(sb.toString());
}
}
重写Request
通过该方法可以通过IO流将request中的数据读取转化为JSONObject,如此就能对入参进行想要的处理了
然而实际运用中发现,经过拦截器后,到达Controller后通过@RequestBody转化而成的对象变成了空对象。
在查询了一些资料后,发现是由于默认的HttpServletRequest的对象仅能使用InputStream对象读取一次数据。
要解决这个问题需要对HttpServletRequest进行重写,重新包装一个可以重复读取的Request。
这里就要使用到HttpServletRequestWrapper这个类了,在继承此类后重写其中的getInputStream方法,并加入一个数据容器用以存储数据。代码如下所示
@Slf4j
public class ReReadRequestWrapper extends HttpServletRequestWrapper {
/**
* 存储body数据
*/
private final byte[] msgBody;
public ReReadRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 将body数据存储到容器中
String bodyStr = getBodyString(request);
msgBody = bodyStr.getBytes("UTF-8");
}
/**
* 获取请求Body
*
* @param request request
* @return String
*/
public String getBodyString(final ServletRequest request) {
try {
return inputStream2String(request.getInputStream());
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
}
}
/**
* 获取请求Body
*
* @return String
*/
public String getBodyString() {
final InputStream inputStream = new ByteArrayInputStream(msgBody);
return inputStream2String(inputStream);
}
/**
* 将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, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("", e);
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("", e);
}
}
}
return sb.toString();
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(msgBody);
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) {
}
};
}
}
注:在设定编码格式的时候记得使用UTF-8,网上很多方法都没有设定编码格式。在本地测试时不会发现问题,但是当项目部署到Linux系统的服务器上时,会因为编码格式的错误导致汉字变为乱码。
该类错误在项目中的很多地方也会碰到,比如:
- 使用MimeMessage发送邮件的时候,需要使用MimeUtility.encodeText来设置发件人和主题,否则部署到Linux上后邮件的发件人和主题会变成乱码。
- 使用AES加解密时在Windows上会生成相同的秘钥,在Linux下生成变化的秘钥,导致解密失败。需要使用SecureRandom.getInstance之后再使用一次secureRandom.setSeed才能生成固定的秘钥。
此类问题在本地测试中无法发现,需要部署至测试服务器之后才能发现问题。
替换Request
新建一个Filter接口的过滤器实现类,在其中新建Request的时候新建我们重写过的ReReadRequestWrapper对象
public class ReplaceStreamFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = new ReReadRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {
}
}
配置过滤器注册,在这里由于仅有第三方接口有重复读取Request接口的需求,所以路径仅配置了/thirdApi/
@Configuration
public class FilterConfig {
/**
* 注册过滤器
*
* @return FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean someFilterRegistration() {
System.out.println("覆盖request");
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(replaceStreamFilter());
registration.addUrlPatterns("/thirdApi/*");
registration.setName("replaceStreamFilter");
return registration;
}
/**
* 实例化StreamFilter
*
* @return Filter
*/
@Bean(name = "replaceStreamFilter")
public Filter replaceStreamFilter() {
return new ReplaceStreamFilter();
}
}
这样每次便可以解决Request无法重复读取的问题了