【问题标题】:What is the preferred way to specify an HTTP "Location" Response Header in Spring MVC 3?在 Spring MVC 3 中指定 HTTP“位置”响应标头的首选方法是什么?
【发布时间】:2010-07-23 14:09:57
【问题描述】:

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

据我所知,Spring 只会提供一个“位置”来响应重定向(“redirect:xyz”或 RedirectView),但是在某些情况下,位置应该与实体主体一起发送(例如,作为“201 Created”的结果)。

恐怕我唯一的选择是手动指定它:

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

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

【问题讨论】:

  • 这种方法有什么问题?
  • 这个方法不错,符合http specs

标签: java rest spring-mvc


【解决方案1】:

关键是使用UriComponentsBuilder。 有几种方法可以获取它的实例

  1. 预配置 UriComponentsBuilder 来自 MvcUriComponentsBuilder
  2. UriComponentsBuilder 作为方法的参数注入

预配置 UriComponentsBuilder 来自 MvcUriComponentsBuilder

通过这种方式,您可以获得UriComponentsBuilder,该UriComponentsBuilder 被配置为生成URI,它指向一些具有预定义参数的控制器方法。

这是来自javadocMvcUriComponentsBuilder 示例:

例如,给定这个控制器:

 @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 开始,Location 可以使用 UriComponentBuilder 参数制作并将其设置为返回的 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 指出这一点。

【讨论】:

  • headers.setLocation 行存在语法错误。有一个额外的结束括号,并且“id”不存在。只是想向遇到此答案的其他人指出,这不是一个可以编译的完整方法。不过谢谢你的回答,很有帮助。
  • 很好的答案,您现在甚至可以缩短一点:return ResponseEntity.created(uriComponents.toUri()).build();
  • UriComponentsBuilder 被注入了吗?可以是@Autowired吗?
  • uriComponent 没有在答案中添加端口。我总是得到locahost/resource/1 而不是localhost:9090/resource/1
【解决方案2】:

以下示例来自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}")

【讨论】:

    【解决方案3】:

    这是一个老问题,但如果你想让 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 中添加的)。

    【讨论】:

      【解决方案4】:

      根据:http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30

      应该使用绝对URI:

      Location       = "Location" ":" absoluteURI  
      

      URI 应该正确转义:

      http://www.ietf.org/rfc/rfc2396.txt

      【讨论】:

      • 您所指的文档已过时。被取代的最终 RFC 7231 §7.12 清楚地表明该值可以是relative
      【解决方案5】:

      您的方法看起来不错,但为了保持简洁,您可以将代码放在自定义 HandlerInterceptor 中,例如,该代码仅在存在 HTTP 201 时触发。

      更多信息请参见here

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-09-02
        • 2011-06-29
        • 2021-10-19
        • 2014-09-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多