十、Spring Web Header 解析常见错误
对于一个 HTTP 请求而言,URL 固然重要,但是为了便于用户使用,URL 的长度有限,所能携带的信息也因此受到了制约。
如果想提供更多的信息,Header 往往是不二之举。不言而喻,Header 是介于 URL 和 Body 之外的第二大重要组成,它提供了更多的信息以及围绕这些信息的相关能力,例如 Content-Type 指定了我们的请求或者响应的内容类型,便于我们去做解码。虽然 Spring 对于 Header 的解析,大体流程和 URL 相同,但是 Header 本身具有自己的特点。例如,Header 不像 URL 只能出现在请求中。所以,Header 处理相关的错误和 URL 又不尽相同。
一.接受 Header 使用错 Map 类型
在 Spring 中解析 Header 时,我们在多数场合中是直接按需解析的。例如,我们想使用一个名为 myHeaderName 的 Header,我们会书写代码如下:
1、只有一个Header的情况
@RequestMapping(path = "/hi", method = RequestMethod.GET)
public String hi(@RequestHeader("myHeaderName") String name){
//省略 body 处理
};
2、有很多个Header的情况
但是假设我们需要解析的 Header 很多时,按照上面的方式很明显会使得参数越来越多。在这种情况下,我们一般都会使用 Map 去把所有的 Header 都接收到,然后直接对 Map 进行处理。于是我们可能会写出下面的代码:
@RequestMapping(path = "/hi1", method = RequestMethod.GET)
public String hi1(@RequestHeader() Map map){
return map.toString();
};
这种可以正常使用,但是如果面对这种请求的时候,结果就会超出预期,即一个Header多个value。
GET http://localhost:8080/hi1
myheader: h1
myheader: h2
此时我们执行请求,会发现返回的结果并不能将这两个值如数返回。结果示例如下:
{myheader=h1, host=localhost:8080, connection=Keep-Alive, user-agent=Apache-HttpClient/4.5.12 (Java/11.0.6), accept-encoding=gzip,deflate}
3、案例解析
对于一个多值的 Header,在实践中,通常有两种方式来实现,一种是采用下面的方式:
一个键,一个值(用,隔开)
Key: value1,value2
一个键多个值
Key:value1
Key:value2
对于方式 1,我们使用 Map 接口自然不成问题。但是如果使用的是方式 2,我们就不能拿到所有的值。
4、问题修正(建议使用HttpHeaders)
要完整接收到所有的 Header,不能直接使用 Map 而应该使用 MultiValueMap。
1.使用MultiValueMap
//方式 1
@RequestHeader() MultiValueMap map
2.使用HttpHeaders
/方式 2
@RequestHeader() HttpHeaders map
对比来说,方式 2 更值得推荐,因为它使用了大多数人常用的 Header 获取方法,例如获取 Content-Type 直接调用它的 getContentType() 即可,诸如此类,非常好用。
二.错认为 Header 名称首字母可以一直忽略大小写
1、直接获取Header的值
@RequestMapping(path = "/hi2", method = RequestMethod.GET)
public String hi2(@RequestHeader("MyHeader") String myHeader){
return myHeader;
};
2、通过MAP形式获取Header的值
@RequestMapping(path = "/hi2", method = RequestMethod.GET)
public String hi2(@RequestHeader("MyHeader") String myHeader, @RequestHeader MultiValueMap map){
return myHeader + " compare with : " + map.get("MyHeader");
};
直接获取 Header 是可以忽略大小写的,但是如果从接收过来的 Map 中获取 Header 是不能忽略大小写的。稍微不注意,我们就很容易认为 Header 在任何情况下,都可以不区分大小写来获取值。
3、案例解析
1.存取 Map 的 Header 是没有忽略大小写的
2.从 Map 中获取的 Header 也没有忽略大小写
4、问题修正
写的规范一点即可
三.试图在 Controller 中随意自定义 CONTENT_TYPE 等
1、代码分析
@RequestMapping(path = "/hi3", method = RequestMethod.GET)
public String hi3(HttpServletResponse httpServletResponse){
httpServletResponse.addHeader("myheader", "myheadervalue");
httpServletResponse.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
return "ok";
};
访问后会得到如下结果
GET http://localhost:8080/hi3 HTTP/1.1 200myheader: myheadervalueContent-Type: text/plain;charset=UTF-8Content-Length: 2Date: Wed, 17 Mar 2021 08:59:56 GMTKeep-Alive: timeout=60Connection: keep-alive
可以看到 myHeader 设置成功了,但是 Content-Type 并没有设置成我们想要的"application/json",而是"text/plain;charset=UTF-8"。
2、案例解析(具体的返回值由多种因素决定,但是不应该在response中修改)
在 Spring Boot 基于内嵌 Tomcat 开发时并不一定能设置成功,最终返回的 Content-Type 是根据实际的返回值及类型等多个因素来决定的。
1.决定用哪一种 MediaType 返回
2.选择消息转化器并完成转化
3、问题修正
1.修改请求中的 Accept 头,约束返回类型
带上 Accept 头,这样服务器在最终决定 MediaType 时,会选择 Accept 的值。
GET http://localhost:8080/hi3
Accept:application/json
2.标记返回类型
主动显式指明类型,修改方法如下:
@RequestMapping(path = "/hi3", method = RequestMethod.GET, produces = {"application/json"})
即使用 produces 属性来指明即可。这样的方式影响的是可以返回的 Media 类型,一旦设置,下面的方法就可以只返回一个指明的类型了。