java web请求乱码问题

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 {
//如果设置过的返回设置的,否则尝试从content-type里获取
charset = coyoteRequest.getCharset();
} catch (UnsupportedEncodingException e) {
}
if (charset != null) {
return charset;
}
//从context里获取
Context context = getContext();
if (context != null) {
String encoding = context.getRequestCharacterEncoding();
if (encoding != null) {
try {
return B2CConverter.getCharset(encoding);
} catch (UnsupportedEncodingException e) {
// Ignore
}
}
}
//返回默认iso8859-1
return org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
}


public Charset getCharset() throws UnsupportedEncodingException {
//这个charset就是调用request.setCharacterEncoding()设置的
if (charset == null) {
getCharacterEncoding();
if (characterEncoding != null) {
charset = B2CConverter.getCharset(characterEncoding);
}
}

return charset;
}

public String getCharacterEncoding() {
if (characterEncoding == null) {
//从contentType里获取
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 {
/*
在这里获取过一次getHeaders
如果原始的Content-Type:application/json
则会被修改成 application/json;charset=request.getCharsetEncoding()
*/
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);

//获取到StringMessageConverter
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 {
//如果没有字符集,则从request中获取,拼接进去
if (contentType != null && contentType.getCharset() == null) {
//获取request上的字符集
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-datax-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来做这样的事。


java web请求乱码问题
https://www.huangchaoyu.com/3145821321.html
作者
hcy
发布于
2020年6月19日
更新于
2024年8月17日
许可协议