在自定义filter中使用自定义的HttpServletRequestWrapper后,出现中文乱码
1、项目使用的环境
tomcat 8.5.34
Springboot 1.5.17
2、使用背景和问题描述
在自定义的过滤器Myfilter中,需要从request body 中获取参数,因此使用自定义的类MyHttpServletRequestWrapper继承HttpServletRequestWrapper来封装一下request,以确保Myfilter之后的处理中能从request中获取到请求参数。但是发现封装了request之后,从request中获取到的中文乱码了。以下是相关代码。
Myfilter
@Order(Integer.MIN_VALUE)
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = servletRequest;
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (!isFormDataRequest(httpServletRequest)) {
requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
}
// 从request body 中获取参数(省略)
}
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {}
private boolean isFormDataRequest(HttpServletRequest httpServletRequest) {
String contentType = httpServletRequest.getContentType();
return Objects.nonNull(contentType) && (contentType.contains("application/x-www-form-urlencoded")
|| contentType.contains("multipart/form-data")) && "POST".equalsIgnoreCase(httpServletRequest.getMethod());
}
}
MyHttpServletRequestWrapper
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = StreamUtil.readBytes(request.getReader(), "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 arg0) {}
};
}
RequestUtils
@Slf4j
public class RequestUtils {
/**
* 获取请求体
*/
public static String getRequestBody(HttpServletRequest request) {
String body = null;
try {
body = StreamUtils.copyToString(request.getInputStream(),Charset.forName("utf-8"));
} catch (Exception e) {
log.error("RequestUtils getRequestBody has an error:{}", e);
}
return body;
}
public static Object getRequestParam(String body, String param) {
if (StringUtils.isBlank(body)) {
return null;
}
Object obj = null;
try {
JSONObject jsonObject = JSON.parseObject(body);
obj = jsonObject.get(param);
} catch (Exception e) {
log.info("RequestUtils getRequestParam has an error.body:{}.error:{}", body, e);
}
return obj;
}
}
3、原因分析
查看源码可知,springboot 有个默认的编码过滤器(OrderedCharacterEncodingFilter)被设置为最先执行,但是由于Myfilter的order也设置为了最小值,导致其执行时间早于OrderedCharacterEncodingFilter。
因为目前的浏览器通常不设置字符编码,即使在HTML页面或表单中指定了字符编码,因此需要OrderedCharacterEncodingFilter为请求指定字符编码。
同时,如果请求尚未指定编码,则此过滤器可以应用其设置的编码,或者在任何情况下强制使用此过滤器设置的编码(当forceEncoding =“true”时(此为默认值))。在后一种情况下,该编码也将作为默认的响应编码。
而且http协议默认的编码是iso-8859-1,因此直接使用 utf-8 去读取数据将导致中文乱码。
OrderedCharacterEncodingFilter源码如下:
public class OrderedCharacterEncodingFilter extends CharacterEncodingFilter implements Ordered {
private int order = -2147483648;
public OrderedCharacterEncodingFilter() {
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
CharacterEncodingFilter部分源码如下
public class CharacterEncodingFilter extends OncePerRequestFilter {
private String encoding;
private boolean forceRequestEncoding;
private boolean forceResponseEncoding;
============= 省略 ===============
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String encoding = this.getEncoding();
if(encoding != null) {
if(this.isForceRequestEncoding() || request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding);
}
if(this.isForceResponseEncoding()) {
response.setCharacterEncoding(encoding);
}
}
filterChain.doFilter(request, response);
}
}
4、解决办法
(1)将Myfilter的执行顺序设置在OrderedCharacterEncodingFilter之后执行,如设置 @Order(Integer.MIN_VALUE +1) 或 @Order(-1)
(2)设置Myfilter中request的编码,具体如下
@Order(Integer.MIN_VALUE)
public class MyFilter implements Filter {
private final HttpEncodingProperties properties;
public MyFilter(HttpEncodingProperties properties) {
this.properties = properties;
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain) throws IOException, ServletException {
servletRequest.setCharacterEncoding(properties.getCharset().name());
ServletRequest requestWrapper = servletRequest;
if (servletRequest instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
if (!isFormDataRequest(httpServletRequest)) {
requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
}
// 从request body 中获取参数(省略)
}
chain.doFilter(requestWrapper, response);
}
@Override
public void destroy() {}
private boolean isFormDataRequest(HttpServletRequest httpServletRequest) {
String contentType = httpServletRequest.getContentType();
return Objects.nonNull(contentType) && (contentType.contains("application/x-www-form-urlencoded")
|| contentType.contains("multipart/form-data")) && "POST".equalsIgnoreCase(httpServletRequest.getMethod());
}
}