1. 概述

在本文中,我们将探讨Spring MVC的@RequestHeader注解。

@RequestHeader可以读取HTTP请求头中的参数绑定到控制器(Controller)上处理方法的参数。

首先,我们将使用*@RequestHeader*分别或同时读取请求头。

然后,研究@RequestHeader注解中的属性。

2. 读取HTTP请求头

2.1 读取单个请求头参数

访问特定的请求头参数,可以使用参数名配置@RequestHeader

@GetMapping("/RequestHeader/AcceptEncoding")
@ResponseBody
public String handle(@RequestHeader("Accept-Encoding") String encoding){
  return String.format("RequestHeader:  Accept-Encoding - %s", encoding);
}

然后我们来测试一下

# curl -H "accept-encoding: UTF-8" "http://localhost:8080/RequestHeader/AcceptEncoding/"
RequestHeader:  Accept-Encoding - UTF-8%

测试发现可以正常读取到我们想要的参数。

但是如果HTTP请求头,不包含我们想要的参数呢?

# curl -H "accept-encodin: UTF-8" "http://localhost:8080/RequestHeader/AcceptEncoding/"
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 400 Missing request header &apos;Accept-Encoding&apos; for method parameter of type String</title>
</head>
<body><h2>HTTP ERROR 400 Missing request header &apos;Accept-Encoding&apos; for method parameter of type String</h2>
<table>
<tr><th>URI:</th><td>/RequestHeader/AcceptEncoding/</td></tr>
<tr><th>STATUS:</th><td>400</td></tr>
<tr><th>MESSAGE:</th><td>Missing request header &apos;Accept-Encoding&apos; for method parameter of type String</td></tr>
<tr><th>SERVLET:</th><td>mvc</td></tr>
</table>
<hr><a href="http://eclipse.org/jetty">Powered by Jetty:// 9.4.24.v20191120</a><hr/>

</body>
</html>

如果在请求中找不到名为accept-language的请求头,则该方法将返回“ 400 Bad Request”错误。

我们查看Debug日志,会看到Spring MVC抛出了MissingRequestHeaderException

20:23:00.417 [qtp1298780808-21] DEBUG o.s.web.servlet.DispatcherServlet - GET "/RequestHeader/AcceptEncoding/", parameters={}
20:23:00.418 [qtp1298780808-21] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to com.ripjava.spring.mvc.model.controller.RequestHeaderController#handle(String)
20:23:00.418 [qtp1298780808-21] DEBUG o.s.w.s.m.m.a.ServletInvocableHandlerMethod - Could not resolve parameter [0] in public java.lang.String com.ripjava.spring.mvc.model.controller.RequestHeaderController.handle(java.lang.String): Missing request header 'Accept-Encoding' for method parameter of type String
20:23:00.420 [qtp1298780808-21] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header 'Accept-Encoding' for method parameter of type String]
20:23:00.421 [qtp1298780808-21] DEBUG o.s.web.servlet.DispatcherServlet - Completed 400 BAD_REQUEST

还有一点请求头对应的参数不必是字符串。

比如,如果我们知道请求头是数字,则可以将变量声明为数字类型:

@GetMapping("/RequestHeader/NumberType")
@ResponseBody
public String handleNumber(@RequestHeader("Header-Number") int number){
  return String.format("RequestHeader: Header-Number - %s", number);
}

简单的测试一下:

# curl -H "accept-encoding: UTF-8" -H "Header-Number:123456"   "http://localhost:8080/RequestHeader/NumberType/"
RequestHeader: Header-Number - 123456%

2.2 获取全部请求头参数

如果我们不确定会出现哪些HTTP请求头参数,或者需要的HTTP请求头很多的话,可以使用@RequestHeader注解而不使用特定名称。

对于变量类型,我们有几种选择:MapMultiValueMapHttpHeaders对象。

2.2.1 使用Map

首先,让我们将HTTP请求头参数使用Map获取:

@GetMapping("/RequestHeader/Map")
@ResponseBody
public String handleMap(@RequestHeader Map<String, String> headers){
  StringBuilder sb = new StringBuilder();
  headers.forEach((key, value) -> {
    sb.append(String.format("RequestHeader: '%s' = %s", key, value))
      .append(System.lineSeparator());
  });
  return  sb.toString();
}

测试一下:

# curl -H "accept-encoding: UTF-8"  \
       -H "Accept:text/html,application/xhtml+xml,application/xml;q=0.9"  \
      "http://localhost:8080/RequestHeader/Map/"
RequestHeader: 'Accept' = text/html,application/xhtml+xml,application/xml;q=0.9
RequestHeader: 'User-Agent' = curl/7.64.1
RequestHeader: 'Host' = localhost:8080
RequestHeader: 'Accept-Encoding' = UTF-8

如果我们使用Map并且其中一个HTTP请求头具有一个以上的值**,我们将只得到第一个值**。

2.2.2 使用MultiValueMap

如果我们的HTTP请求头可能有多个参数,我们可以将它们作为MultiValueMap获取。

public interface MultiValueMap<K, V> extends Map<K, List<V>> {

我们写一个简单的例子:

@GetMapping("/RequestHeader/MultiValueMap")
@ResponseBody
public String handleMap(@RequestHeader MultiValueMap<String, String> headers){
  StringBuilder sb = new StringBuilder();
  headers.forEach((key, value) -> {
    sb.append(String.format("RequestHeader: '%s' = %s", key, value.stream().collect(Collectors.joining("|"))))
      .append(System.lineSeparator());
  });
  return  sb.toString();
}

测试一下:

# curl -H "accept-encoding: UTF-8"  \
       -H "accept-encoding: MS932"  \
       -H "Accept:text/html,application/xhtml+xml,application/xml;q=0.9"  \
      "http://localhost:8080/RequestHeader/MultiValueMap/"
RequestHeader: 'Accept' = text/html,application/xhtml+xml,application/xml;q=0.9
RequestHeader: 'User-Agent' = curl/7.64.1
RequestHeader: 'Host' = localhost:8080
RequestHeader: 'Accept-Encoding' = UTF-8|MS932
2.2.3 使用HttpHeaders

我们还可以使用HttpHeaders对象来获取。因为HttpHeaders继承了MultiValueMap

public class HttpHeaders implements MultiValueMap<String, String>, Serializable {

我们写一个其他获取url的例子:

@GetMapping("/RequestHeader/HttpHeaders")
@ResponseBody
public String handleMap(@RequestHeader HttpHeaders headers){
  InetSocketAddress host = headers.getHost();
  return "http://" + host.getHostName() + ":" + host.getPort();
}

HttpHeaders对象有很多HTTP定义的标准请求头的参数的get/set方法。比如上面代码的getHost

从Map,MultiValueMap或HttpHeaders对象中按名称访问请求头参数时,如果不存在则为空。

3. @RequestHeader属性

现在,让我们仔细看一下它的属性。

当我们使用参数的名称时,我们已经隐式使用了namevalue属性:

public String handle(@RequestHeader("Accept-Encoding") String encoding){

当然,也可以通过指定name属性来完成同一件事:

public String handle(@RequestHeader(name = "Accept-Encoding") String encoding){

value也是一样的:

public String handle(@RequestHeader(value = "Accept-Encoding") String encoding){

当指定参数的名称时,默认情况下参数是必需的。

如果在请求中找不到指定的参数,控制器会抛出异常MissingRequestHeaderException,并返回错误400

不过,可以使用required属性来指定HTTP请求头不是必须的:

@GetMapping("/RequestHeader/NonRequiredHeader")
public String handleNonRequiredHeader(
  @RequestHeader(value = "optional-header", required = false) String optionalHeader) {
  return String.format("RequestHeader:  optional-header - %s", optionalHeader);
}

如果请求头中不存在参数,变量将会为null,因此需要确保进行适当的null检查。

除了使用required参数,我们可以将参数包装在Optional类中,这样我们不使用required参数,也可以将参数设置为可选。

@GetMapping("/RequestHeader/OptionalRequiredHeader")
public String handleOptionalRequiredHeader(
  @RequestHeader(value = "optional-header") Optional<String> optionalHeader) {
  return String.format("RequestHeader:  optional-header - %s", optionalHeader);
}

我们还使用defaultValue属性为HTTP请求头提供默认值:

@GetMapping("/RequestHeader/DefaultHeader")
public String handleDefaultHeader(
  @RequestHeader(value = "optional-header", defaultValue ="test") String optionalHeader) {
  return String.format("RequestHeader:  optional-header - %s", optionalHeader);
}

4. 结论

在本文中,我们学习了在控制器中访问HTTP请求头的信息。

首先,我们使用@RequestHeader注解为控制器方法来获取HTTP请求头。

然后,我们简单的研究一下@RequestHeader注解的属性。

与往常一样,可以在GitHub上找到示例代码。