【问题标题】:Is there a means to set the host & port for the Spring HATEOAS `ControllerLinkBuilder`?有没有办法为 Spring HATEOAS `ControllerLinkBuilder` 设置主机和端口?
【发布时间】:2014-08-02 12:10:24
【问题描述】:

Spring HATEOAS 提供了方便的ControllerLinkBuilder 来创建到控制器方法的链接,这些链接将作为hrefs 添加到返回给客户端的JSON/XML 中。例如:

resource.add(linkTo(methodOn(FooController.class)
    .findFoo(entity.getClient().getId()))
    .withRel("show"));

... 可能会生成类似于以下内容的 JSON:

{
  "name":"foo", 
  "links":[
    {"rel":"show","href":"http://111.11.11.111:28080/foos/1"}
  ]
}

不过……

我倾向于通过反向代理访问我的服务。我想大多数人可能会。这让我可以在不同的端口上运行多个服务,但让我可以通过相同的基本 URL 访问它们。不幸的是,通过代理访问意味着 Spring HATEOAS 生成的 URL 不是对访问资源有效的 URL。

现在我可以对链接进行硬编码,但这相当脆弱。让 ControllerLinkBuilder 根据我的控制器 @RequestMapping 配置生成 URL 对我来说很有价值,因为它避免了我的链接与现实不同步的风险。

所以我想知道是否有一个属性可以用来强制主机和端口值。我正在使用 Spring Boot,因此理想情况下,我可以将一个属性添加到每个环境中的 application.properties 文件中。

注意:

由于这个问题似乎是由 Spring 中的错误引起的,我应该指出我使用的是 Spring Boot 1.0.2.RELEASE。

【问题讨论】:

  • 通过ControllerLinkBuilder 的代码,看起来它应该使用X-Forwarded-Host 标头来生成链接。我会做更多的测试,但很可能是我的链接不正确,因为我的反向代理没有正确设置标题。
  • 如果您将嵌入式 Tomcat 与 Spring Boot 一起使用,它将默认设置一个阀门来填充标头。如果您要部署到现有容器,则必须自己设置阀门。
  • 我使用的是 Spring Boot 嵌入式 Tomcat。我现在已经确认设置了一个没有端口的X-Forwarded-Host 标头。主机部分设置正确,所以我正在尝试调试 ControllerLinkBuilder 以确定为什么它似乎附加了 server.port 值而不是关闭它。
  • 这看起来像是ServletUriComponentsBuilder 中的一个错误,它也已复制到ControllerLinkBuilder 的代码中。我会发布一个包含详细信息的答案。

标签: spring-boot spring-hateoas


【解决方案1】:

Spring-Boot 使用旧版本的 Spring-HATEOAS,我认为是 .11 他们添加了对 X-Forwarded-Port 和 X-Forwarded-Ssl 标头的支持,只需将该显式版本添加到您的 POM 中,如果您代理正在做正确的事情并添加那些你应该很好的标题。

此外,如果您的代理可以配置为不重写 HOST 标头,则内置控制器链接构建器将正常工作。

【讨论】:

  • 不幸的是,这个问题是 Spring MVC (jira.spring.io/browse/SPR-11872) 中的一个错误,它在 HATEOAS 中也有重复(复制粘贴重用的一点点;))。该错误存在于最新源中。
【解决方案2】:

我最初提出的问题的纯粹答案似乎涉及编写我自己的ControllerLinkBuilder 实现,它可以选择基于我设置的环境变量构建 URL。我可以这样做。

但是,我试图强制 URL 的原因是 ControllerLinkBuilder 中有一个错误。值得注意的是,这个错误是从ServletUriComponentsBuilder复制的代码中的错误。

String scheme = request.getScheme();

// The port number retrieved here is the port set by server.port
int port = request.getServerPort();
String host = request.getServerName();

String header = request.getHeader("X-Forwarded-Host");

if (StringUtils.hasText(header)) {
    String[] hosts = StringUtils.commaDelimitedListToStringArray(header);
    String hostToUse = hosts[0];
    if (hostToUse.contains(":")) {
        String[] hostAndPort = StringUtils.split(hostToUse, ":");
        host  = hostAndPort[0];

        // Note that the port is set if there is a ":" in the address.
        port = Integer.parseInt(hostAndPort[1]);
    }
    else {
        host = hostToUse;
    }
}

ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder();
builder.scheme(scheme);
builder.host(host);

// Here lies the bug...
if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) {
    builder.port(port);
}

基本上,仅当server.port 不是80 或443 时才设置端口,而不是基于用于请求的端口。这意味着如果X-Forwarded-Host 正在使用该方案的默认端口(因此在“:”之后没有任何内容),那么将使用应用程序端口而不是默认端口。

【讨论】:

    猜你喜欢
    • 2019-05-21
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 2022-10-07
    • 1970-01-01
    • 2019-06-12
    相关资源
    最近更新 更多