从 Spring Boot 1.5.9 升级到 2.2.6 时,HATEOAS 链接中不考​​虑上下文路径

Context Path not considered in HATEOAS links when upgrading from Spring Boot 1.5.9 to 2.2.6

我最近将基于 Spring Boot 的旧应用程序从版本 1.5.9 升级到 2.2.6。

不幸的是,升级后,HATEOAS 生成的 url 发生了变化。基本上,现在链接中缺少上下文路径。

示例:

Before: https://domain.test.com/service/api/endpoint
Now:    https://domain.test.com/service/endpoint

现在我在应用程序属性中使用以下配置:

server.servlet.context-path: /api
server.forward-headers-strategy: FRAMEWORK
spring.data.rest.basePath: /api

(使用 none,主机完全不同(因为 x-forwarded-host。我也尝试过使用本机,但行为相同)

我还创建了一个 ForwardedHeaderFilter bean。

    @Bean
    public ForwardedHeaderFilter forwardedHeaderFilter() {
        return new ForwardedHeaderFilter();
    }

有什么办法可以绕过这个问题吗?我做错了什么吗?

一种替代方法是调整 api 网关,但从业务流程的角度来看,这会非常复杂,因此我更喜欢更技术性的方法。

谢谢!

作为临时解决方案,在我有时间真正深入了解之前,我创建了一个新的实用程序 class,负责调整路径:

public class LinkUtil {
    private LinkUtil() {
    }

    @SneakyThrows
    public static <T> Link linkTo(T methodOn) {
        String rawPath = WebMvcLinkBuilder.linkTo(methodOn).toUri().getRawPath();
        rawPath = StringUtils.remove(rawPath, "/service");
        BasicLinkBuilder basicUri = BasicLinkBuilder.linkToCurrentMapping().slash("/api").slash(rawPath);

        return new Link(basicUri.toString());
    }
}

其中 /api 是上下文路径。

然后我这样使用它:

Link whateverLink = LinkUtil.linkTo(methodOn(WhateverClass.class).whateverMethod(null)).withRel("whatever-rel));

@LoolKovski 的临时解决方案依赖于现有的 ServletRequest,因为 #linkToCurrentMapping。如果您也需要消除该限制,请使用以下代码:

public class LinkUtil {
    private LinkUtil() {
    }

    @SneakyThrows
    public static <T> Link linkTo(T methodOn) {
        var originalLink = WebMvcLinkBuilder.linkTo(methodOn);
        var rawPathWO = StringUtils.remove(originalLink.toUri().getRawPath(), "/service");
        return originalLink.withHref("/api" + rawPathWO);
    }
}

实际上,在我的例子中,链接是在 RestController bean 之一的初始化过程中生成的,因此我的真实代码类似于以下代码。 我之前不需要切断其他路径部分,只需要在前面加上配置的上下文路径。

@RestController
public class ExampleController implements ServletContextAware {

@Override
public void setServletContext(ServletContext servletContext) {
    final var executor = Executors.newSingleThreadExecutor();
    executor.submit(() -> {
        someRepository.getExamples().forEach((name, thing) -> {
            Link withRel = linkTo(methodOn(ExampleController.class).getElement(null, name, null))
                    .withSelfRel();
            withRel = withRel.withHref(servletContext.getContextPath() + withRel.toUri().getRawPath());
            thing.add(withRel);
        });
        executor.shutdown();
    });
}

@RequestMapping(path = "/{name}/", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<Example> getElement(ServletWebRequest req, @PathVariable("name") String name, Principal principal) {
    [...]
}