java web请求乱码问题
1.get请求路径参数内的中文
发起请求 /test?name=黄
1.因为有汉字,浏览器会将其编码为Url编码,使用哪种字符集浏览器决定,但是多数应该都是utf-8
2.tomcat解析此处的参数为name=%E9%BB%84
的形式
3.代码调用request.getParameterMap()
此时会进行Url解码
4.tomcat将name=%E9%BB%84
解码,使用utf-8字符集,此字符集是硬编码在代码里的。
请查看类org.apache.tomcat.util.http.Parameters
类,此类有个字段queryStringCharset
默认就是utf-8,默认情况下使用utf-8解析路径上的参数。
2.post请求,x-www-form-urlencoded
此类型的请求体的参数是由tomcat解析的。
1.如果调用过request.setCharacterEncoding()
,设置过字符集,则使用设置的。
2.为空的话,尝试从content-type
里获取
3.为空的话,尝试从Context
上获取
4.为空的话,返回默认iso8859-1
tomcat内获取字符集源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| private Charset getCharset() { Charset charset = null; try { charset = coyoteRequest.getCharset(); } catch (UnsupportedEncodingException e) { } if (charset != null) { return charset; }
Context context = getContext(); if (context != null) { String encoding = context.getRequestCharacterEncoding(); if (encoding != null) { try { return B2CConverter.getCharset(encoding); } catch (UnsupportedEncodingException e) { } } }
return org.apache.coyote.Constants.DEFAULT_BODY_CHARSET; }
public Charset getCharset() throws UnsupportedEncodingException { if (charset == null) { getCharacterEncoding(); if (characterEncoding != null) { charset = B2CConverter.getCharset(characterEncoding); } }
return charset; } public String getCharacterEncoding() { if (characterEncoding == null) { characterEncoding = getCharsetFromContentType(getContentType()); }
return characterEncoding; }
|
3.post请求,form-data
获取请求体内参数的过程和x-www-form-urlencoded
是一样的,
只是解析的方式不一样,但获取字符集过程是一样的。
4.post请求体内的中文,application/json
Tomcat
不会处理此类型的请求体,会当成inputstream
的形式放入request
里。
Controller
内使用@RequestBody
接收参数,参数类型为String
。
Springmvc
会帮我们将它映射到参数上,根据参数类型不同,映射方法也不同,如果是Pojo则会使用配置的MappingJackson2HttpMessageConverter
或者FastJsonHttpMessageConverter
将流转成Pojo,现在我们讨论转成字符串的形式。
处理参数映射时,从多个MessageConverter
中选择一个能处理的Converter
,因为参数是字符串,这里选择到的是StringMessageConverter
。
在下面代码16行处,调用了inputMessage.getHeaders()
,这里会解析出所有的请求头,且会将字符集设置到content-type
上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters()
@Nullable protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType; boolean noContentType = false; try {
contentType = inputMessage.getHeaders().getContentType(); } catch (InvalidMediaTypeException ex) { throw new HttpMediaTypeNotSupportedException(ex.getMessage()); }
Class<?> contextClass = parameter.getContainingClass(); Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null); Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message; try { message = new EmptyBodyCheckingHttpInputMessage(inputMessage); for (HttpMessageConverter<?> converter : this.messageConverters) { Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass(); GenericHttpMessageConverter<?> genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) : (targetClass != null && converter.canRead(targetClass, contentType))) { if (message.hasBody()) { HttpInputMessage msgToUse = getAdvice().beforeBodyRead(message, parameter, targetType, converterType); body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse)); body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType); } else { body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType); } break; } } } catch (IOException ex) {
}
return body; }
|
这里是获取Heads
过程,注释处将获取servletRequest.getCharacterEncoding()
拼接在content-type
上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| @Override public HttpHeaders getHeaders() { if (this.headers == null) { this.headers = new HttpHeaders(); for (Enumeration<?> names = this.servletRequest.getHeaderNames(); names.hasMoreElements();) { String headerName = (String) names.nextElement(); for (Enumeration<?> headerValues = this.servletRequest.getHeaders(headerName); headerValues.hasMoreElements();) { String headerValue = (String) headerValues.nextElement(); this.headers.add(headerName, headerValue); } }
try { if (contentType != null && contentType.getCharset() == null) { String requestEncoding = this.servletRequest.getCharacterEncoding(); if (StringUtils.hasLength(requestEncoding)) { Charset charSet = Charset.forName(requestEncoding); Map<String, String> params = new LinkedCaseInsensitiveMap<>(); params.putAll(contentType.getParameters()); params.put("charset", charSet.toString()); MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params); this.headers.setContentType(mediaType); } } } catch (InvalidMediaTypeException ex) { }
if (this.headers.getContentLength() < 0) { int requestContentLength = this.servletRequest.getContentLength(); if (requestContentLength != -1) { this.headers.setContentLength(requestContentLength); } } }
return this.headers; }
|
在StringmessageConverter
读取数据时,会先从content-type
中获取,再使用默认的,这里的content-type
已经在上面被改写了。所以默认的字符集只有在没有主动调用request.setCharEncoding
,也无法从content-type
上获取到字符集时才会使用。
1 2 3 4 5
| @Override protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException { Charset charset = getContentTypeCharset(inputMessage.getHeaders().getContentType()); return StreamUtils.copyToString(inputMessage.getBody(), charset); }
|
5.总结
1
对于get请求路径上的中文字符,默认是utf-8就可以,一般不会有问题。
2
对于post请求中的form-data
和x-www-form-urlencoded
,参数是由Tomcat解析的,对字符集的优先级是:
request.getEncoding()
> Content-Type上的
> context.getRequestCharacterEncoding()
> 默认的iso8859-1
3
对于post请求是application/json
的,解析操作由Springmvc
处理,优先级为:
Content-Type上的
> request.getEncoding()
> StringMessageConverter指定的
4
对Request
设置字符集需要在第一次获取参数前设置才可以,所以可以添加一个Filter
在最前面,将指定的字符集设置进去。Spring
为我们提供了一个CharacterEncodingFilter
来做这样的事。