> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/di-16-zhang-shi-yong-spring-mvc-chuang-jian-rest-api/untitled-2/untitled.md).

# 16.3.2　在响应中设置头部信息

在 saveSpittle() 方法中，我们在处理 POST 请求的过程中创建了一个新的 Spittle 资源。但是，按照目前的写法（参考程序清单 16.3），我们无法准确地与客户端交流。

在 saveSpittle() 处理完请求之后，服务器在响应体中包含了 Spittle 的表述以及 HTTP 状态码 200（OK），将其返回给客户端。 这里没有什么大问题，但是还不是完全准确。

当然，假设处理请求的过程中成功创建了资源，状态可以视为 OK。 但是，我们不仅仅需要说 “OK”。我们创建了新的内容，HTTP 状态码也将这种情况告诉给了客户端。不过，HTTP 201 不仅能够表明请求成功完成，而且还能描述创建了新资源。如果我们希望完整准确地与客户端交流，那么响应是不是应该为 201（Created），而不仅仅是 200（OK）呢？

根据我们目前所学到的知识，这个问题解决起来很容易。我们需要做的就是为 saveSpittle() 方法添加 @ResponseStatus 注解，如下所示：

```java
@RequestMapping(method=RequestMethod.POST, consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public Spittle saveSpittle(@RequestBody Spittle spittle) {
  return spittleRepository.save(spittle);
}
```

这应该能够完成我们的任务，现在状态码能够精确反应发生了什么情况。它告诉客户端我们新创建了资源。问题已经得以解决！

但这只是问题的一部分。客户端知道新创建了资源，你觉得客户端会不会感兴趣新创建的资源在哪里呢？毕竟，这是一个新创建的资源，会有一个新的 URL 与之关联。难道客户端只能猜测新创建资源的 URL 是什么吗？我们能不能以某种方式将其告诉客户端？

当创建新资源的时候，将资源的 URL 放在响应的 Location 头部信息中，并返回给客户端是一种很好的方式。因此，我们需要有一种方式来填充响应头部信息，此时我们的老朋友 ResponseEntity 就能提供帮助了。

如下的程序清单展现了一个新版本的 saveSpittle()，它会返回 ResponseEntity 用来告诉客户端新创建的资源。

{% code title="程序清单 16.4 当返回 ResponseEntity 时，在响应中设置头部信息" %}

```java
@RequestMapping(method=RequestMethod.POST, consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle) {
    Spittle spittle = spittleRepository.save(spittle);
    
    HttpHeaders headers = new HttpHeaders();
    URI locationUri = URI.create("http://localhost:8080/spittr/spittles" + spittle.getId());
    headers.setLocation(locationUri);
    
    ResponseEntity<Spittle> responseEntity = new ResponseEntity<Spittle>(spittle, headers, HttpStatus.CREATED);
    return responseEntity;
  }
```

{% endcode %}

在这个新的版本中，我们创建了一个 HttpHeaders 实例，用来存放希望在响应中包含的头部信息值。HttpHeaders 是 MultiValueMap 的特殊实现，它有一些便利的 Setter 方法（如 setLocation()），用来设置常见的 HTTP 头部信息。在得到新创建 Spittle 资源的 URL 之后，接下来使用这个头部信息来创建 ResponseEntity。

哇！原本简单的 saveSpittle() 方法瞬间变得臃肿了。但是，更值得关注的是，它使用硬编码值的方式来构建 Location 头部信息。URL 中 “localhost” 以及 “8080” 这两个部分尤其需要注意，因为如果我们将应用部署到其他地方，而不是在本地运行的话，它们就不适用了。

我们其实没有必要手动构建 URL，Spring 提供了 UriComponentsBuilder，可以给我们一些帮助。它是一个构建类，通过逐步指定 URL 中的各种组成部分（如 host、端口、路径以及查询），我们能够使用它来构建 UriComponents 实例。借助 UriComponentsBuilder 所构建的 UriComponents 对象，我们就能获得适合设置给 Location 头部信息的 URI。

为了使用 UriComponentsBuilder，我们需要做的就是在处理器方法中将其作为一个参数，如下面的程序清单所示。

{% code title="程序清单 16.5　使用 UriComponentsBuilder 来构建 Location URI" %}

```java
@RequestMapping(method=RequestMethod.POST, consumes="application/json")
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle, UriComponentsBuilder ucb) {
  Spittle saved = spittleRepository.save(spittle);
    
  HttpHeaders headers = new HttpHeaders();
  URI locationUri = ucb.path("/spittles/")
      .path(String.valueOf(saved.getId()))
      .build()
      .toUri();
  headers.setLocation(locationUri);
    
  ResponseEntity<Spittle> responseEntity = new ResponseEntity<Spittle>(saved, headers, HttpStatus.CREATED);
  return responseEntity;
}
```

{% endcode %}

在处理器方法所得到的 UriComponentsBuilder 中，会预先配置已知的信息如 host、端口以及 Servlet 内容。它会从处理器方法所对应的请求中获取这些基础信息。基于这些信息，代码会通过设置路径的方式构建 UriComponents 其余的部分。

注意，路径的构建分为两步。第一步调用 path() 方法，将其设置为 “/spittles/”，也就是这个控制器所能处理的基础路径。然后，在第二次调用 path() 的时候，使用了已保存 Spittle 的 ID。我们可以推断出来，每次调用 path() 都会基于上次调用的结果。

在路径设置完成之后，调用 build() 方法来构建 UriComponents 对象，根据这个对象调用 toUri() 就能得到新创建 Spittle 的 URI。

在 REST API 中暴露资源只代表了会话的一端。如果发布的 API 没有人关心和使用的话，那也没有什么价值。通常来讲，移动或 JavaScript 应用会是 REST API 的客户端，但是 Spring 应用也完全可以使用这些资源。我们换个方向，看一下如何编写 Spring 代码实现 RESTful 交互的客户端。
