URL Rewriting With Spring Cloud Gateway – 用Spring Cloud Gateway重写URL

最后修改: 2022年 2月 23日

1. Introduction


A common use case for the Spring Cloud Gateway is to act as a facade to one or more services, thus offering clients a simpler way to consume them.


In this tutorial, we’ll show different ways to customize the exposed APIs by rewriting the URLs before sending the request to the backends.


2. Spring Cloud Gateway Quick Recap


The Spring Cloud Gateway project is built on top of the popular Spring Boot 2 and Project Reactor, so it inherits its main treats:

Spring Cloud Gateway项目是建立在流行的Spring Boot 2和Project Reactor之上的,因此它继承了其主要待遇。

  • Low resource usage, thanks to its reactive nature
  • Support for all goodies from the Spring Cloud ecosystem (discovery, configuration, etc.)
  • Easy to extend and/or customize using standard Spring patterns

We’ve already covered its main features in earlier articles, so here we’ll just list the  main concepts:


  • Route: a set of processing steps that a matching incoming request goes through in the Gateway
  • Predicate: A Java 8 Predicate that gets evaluated against a ServerWebExchange.
  • Filters: GatewayFilter instances that can inspect and/or change a ServerWebExchange. The Gateway supports both global filters and per-route ones.

In a nutshell, here’s the processing sequence an incoming request goes through:


  • The Gateway uses the Predicates associated with each route to find which one will handle the request
  • Once a route is found, the request (a ServerWebExchange instance) goes through each configured filter until it is eventually sent to a backend.
  • When the backend sends a response back, or there’s an error (timeout or connection reset, for instance), filters get again a chance to process the response before it is sent back to the client.

3. Configuration-based URL Rewrite


Going back to this article’s main subject, let’s see how to define a route that rewrites the incoming URL before sending it to the backend. For example, suppose that given an incoming request of the form /api/v1/customer/*, the backend URL should be http://v1.customers/api/*. Here, we’re using “*” to represent “anything beyond this point”.

回到本文的主题,让我们看看如何定义一个路由,在将传入的URL发送到后端之前对其进行重写。例如,假设有一个形式为/api/v1/customer/*的传入请求,后端URL应该是http://v1.customers/api/*。在这里,我们用 “*”来表示 “超出这个点的任何东西”。

To create a configuration-based rewrite, we just need to add a few properties to the application’s configuration. Here, we’ll use YAML-based configuration for clarity, but this information could come from any supported PropertySource:


      - id: rewrite_v1
        uri: ${rewrite.backend.uri:http://example.com}
        - Path=/v1/customer/**
        - RewritePath=/v1/customer/(?<segment>.*),/api/$\{segment}

Let’s dissect this configuration. Firstly, we have the route’s id, which is just its identifiers. Next, we have the backend URI given by the uri property. Notice that only hostname/port are considered, as the final path comes from the rewrite logic.


The predicates property defines the conditions that must be met to activate this route. In our case, we use the Path predicate, which takes an ant-like path expression to match against the path of the incoming request.


Finally, the filters property has the actual rewrite logic. The RewritePath filter takes two arguments: a regular expression and a replacement string. The filter’s implementation works by simply executing the replaceAll() method on the request’s URI, using the provided parameters as arguments.


A caveat of the way that Spring handles configuration files is we can’t use the standard ${group} replacement expression, as Spring will think it is a property reference and try to replace its value. To avoid this, we need to add a backslash between the “$” and “{” characters that will be removed by the filter implementation before using it as the actual replacement expression.


4. DSL-based URL Rewrite


While RewritePath is quite powerful and easy to use, it falls short in scenarios where the rewrite rule has some dynamic aspects. Depending on the case, it might still be possible to write multiple rules using predicates as guards for each branch of the rule.


However, if this is not the case, we can create a route using the DSL-based approach. All we need to do is create a RouteLocator bean that implements the route’s logic. As an example, let’s create a simple route that, as before, rewrites the incoming URI using a regular expression. This time, however, the replacement string will be dynamically generated on each request:

然而,如果不是这种情况,我们可以使用基于 DSL 的方法来创建路由。我们需要做的就是创建一个实现路由逻辑的 RouteLocator Bean。作为一个例子,让我们创建一个简单的路由,像以前一样,使用正则表达式重写传入的URI。但这次,替换字符串将在每个请求中动态生成。

public class DynamicRewriteRoute {
    private String backendUri;
    private static Random rnd = new Random();
    public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) {
        return builder.routes()
          .route("dynamicRewrite", r ->
              .filters(f -> f.filter((exchange, chain) -> {
                  ServerHttpRequest req = exchange.getRequest();
                  addOriginalRequestUrl(exchange, req.getURI());
                  String path = req.getURI().getRawPath();
                  String newPath = path.replaceAll(
                    "/api/zip/${zipcode}-" + String.format("%03d", rnd.nextInt(1000)));
                  ServerHttpRequest request = req.mutate().path(newPath).build();
                  exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI());
                  return chain.filter(exchange.mutate().request(request).build());

Here, the dynamic part is just a random number appended to the replacement string. A real-world application might have more complex logic, but the essential mechanism would be the same as shown.


A few remarks about the steps this code went through: Firstly, it calls the addOriginalRequestUrl(), which comes from the ServerWebExchangeUtils class, to store the original URL under the exchange’s attribute GATEWAY_ORIGINAL_REQUEST_URL_ATTR. The value of this attribute is a list to which we’ll append the received URL before going any modification and used internally by the gateway as part of the X-Forwarded-For header’s handling.


Secondly, once we’ve applied the rewrite logic, we must save the modified URL in the GATEWAY_REQUEST_URL_ATTR exchange’s attribute. This step is not directly mentioned in the documentation but ensures that our custom filter plays nicely with other available filters.


5. Testing


To test our rewrite rules, we’ll use standard JUnit 5 classes with a small twist: we’ll spin up a simple server based on Java SDK’s com.sun.net.httpserver.HttpServer class. The server will start on a random port, thus avoiding port conflicts.

为了测试我们的重写规则,我们将使用标准的JUnit 5类,但有一个小插曲:我们将基于Java SDK的com.sun.net.httpserver.HttpServer类旋转起一个简单的服务器。该服务器将在一个随机的端口上启动,从而避免了端口冲突。

The downside of this approach, however, is we have to find out which port was actually assigned to the server and pass it to Spring, so we can use it to set the route’s uri property. Fortunately, Spring provides us with an elegant solution for this problem: @DynamicPropertySource. Here, we’ll use it to start the server and register a property with the bound port’s value:

然而,这种方法的缺点是我们必须找出实际分配给服务器的端口,并将其传递给 Spring,以便我们可以使用它来设置路由的uri属性。幸运的是,Spring为我们提供了解决这一问题的优雅方案:@DynamicPropertySource.在这里,我们将使用它来启动服务器并注册一个具有绑定端口值的属性。

static void registerBackendServer(DynamicPropertyRegistry registry) {
    registry.add("rewrite.backend.uri", () -> {
        HttpServer s = startTestServer();
        return "http://localhost:" + s.getAddress().getPort();

The test handler simply echoes back the received URI in the response body. This allows us to verify that the rewrite rules work as expected. For instance, this is the


void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) {
      .uri("http://localhost:" + localPort + "/v1/customer/customer1")
      .consumeWith((result) -> {
          String body = new String(result.getResponseBody());
          assertEquals("/api/customer1", body);

6. Conclusion


In this quick tutorial, we’ve shown different ways to rewrite URLs using the Spring Cloud Gateway library. As usual, all code is available over on GitHub.

在这个快速教程中,我们展示了使用Spring Cloud Gateway库重写URL的不同方法。像往常一样,所有的代码都可以在GitHub上找到