在SpringMVC的响应中添加Location响应头

背景

HTTP协议有一个状态码是201(Created),表示请求成功、已在服务器上成功创建资源。这是一个非常实用的HTTP状态码。使用这个语义明确的状态码是远比只返回一个HTTP 200要好的。


假设我们发送一个POST请求来创建一本书:

1
2
3
4
5
6
7
POST /books HTTP/1.1
Host: demo.ningyu.me

{
"name": "我的团长我的团",
"author": "兰晓龙"
}

服务器程序在返回201状态码之后,通常还要在响应报文的首部中添加一个Location字段,字段的值为创建资源的URI。响应体中返回新创建好的资源:

1
2
3
4
5
6
7
8
HTTP 201 Created
Location: https://demo.ningyu.me/books/1

{
"id": 1,
"name": "我的团长我的团",
"author": "兰晓龙"
}

那么如何在SpringMVC实现呢?解决问题的关键是UriComponentsBuilder和ServletUriComponentsBuilder这两个类,它们俩都成实现这个目标,但稍有不同,后面会提到。



UriComponentsBuilder

使用来自MvcUriComponentsBuilder预先配置好的UriComponentsBuilder

1
2
3
4
5
6
7
8
9
10
11
@PostMapping
public ResponseEntity<?> add(@RequestBody BookDto dto, UriComponentsBuilder builder)
{
UriComponents uriComponents = MvcUriComponentsBuilder.fromMethodCall(MvcUriComponentsBuilder.on(BookController.class).findById(123L)).buildAndExpand(123L);
/**
* 也可以使用指定要调用的方法的名称
* UriComponents uriComponents = MvcUriComponentsBuilder.fromMethodName(BookController.class, "findById", 123L).buildAndExpand(123L);
*/
URI location = uriComponents.toUri();
return ResponseEntity.created(location).build();
}

将UriComponentsBuilder注入为方法参数

1
2
3
4
5
6
7
8
@PostMapping
public ResponseEntity<?> add(@RequestBody BookDto dto, UriComponentsBuilder builder)
{
Book saved = bookService.save(dto);
URI location = builder.replacePath("/books/{id}").buildAndExpand(saved.getId()).toUri();
// 也可以这样:URI location = builder.path("/books/{id}").buildAndExpand(saved.getId()).toUri();
return ResponseEntity.created(location).body(saved);
}

需要注意的是使用UriComponentsBuilder,将不会计算当前请求路径。您需要手动添加它,也就是如果你只是使用了builder.path("/books/{id}").buildAndExpand(saved.getId()).toUri()这样的方式,那么Location的值不会包含https://demo.ningyu.me这个前缀。



ServletUriComponentsBuilder

1
2
3
4
5
6
7
8
@PostMapping
public ResponseEntity<?> add(@RequestBody BookDto dto)
{
Book saved = bookService.save(pointDto);
URI location = ServletUriComponentsBuilder.fromCurrentRequestUri().path("{id}").buildAndExpand(saved.getId()).toUri();
return ResponseEntity.created(location).build();
// 也可以这样:return ResponseEntity.status(HttpStatus.CREATED).header(HttpHeaders.LOCATION, String.valueOf(location)).build();
}



遗留问题

如果是成功创建了多个资源,又该怎么处理呢?



参考