在Spring MVC 3中,指定HTTP“Location”响应头的首选方式是什么?

43

在Spring MVC 3中,指定HTTP“Location”响应头的首选方法是什么?

据我所知,Spring只会在重定向(“redirect:xyz”或RedirectView)的响应中提供“Location”,但是有些情况下需要在实体主体中发送Location(例如,在“201 Created”结果的情况下)。

我担心我的唯一选择是手动指定它:

httpServletResponse.setHeader("Location", "/x/y/z");

这是否正确?有更好的方法来解决这个问题吗?


那种方法有什么问题? - Gandalf
这是一个好的做法,它符合http规范 - rds
5个回答

102
关键点是使用UriComponentsBuilder。有几种方法可以获取它的实例:
  1. MvcUriComponentsBuilder中预配置的UriComponentsBuilder
  2. 作为参数注入到方法中的UriComponentsBuilder

MvcUriComponentsBuilder中预配置的UriComponentsBuilder

通过这种方式,您可以获得一个已配置的UriComponentsBuilder,用于生成指向某些控制器方法的URI,并具有预定义的参数。

以下是MvcUriComponentsBuilderjavadoc示例:

例如,给定此控制器:

 @RequestMapping("/people/{id}/addresses")
 class AddressController {

   @RequestMapping("/{country}")
   public HttpEntity<Void> getAddressesForCountry(@PathVariable String country) { ... }

   @RequestMapping(value="/", method=RequestMethod.POST)
   public void addAddress(Address address) { ... }
 }

可以创建一个UriComponentsBuilder:
 // Inline style with static import of "MvcUriComponentsBuilder.on"

 MvcUriComponentsBuilder.fromMethodCall(
    on(AddressController.class).getAddressesForCountry("US")).buildAndExpand(1);

有时候更好的选择是通过名称指定控制器方法:

UriComponents uriComponents = MvcUriComponentsBuilder.fromMethodName(
    AddressController.class, "getAddressesForCountry", "US").buildAndExpand(1);
URI nextUri = uriComponents.toUri();

UriComponentsBuilder注入方法的参数

从Spring 3.1开始,可以使用UriComponentBuilder参数创建Location并将其设置为返回的ResponseEntityUriComponentBuilder了解上下文并操作相对路径:

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createCustomer(UriComponentsBuilder b) {

    UriComponents uriComponents = 
        b.path("/customers/{id}").buildAndExpand(id);

    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(uriComponents.toUri());
    return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}

自版本4.1起,您甚至可以使其更短。
@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createCustomer(UriComponentsBuilder b) {

    UriComponents uriComponents = 
        b.path("/customers/{id}").buildAndExpand(id);

    return ResponseEntity.created(uriComponents.toUri()).build();
}

感谢Dieter Hubau指出此事。


2
头部设置位置的行上有语法错误。多了一个右括号而且“id”不存在。只是想提醒其他看到这个答案的人,这不是一个完整的可以编译的方法。但是感谢这个答案,很有帮助。 - Kevin M
8
返回ResponseEntity.created(uriComponents.toUri()).build(); - Dieter Hubau
UriComponentsBuilder 被注入了吗?它可以使用 @Autowire 吗? - Jin Kwon
uriComponent没有在答案中添加端口。 我总是得到http://locahost/resource/1而不是http://localhost:9090/resource/1。 - Luis Roberto

39

下面的例子来自Spring教程:

@RequestMapping(method = RequestMethod.POST)
ResponseEntity<?> add(@PathVariable String userId, @RequestBody Bookmark input) {
    this.validateUser(userId);

    return this.accountRepository
            .findByUsername(userId)
            .map(account -> {
                Bookmark result = bookmarkRepository.save(new Bookmark(account,
                        input.uri, input.description));

                URI location = ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/{id}")
                    .buildAndExpand(result.getId()).toUri();

                return ResponseEntity.created(location).build();
            })
            .orElse(ResponseEntity.noContent().build());

}

请注意以下内容将为您计算上下文路径(URI),避免代码重复,使您的应用程序更加便携:

ServletUriComponentsBuilder
                    .fromCurrentRequest().path("/{id}")

6

这是一个老问题,但如果你想让 Spring 真正为你构建 URI,以下是你可以做的。

@RestController
@RequestMapping("/api/v1")
class JobsController {

  @PostMapping("/jobs")
  fun createJob(@RequestParam("use-gpu") useGPU: Boolean?): ResponseEntity<Unit> {

    val headers = HttpHeaders()

    val jobId = "TBD id"

    headers.location =
            MvcUriComponentsBuilder
                    .fromMethodName(JobsController::class.java, "getJob", jobId)
                    .buildAndExpand(jobId)
                    .toUri()

    return ResponseEntity(headers, HttpStatus.CREATED)
  }

  @GetMapping("/job/{jobId}")
  fun getJob(@PathVariable jobId: String) = ... // fetch job
}

在这个示例中(使用 Kotlin 编写,但与 java 类似),基本 URI 为 /api/v1(在类的顶部定义)。使用 MvcUriComponentsBuilder.fromMethodName 调用让 Spring 确定正确的完整 URI。(MvcUriComponentsBuilder 在 4.0 中添加)。

2

2
您所提及的文档已经过时。被取代的最终版RFC 7231 §7.12明确说明该值可以是“相对的”。 - Martin Andersson

1

你的方法看起来不错,但为了保持代码整洁,你可以将代码放在一个自定义的HandlerInterceptor中,只有当出现HTTP 201时才触发。

更多信息请参见这里


谢谢分享。不知为何,在所有肤浅的细节中很难找到关于它的信息。 - Ed Gomoliako

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接