背景
写了个拦截器,会拦截请求并获取请求参数来进行鉴权,由于鉴权方案:所有参数参与加密签名的生成
所以以流获取请求参数。
这导致流读取之后,后面无法再次读取。
原因及解决方案参考:https://blog.csdn.net/weixin_43848040/article/details/115483405
原因:
HttpServletRequest的输入流只能读取一次的原因
当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承与InputStream。
InputStream的read()方法内部有一个position,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()返回-1,表示已经读取完了
,如果想要重新读取,则需要调用reset()方法
,position就会移动到上次调用mark的位置,mark默认是0,所有就能重头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。
InputStream默认不实现reset(),并且markSupported()默认也是返回false
我们再来看看ServletInputStream,可以看到该类并没有重写mark(),reset()以及markSupported()方法
解决
我们可以把流读取出来后用容器存起来,后面就可以多次利用了
。JavaEE提供了一个HttpServletRequestWrapper类,它是一个http请求包装器,基于装饰者模式实现类HttpServletRequest界面。
继承HttpServletRequestWrapper,将请求体中的流copy一份
,可以重写getinputStream()和getReader()方法,或自定义方法供外部使用。
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class MAPIHttpServletRequestWrapper extends HttpServletRequestWrapper {
//存储body数据的容器
private final byte[] body;
public MAPIHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 将body数据存储起来
body = getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
/**
* 获取请求Body
*
* @param request request
* @return String
*/
public String getBodyString(final HttpServletRequest request) {
try {
return inputStream2String(request.getInputStream());
} catch (IOException e) {
log.error("WrappedHttpServletRequest-getBodyString报错",e);
throw new RuntimeException(e);
}
}
/**
* 将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, Charset.forName("UTF-8")));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
log.error("WrappedHttpServletRequest-inputStream2String报错1", e);
throw new RuntimeException(e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error("WrappedHttpServletRequest-inputStream2String报错2", e);
}
}
}
return sb.toString();
}
/**
* 将inputStream里的数据读取出来并转换成Map
*
* @param in
* @return Map<String,Object>
*/
public static Map<String, Object> getBodyMap(InputStream in) {
String param = null;
BufferedReader streamReader = null;
try {
streamReader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
StringBuilder responseStrBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
responseStrBuilder.append(inputStr);
}
JSONObject jsonObject = JSONObject.parseObject(responseStrBuilder.toString(), JSONObject.class);
if (jsonObject == null) {
return new HashMap<String,Object>();
}
param = jsonObject.toJSONString();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (streamReader != null){
try {
streamReader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return JSONObject.parseObject(param, Map.class);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
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) {
}
};
}
}
过滤器
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
// TODO 确认urlPatterns配置
@Slf4j
@WebFilter(urlPatterns = {"/*"})
@Order(1)
public class RequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("Filter Begin....");
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new MAPIHttpServletRequestWrapper((HttpServletRequest) request);
}
//判断是否需要包装
if(requestWrapper == null) {
// 直接放行请求
chain.doFilter(request, response);
} else {
// 构建RequestWrapper请求包装,
//然后chain.doFilter(requestWrapper, response);
//将包装后的请求传递给下一个Filter或Servlet。
chain.doFilter(requestWrapper, response);
}
UserHolder.removeUser();
log.info("Filter end....");
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
方法二:StreamUtils来实现流的copy
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
/**
*
* 由于一些签名、请求头预处理需要读一次流,会导致请求异常,所以需要包装一下。
*/
public class RepeatableRequestBodyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//getInputStream只能调用一次 解决方法,copy一份
filterChain.doFilter(new RepeatableRequestBodyWrapper(request), response);
}
public static class RepeatableRequestBodyWrapper extends HttpServletRequestWrapper {
/**
* 将流保存下来
*/
private byte[] requestBody;
public RepeatableRequestBodyWrapper(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(), StandardCharsets.UTF_8));
}
}
}