最近在项目开发中,需要记录请求日志,刚开始ContentType的类型都是application/x-www-form-urlencoded这种,所以我们在获取请求记录时,可以通过如下方式获取
Enumeration<?> temp = request.getParameterNames();
String params_json = "";
if (null != temp) {
while (temp.hasMoreElements()) {
String en = (String) temp.nextElement();
String value = request.getParameter(en);
res.put(en, value);
//如果字段的值为空,判断若值为空,则删除这个字段>
if (null == res.get(en) || "".equals(res.get(en))) {
res.remove(en);
}
}
params_json = gson.toJson(res);
}
但是当类型为application/json时,发现使用request.getParameterNames()这种方式拿不到数据了。是因为application/json这种我们都是通过@RequestBody来接收的
而 @RequestBody是通过读流方式来获取的(这个是重点),那么我们就需要通过流的方式来获取参数,于是写了如下代码获取
/**
* 流转为json字符串
* @param req
* @return
*/
private String getRequestPayload(HttpServletRequest req) {
StringBuilder sb = new StringBuilder();
try{
req.setCharacterEncoding("UTF-8");
BufferedReader reader=null;
reader = new BufferedReader(new InputStreamReader(req.getInputStream(),"UTF-8"));
String line=null;
while((line = reader.readLine())!=null){
sb.append(line);
}
}catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
但是一调用,直接报错了,提示类似错误:java.lang.IllegalStateException: getReader() has already been called for this request
这个错误就是说:getInputStream()和getReader()只能调用一次,前面我已经说了@RequestBody已经读了一次流了,所以在读就会报错。
那么如何解决:我们需要继承HttpServletRequestWrapper来重写getInputStream和getReader方法
package com.toutiao_efficiency.common.requestWrapper;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.StreamUtils;
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;
/**
* @author 徐长城
* @Date 2020/11/17 17:29
* @Description : 解决getInputStream()和getReader()只能调用一次
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;//用于将流保存下来
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
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) {
}
};
}
@Override
public BufferedReader getReader() throws IOException{
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
写好了以后,我们需要在filter中使用它,如图:
判断当类型为application/json,就行包装转换,这样就可以解决不能多次读流问题了