Springboot请求响应乱码
背景
在一个项目中出现了一个比较棘手的问题,情况是这样的:由于项目场景的需要,引入了公司技术架构部门在activiti
工作流的基础上进行二次开发的工作流组件,使用该组件,需要实现几个获取岗位信息、人员信息相关的接口,工作流组件通过restTemplate
调用这些接口来获取对应的人员、岗位信息用于工作流服务的节点信息显示,在开发过程中一切正常且部署到开发环境中调试过程中也是一切正常,但是当部署到测试环境后,发现接口出现乱码。
排查
由于工作流服务采用的是tomcat
方式部署,第一反应是修改tomcat
中的相关配置文件web.xml
和server.xml
的相关配置,但是修改之后发现并无效果。又尝试在测试环境使用开发环境的正常使用的镜像,还是出现乱码,到这个时候真的是一点头绪都没有了,不知从何下手。在不断的排查过程中,发现仅仅是通过restTemplate
请求业务服务的岗位信息、人员信息接口出现了乱码问题,其他接口并无异常。
而后发现开发环境和测试环境的同一个接口,响应时的Content-Type
中,测试环境少了charset=UTF-8
,马上使用@RequestMapping
的produces = "application/json;charset=UTF-8"
指定编码方式,经测试,正常解决乱码问题!!!
解析
在解决这个乱码问题之后,其实还是很疑惑的,为什么在开发环境和测试环境出现了不同的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 { 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/json
、application/*+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) { 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) { 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
在测试环境中存在丢失的情况,也没有办法进入测试环境进行具体问题的排查,仅通过这一问题,对请求、响应的相关细节有了进一步的了解。