内容协商:SpringBoot添加jackson-xml后浏览器返回值的变化

探究SpringBoot在引入jackson-dataformat-xml后浏览器返回值的变化:内容协商

一直以来基本上都是和JSON格式的数据打交道,前端传递参数或后端返回结果也基本上都用的JSON字符串,偶尔需要处理例如XML格式的数据也用一些小工具类就处理了。
而最近项目有个需求是需要暴露一个接口用于接收第三方回调的Content-Typeapplication/xml的参数,因为字段比较多,因此就引入了jackson-dataformat-xml依赖进行自动序列化和反序列化。
测试结果良好,正当我高兴之余,随手用浏览器访问了一个接口,发现返回的并不是JSON格式的字符串,而是看起来像纯文本一样的文字。刚好控制台也开着,发现我的JSON格式化插件报错了。

JSON Plugin Error

恰好早上顺手升级了这个插件的JSON格式化部分,我的第一反应是插件出问题了...但我定睛一看,好家伙,返回的是XML格式的数据,大概是被当成自定义XML文档展示了,用匿名模式访问,直接展示出XML文档,一想到刚才引用的jackson-dataformat-xml,瞬间明白了应该是引入依赖后自动配置的问题,注释掉依赖后果然按以往一样返回了JSON字符串

API XML Result

看了一下NetWork发送的请求,Request Headers中的Accept写着text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9,看来是内容协商的问题,按照浏览器的请求头顺序,如果服务端引入了XML的MediaType支持,确实应该优先返回XML文档。

什么是内容协商?

在HTTP协议中,内容协商机制的通俗理解就是访问同一URL资源的时候,返回的数据会根据协商的类型展现不同形式。例如对于/api/users这个接口,可以返回JSON格式的数据,也可以返回XML类型的数据,具体返回哪种类型,由内容协商的标识来决定。

内容协商标识

客户端在发送HTTP请求的时候,可以在HTTP Headers中设置一系列标准消息头(Accept、Accept-Charset、 Accept-Encoding、Accept-Language)使服务端返回内容协商的对应数据。
比如设置为Accept-Language: zh; q=1.0, en; q=0.5就表示客户端可以接收中文和英文,同时中文的权重会更高。
在浏览器地址栏默认访问API地址时,发出的请求是Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9,则表示接受服务端的返回数据类型是HTML页面XML文档,优先级按顺序递减。在MDN中有关于内容协商和标准请求头内容更加详细的相关解释。

浏览器访问为什么返回XML

我们用一般写的Rest API接口,基本上都是返回JSON格式的数据,因此服务端会按照客户端的默认Accept进行对应格式的返回。而SpringBoot在新建工程并且未添加jackson-dataformat-xml依赖的情况下,按照浏览器默认的Accept顺序,靠前的text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng都不支持或不匹配,因此到了*/*之后,返回由内置jackson-json提供的默认支持application/json。而在加入jackson-dataformat-xml依赖之后,自动为SpringBoot注册了XML相关的支持处理,因此在匹配到application/xhtml+xml时就会优先返回XML类型的数据。而类似于Axios的HTTP库或者接口测试工具,默认的Accept一般是application/json, text/plain, */*,所以会优先返回JSON字符串、其次是纯文本和其他类型。

如何在使用XML依赖的同时,返回JSON

对于返回结果的格式,最佳的方式肯定是客户端/发送请求方设置Accept请求头,然后服务端进行内容协商然后返回。
但是因为有时候在开发时为了方便调试,使用会直接用浏览器访问一些接口,希望先返回JSON格式的字符串而不是XML。
按照最佳方式应该设置浏览器默认的标准消息头,加入application/json并放在最前面,这样就会优先返回JSON字符串。但是一般浏览器本身是不支持用让用户去设置这些参数,只能通过浏览器插件来拦截请求进行额外设置和处理,也挺麻烦的。
所以我们其实可以在开发的时候,对服务端进行一些配置,让其返回我们想要的数据格式。

方式一:启用SpringBoot/MVC的内容协商偏好参数

启用SpringBoot的内容协商偏好参数,然后在请求的QueryString中加入该参数,就会根据参数来进行内容协商返回结果。
application.yml配置文件中进行以下设置:

spring:
  mvc:
    contentnegotiation:
      # 启用内容协商的偏好参数
      favor-parameter: true
      # 设置偏好参数的参数名,不设置该项的话,默认是format
      parameter-name: fp

或者在代码中实现WebMvcConfigurer接口,然后进行相同配置:

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorParameter(true).parameterName("fp");
    }
}

此时可以根据请求URL的queryString参数fp来确定返回的数据格式类型,如:localhost:8080/api/users?fp=json会返回JSON格式,localhost:8080/api/users?fp=xml则返回XML格式。

方式二:设置默认的内容类型,并禁用检查Accept请求头

先按照优先级设置一个或多个默认的内容协商类型,然后禁用检查Accept请求头,服务端就会忽视原有请求的Accept中的内容协商类型,而按照配置的默认的类型进行响应。
按以下配置的情况下,请求默认总是返回JSON。
如果想让部分接口只返回XML格式的数据,可以在@RequestMapping中配置produces,如@GetMapping(value = "/api/users",produces = MediaType.APPLICATION_XHTML_XML_VALUE),那个接口就会返回XML格式的数据。

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer
                .defaultContentType(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.APPLICATION_XHTML_XML)
                .ignoreAcceptHeader(true);
    }
}

方式三:移除依赖的自动配置项

因为jackson-dataformat-xml的引入,使得XML相关的支持被配置到了SpringMvc中,因此只要移除这些配置,就可以让接口只返回JSON字符串。
但这其实并不是一个好的解决方式,因为它误解了内容协商的意思,并掩盖了问题。不过如果只想用依赖中的XmlMapper对一些对象或字符串进行XML的序列化和反序列化,不需要接收XML格式的参数,也不需要其他依赖自动装配的特性,那么可以选择这种方法。但是出于这种目的,似乎使用其他第三方库或工具类会是更好的选择...
如果想要移除自动配置的转换器,只需要实现WebMvcConfigurer接口,并重写其中的extendMessageConvertersconfigureMessageConverters,然后进行相应移除的操作,移除后Controller就失去了对XML请求和返回的支持。
例如:

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof MappingJackson2XmlHttpMessageConverter);
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇