如何在Spring Webflux中获取当前控制器方法的URL?

How to get URL of the current controller method in Spring Webflux?

这个问题类似于 What's the best way to get the current URL in Spring MVC? but for Webflux (it might also be related to 但这个问题更具体所以我希望有一个解决方案)。

我想做的是在 Webflux 控制器中获取当前 URL。例如:

@GetMapping("/greeting")
public Mono<Greeting> greeting() {
   String linkToCurrentResource = .. // How do I get this?
   Greeting greeting = new Greeting("hello",  linkToCurrentResource);
   return Mono.just(greeting);
}

最好linkToCurrentResource 应该理解X-Forwarded-??? headers 如果运行 在负载均衡器后面。所以在这种情况下,我希望 linkToCurrentResource 类似于 https://external-uri.com/api/greeting。如果重要的话,我正在使用 Netty 服务器实现。

您可以使用 ServerHttpRequest 作为方法参数来获取 uri:

@RestController
public class GreetingController {
  @GetMapping("/greeting")
  public Mono<Greeting> getGreeting(ServerHttpRequest serverHttpRequest) {
    return Mono.just(new Greeting("greeting", serverHttpRequest.getURI().toString()));
  }
}

Preferably linkToCurrentResource should understand X-Forwarded-??? headers if running behind a load balancer.

那你可以暴露一个ForwardedHeaderTransformer @Bean.

来自其文档:

Extract values from "Forwarded" and "X-Forwarded-*" headers to override the request URI (i.e. HttpRequest.getURI()) so it reflects the client-originated protocol and address.

@Configuration
open class MvcConfig {
    @Bean
    open fun forwardedHeaderTransformer() = ForwardedHeaderTransformer()
}

这里有一些测试:

@ExtendWith(SpringExtension::class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
        properties = ["server.port=4333"])
class GreetingController2Test {
    @Autowired
    private lateinit var restTemplate: TestRestTemplate

    @Test
    fun `should return uri`() {
        val responseEntity = restTemplate.getForEntity("/greeting", Greeting::class.java)
        val greeting = responseEntity.body!!
        assertEquals("http://localhost:4333/greeting", greeting.uri)
    }

    @Test
    fun `should return uri composed from forwarded-??? headers`() {
        val headers = HttpHeaders()
        headers["X-Forwarded-Host"] = "external-uri.com"
        headers["X-Forwarded-Proto"] = "https"
        headers["X-Forwarded-Prefix"] = "/prefix"

        val httpEntity = HttpEntity(null, headers)
        val responseEntity = restTemplate.exchange("/greeting", HttpMethod.GET, httpEntity, Greeting::class.java)
        val greeting = responseEntity.body!!
        assertEquals("https://external-uri.com/prefix/greeting", greeting.uri)
    }
}

Greeting.kt:

data class Greeting(
        val g: String? = null,
        val uri: String? = null
)