🤖🤖-摘要:
本文介绍了HTTP内容协商,包括服务端驱动型内容协商和代理驱动型内容协商,并分析了SpringMVC和SpringBoot的内容协商方式。文章还通过示例和图示说明了如何进行基于请求头和基于请求参数的内容协商,以及如何进行测试和配置。最后,探讨了内容协商的原理。

内容协商

HTTP内容协商

HTTP协议中,内容协商是一种机制,用于为同一URI提供资源不同的表示形式,以帮助用户代理指定最适合用户的表示形式
例如,哪种文档语言,哪种图片格式或者哪种内容编码

内容协商通常有两种方式,服务端驱动型内容协商和代理驱动型内容协商

服务端驱动型内容协商

在服务端驱动型内容协商或者主动内容协商中,浏览器(或者其他任何类型的用户代理)会随同 URL 发送一系列的 HTTP 标头.
这些标头描述了用户倾向的选择.服务器则以此为线索,通过内部算法来选择最佳方案提供给客户端.如果它不能提供一个合适的资源,
它可能使用 406(Not Acceptable)、415(Unsupported Media Type)进行响应并为其支持的媒体类型设置标头.
例如,分别对 POST 和 PATCH 请求使用 Accept-Post (en-US) 或 Accept-Patch 标头

服务端驱动型内容协商

HTTP/1.1 规范指定了一系列的标准标头用于启动服务端驱动型内容协商(Accept、Accept-Charset、Accept-Encoding、Accept-Language)

请求头请求头说明响应头响应头说明
Accept告诉服务端需要的类型Content-Type告诉客户端响应的媒体类型
Accept-Language告诉服务端需要的语言Content-Language告诉客户端响应的语言
Accept-Charset告诉服务端需要的字符集Content-Charset告诉客户端响应的字符集
Accept-Encoding告诉服务端需要的压缩方式Content-Encoding告诉客户端响应的压缩方式

代理驱动型内容协商

从 HTTP 协议制定之初,该协议就准许另外一种协商机制:代理驱动型内容协商,或称为响应式协商。
在这种协商机制中,当面临不明确的请求时,服务器会返回一个页面,其中包含了可供选择的资源的链接。
资源呈现给用户,由用户做出选择.但是HTTP 标准没有明确指定提供可选资源链接的页面的格式,这阻碍了该过程的无痛自动化。
除了退回至服务端驱动型内容协商外,这种自动化方法几乎无一例外都是通过脚本技术来完成的,
尤其是 JavaScript 重定向技术:在检测了协商的条件之后,脚本会触发重定向动作。
另外一个问题是,为了获得实际的资源,需要额外发送一次请求,减慢了将资源呈现给用户的速度

SpringMVC的内容协商

SpringMVC实现了HTTP内容协商的同时,又进行了扩展.
支持4种内容协商方式:HTTP首部Accept,扩展名,请求参数,或者固定类型

SpringBoot的内容协商

由于SpringBoot的web场景启动器整合了SpringMVC,因此SpringBoot引入web场景启动器后即可拥有内容协商功能

SpringBoot有两种方式:基于请求头和基于请求参数的实现

  • 基于请求头内容协商:(默认开启)
    • 客户端向服务端发送请求,携带HTTP标准的Accept请求头
    • Accept: application/jsontext/xmltext/yaml
    • 服务端根据客户端请求头期望的数据类型进行动态返回
  • 基于请求参数内容协商:(需要开启)
    • 发送请求: GET /person?format=json
    • 匹配到 @GetMapping(“/person”)
    • 根据参数协商,优先返回json类型数据,(需要开启参数匹配设置)
    • 发送请求 GET /person?format=xml,优先返回xml类型数据

测试

默认情况,返回JSON

是因为???web场景依赖 spring-boot-starter-json
spring-boot-starter-json依赖jackson-databind
jackson-databind可以将对象转为JSON

效果:

修改为XML格式:

  1. 引依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
  1. 标注解
@JacksonXmlRootElement  // 可以写出为xml文档
@Data
public class Person {
private long id;
private String name;
private int age;
}

效果:

参数演示:

需要开启基于请求参数的内容协商

代码版:

/**
* 内容协商的相关配置
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer){
// 开启基于请求参数的内容协商功能
configurer.favorParameter(true);// 默认:false
// 自定义内容协商时使用的参数名
configurer.parameterName("type");// 默认:format
}

配置文件版:

# 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
spring.mvc.contentnegotiation.favor-parameter=true
# 指定内容协商时使用的参数名。默认是 format
spring.mvc.contentnegotiation.parameter-name=type

内容协商原理浅析

其实就是

  • HttpMessageConverter 怎么工作?合适工作?
  • 定制 HttpMessageConverter 来实现多端内容协商
  • 编写WebMvcConfigurer提供的configureMessageConverters底层,修改底层的MessageConverter

@ResponseBodyHttpMessageConverter处理

标注了@ResponseBody的返回值 将会由支持它的HttpMessageConverter写给浏览器

  • 如果controller方法的返回值标注了@ResponseBody注解

    • 请求进来先来到DispatcherServletdoDispatch()进行处理
    • 找到一个HandlerAdapter适配器。利用适配器执行目标方法
    • RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
    • 目标方法执行之前,准备好两个东西
      • HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
      • HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
    • RequestMappingHandlerAdapter里面的invokeAndHandle()真正执行目标方法
    • 目标方法执行完成,会返回返回值对象
    • 找到一个合适的返回值处理器HandlerMethodReturnValueHandler
    • 最终找到RequestResponseBodyMethodProcessor能处理 标注了@ResponseBody注解的方法
    • RequestResponseBodyMethodProcessor调用writeWithMessageConverters,利用MessageConverter把返回值写出去
  • HttpMessageConverter会先进行内容协商

    • 遍历所有的MessageConverter看谁支持这种内容类型的数据
    • 默认MessageConverter有以下:
    • 最终因为要json所以MappingJackson2HttpMessageConverter支持写出json
    • jackson用ObjectMapper把对象写出去

WebMvcAutoConfiguration提供几种默认HttpMessageConverters

EnableWebMvcConfiguration通过addDefaultHttpMessageConverters添加了默认的MessageConverter;
如下:

  • ByteArrayHttpMessageConverter: 支持字节数据读写
  • StringHttpMessageConverter: 支持字符串读写
  • ResourceHttpMessageConverter: 支持资源读写
  • ResourceRegionHttpMessageConverter: 支持分区资源写出
  • AllEncompassingFormHttpMessageConverter: 支持表单xml/json读写
  • MappingJackson2HttpMessageConverter: 支持请求响应体Json读写

系统提供默认的MessageConverter 功能有限,仅用于json或者普通返回数据。
额外增加新的内容协商功能,必须增加新的HttpMessageConverter