0%

Springboot请求响应乱码

Springboot请求响应乱码

背景

在一个项目中出现了一个比较棘手的问题,情况是这样的:由于项目场景的需要,引入了公司技术架构部门在activiti工作流的基础上进行二次开发的工作流组件,使用该组件,需要实现几个获取岗位信息、人员信息相关的接口,工作流组件通过restTemplate调用这些接口来获取对应的人员、岗位信息用于工作流服务的节点信息显示,在开发过程中一切正常且部署到开发环境中调试过程中也是一切正常,但是当部署到测试环境后,发现接口出现乱码。

排查

​ 由于工作流服务采用的是tomcat方式部署,第一反应是修改tomcat中的相关配置文件web.xmlserver.xml的相关配置,但是修改之后发现并无效果。又尝试在测试环境使用开发环境的正常使用的镜像,还是出现乱码,到这个时候真的是一点头绪都没有了,不知从何下手。在不断的排查过程中,发现仅仅是通过restTemplate请求业务服务的岗位信息、人员信息接口出现了乱码问题,其他接口并无异常。

​ 而后发现开发环境和测试环境的同一个接口,响应时的Content-Type中,测试环境少了charset=UTF-8,马上使用@RequestMappingproduces = "application/json;charset=UTF-8"指定编码方式,经测试,正常解决乱码问题!!!

测试环境工作流请求返回.png

开发环境工作流返回.png

解析

​ 在解决这个乱码问题之后,其实还是很疑惑的,为什么在开发环境和测试环境出现了不同的Content-Type响应头信息,测试环境响应头中的charset=UTF-8为什么会消失?由于我们是采用指定了@RequestMapping注解的produces属性才解决问题,那么具体的情况还得从它入手,如果不指定值,那么它默认的处理逻辑是什么?

跟踪源码发现:在HeaderContentNegotiationStrategy进行了api媒体类型的相关处理,主要逻辑是获取请求头中的accept属性值,若为空,则指定*/*为结果值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {

// 获取请求头中的accept属性
String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
if (headerValueArray == null) {
// 若为空,则返回所有 -> 实际值就是: */*
return MEDIA_TYPE_ALL_LIST;
}

List<String> headerValues = Arrays.asList(headerValueArray);
try {
List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
MediaType.sortBySpecificityAndQuality(mediaTypes);
return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotAcceptableException(
"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
}
}

接着会进入AbstractMessageConverterMethodProcessor#getProducibleMediaTypes这个返回主要用于返回响应的媒体类型,主要会通过响应的返回值的数据类型根据具体的媒体转换器的canWrite方法来获取支持的媒体类型。

根据测试发现,如果在accept或者是@RequestMapping指定了produces属性值,那么这里直接获取到对应的值之后就返回了,而下面的通过默认的消息转换器中获取到的媒体类型都是不带具体的编码格式的,比如我的这个接口返回的主要就是application/jsonapplication/*+json两种。

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
protected List<MediaType> getProducibleMediaTypes(
HttpServletRequest request, Class<?> valueClass, @Nullable Type targetType) {
// 如果这里获取不到媒体的类型的情况下,进入下面的消息转换器获取,
// 如果在@RequestMapping中指定了produces或者是原本请求头中的accept中指定了值,这里直接就是获取到指定的那个值
Set<MediaType> mediaTypes =
(Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return new ArrayList<>(mediaTypes);
}else if (!this.allSupportedMediaTypes.isEmpty()) {
List<MediaType> result = new ArrayList<>();
for (HttpMessageConverter<?> converter : this.messageConverters) {
// 通过消息转换器获取响应结果支持的媒体类型
// 比如接口的返回类型是List,则这里会获取到application/json、application/*+json
// 返回类型是String,可能会有其他的媒体类型之类
if (converter instanceof GenericHttpMessageConverter && targetType != null) {
if (((GenericHttpMessageConverter<?>) converter).canWrite(targetType, valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}else if (converter.canWrite(valueClass, null)) {
result.addAll(converter.getSupportedMediaTypes());
}
}
return result;
}else {
return Collections.singletonList(MediaType.ALL);
}
}

总结

​ 在我们公司其实研发也是获取不到开发环境、测试环境的服务器登录等相关权限,都是需要告诉运维人员要操作什么,或者获取到对应的配置文件信息,然后进行更改,所以现在尽管知道了可能是请求头中的accept在测试环境中存在丢失的情况,也没有办法进入测试环境进行具体问题的排查,仅通过这一问题,对请求、响应的相关细节有了进一步的了解。

-------------本文结束感谢您的阅读-------------