最近遇到一个很蛋疼的问题,机器发来http请求,信息都放在body Data里用gb2312编码,然后后台用@RequestBody来接受,这时问题来了,机器发来的请求没有设置content-type,于是默认就是content-type:application/x-www-form-urlencoded,然后spring容器就默认设置CharacterEncoding为utf-8,来解码。更奇怪的是,spring中途还对body使用urlencode。
比方说http body内容是“温度设定值”,最后接受到的是
%ef%bf%bd%c2%b6%ef%bf%bd%ef%bf%bd%e8%b6%a8%d6%b5,用URLDecode.decode(data,"gb2312")解码后是“锟铰讹拷锟借定值”,经过我多次试验,实际过程是这样的,
public static void main(String[] args) {
try {
String data = "温度设定值";
System.out.println(data);
data = new String(data.getBytes("gb2312"),"utf-8");
System.out.println(data);
data = URLEncoder.encode(data,"utf-8");
System.out.println(data);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
温度设定值
�¶��趨ֵ
%EF%BF%BD%C2%B6%EF%BF%BD%EF%BF%BD%E8%B6%A8%D6%B5
多次debug后终于发现了spring自作主张对内容进行urlencode,在ServletServerHttpRequest类的
getBodyFromServletRequestParameters方法中
/**
* Use {@link javax.servlet.ServletRequest#getParameterMap()} to reconstruct the
* body of a form 'POST' providing a predictable outcome as opposed to reading
* from the body, which can fail if any other code has used the ServletRequest
* to access a parameter, thus causing the input stream to be "consumed".
*/
private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
Map form = request.getParameterMap();
for (Iterator nameIterator = form.keySet().iterator(); nameIterator.hasNext();) {
String name = nameIterator.next();
List values = Arrays.asList(form.get(name));
for (Iterator valueIterator = values.iterator(); valueIterator.hasNext();) {
String value = valueIterator.next();
writer.write(URLEncoder.encode(name, FORM_CHARSET));
if (value != null) {
writer.write('=');
writer.write(URLEncoder.encode(value, FORM_CHARSET));
if (valueIterator.hasNext()) {
writer.write('&');
}
}
}
if (nameIterator.hasNext()) {
writer.append('&');
}
}
writer.flush();
return new ByteArrayInputStream(bos.toByteArray());
}
protected static final String FORM_CHARSET = "UTF-8";
FORM_CHARSET是静态常量,也就是说是固定的。spring为什么要将@RequestBody接受的数据进行urlencode编码我不得而知,总之我们知道了不管怎样最后都应该用UrlDecode.decode(data,"utf-8")进行解码,然后问题就变成了如何解决spring用utf-8默认解码的问题。
查了很多资料,也试验了很多,最快捷的方法自然是机器传来的信息加上消息头 content-type charset=GB2312,这样tomcat容器就会使用gb2312进行解码了。但是我的提议没有被接受。那么只有强制在这个消息路径里使用
request.setCharacterEncoding("gbk2312")了,但是servlet规定只有在调用request.getParameters()之前设置
characterEncoding才会生效,而spring容器早就不知道在之前做过了多少事情了。我试图在过滤器中设置request编码,但很可惜并不生效,说明spring在过滤器之前就调用了getParameters方法。
@WebFilter(filterName = "encodingFilter", urlPatterns = "/*")
public class MutiCharacterEncodingFilter extends OncePerRequestFilter
在这个问题上我花费了大量时间,几乎绝望了,网上的信息都是说要在getParameters之前设置request编码,这我已经充分了解了,但是你告诉我加在哪儿啊?怎么在spring做出处理之前设置编码。最绝望的就是,你知道解决问题的方法,但却不知道怎么实现。最后我幸运地找到了资料,很可惜并不是我自己独立完成的。
作者跟踪源码,发现CharacterEncodingFilter会调用request.setCharacterEncoding("UTF-8"),于是他写了一个类继承
CharacterEncodingFilter,并在Application中注入它
@Bean()
@ConfigurationProperties(prefix = "spring.http.encoding")
@ConfigurationPropertiesBinding
public MutiCharacterEncodingFilter mutiCharacterEncodingFilter(){
MutiCharacterEncodingFilter encodingFilter = new MutiCharacterEncodingFilter();
encodingFilter.setEncoding(charset);
encodingFilter.setForceRequestEncoding(forceRequest);
encodingFilter.setForceResponseEncoding(forceResponse);
return encodingFilter;
}
这样自定义的过滤器就会取代CharacterEncodingFilter。request.setCharacterEncoding也就生效了。
这让我对springboot的了解更加得深入,之前都是copy别人的代码,发现有很多用@Bean的方式配置变量,一直只知其然不知其所以然。看来springboot会把@Bean标注的变量替换掉他默认的变量,只要这个变量继承了那个默认变量。而我用
@WebFilter加入的过滤器只是加在这些默认过滤器的后面,而不是替换这些默认过滤器。
至此,乱码问题终于解决