Spring Cloud Gateway 重写 URL

目录

1、简介

[2、Spring Cloud Gateway 快速回顾](#2、Spring Cloud Gateway 快速回顾)

[3、基于配置的 URL 重写](#3、基于配置的 URL 重写)

[4、基于 DSL 的 URL 重写](#4、基于 DSL 的 URL 重写)

5、测试

6、总结


1、简介

Spring Cloud Gateway 的常见用例是作为一个网关,代理一个或多个服务,从而为客户端提供更简单的消费方式。

本文将带你了解如何在将请求发送到后端之前,通过重写 URL 来自定义暴露的 API 的不同方式。

2、Spring Cloud Gateway 快速回顾

Spring Cloud Gateway 项目是在流行的 Spring Boot 2 和 Project Reactor 的基础上构建的,因此继承了其主要特性:

  • 响应式,资源占用低
  • 支持 Spring Cloud 生态系统的所有功能(服务发现、配置等)
  • 使用标准 Spring 模式轻松扩展和/或定制

这里只列出它的主要概念,更多详细信息请参阅 中文文档:

  • Route:路由,在网关中,匹配的传入请求会经历一系列的处理步骤。
  • Predicate:针对 ServerWebExchange 进行评估的 Java 8 Predicate。
  • Filters:可以检查、更改 ServerWebExchange 的 GatewayFilter 实例。网关支持全局 Filter 和按路由的 Filter。

简而言之,接收请求的处理顺序如下:

  • 网关使用与每条路由相关的 Predicate 来查找哪条路由可以处理请求。
  • 一旦找到路由,请求(ServerWebExchange 实例)就会通过每个配置的 Filter,直到最终发送到后端。
  • 当后端发送回响应或出现错误(例如超时或连接重置)时,Filter 可以再次处理响应,然后再将其发送回客户端。

3、基于配置的 URL 重写

回到本文的主题,让我们看看如何定义一个路由,在将请求发送到后端之前重写传入的 URL。例如,假设输入的请求格式为 /api/v1/customer/*,后端 URL 应为 http://v1.customers/api/\*。这里,使用 "*" 来表示 "在此之后的任何内容"。

只需在应用的配置中添加几个属性,就可以创建基于配置的重写。为了更好的可读性,使用基于 YAML 的配置,这些信息可以来自任何受支持的 PropertySource:

复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: rewrite_v1
        uri: ${rewrite.backend.uri:http://example.com}
        predicates:
        - Path=/v1/customer/**
        filters:
        - RewritePath=/v1/customer/(?<segment>.*),/api/$\{segment}

分析一下这个配置。首先,路由有一个 id,这只是它的标识符。其次,uri 属性给出了后端 URI。注意,这里只考虑了主机名/端口,因为最终路径来自重写逻辑。

predicates 属性定义了激活此路由必须满足的条件。在本例中,我们使用了 Path predicate,它使用类似于 Ant 的路径表达式来匹配传入请求的路径。

最后,filters 属性具有实际的重写逻辑。RewritePath Filter 需要两个参数:正则表达式和替换字符串。Filter 的实现方式是,使用提供的参数作为参数,在请求的 URI 上执行 replaceAll() 方法。

Spring 处理配置文件的方式有一个注意事项,那就是不能使用标准的 {group} 替换表达式,因为 Spring 会认为这是一个属性引用,并尝试替换其值。为了避免这种情况,需要在 和 { 字符之间添加反斜杠,Filter 会在使用它作为实际替换表达式之前移除反斜杠。

4、基于 DSL 的 URL 重写

虽然 RewritePath 非常强大且易于使用,但在重写规则具有某些动态特性的情况下,它就显得力不从心了。根据情况,可以使用基于 DSL 的方法创建路由。我们需要做的就是创建一个 RouteLocator Bean 来实现路由的逻辑。

举个例子,创建一个简单的路由,和上面一样,使用正则表达式重写传入的 URI。但这次,替换字符串将在每次请求时动态生成:

复制代码
@Configuration
public class DynamicRewriteRoute {
    
    @Value("${rewrite.backend.uri}")
    private String backendUri;
    private static Random rnd = new Random();
    
    @Bean
    public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) {
        return builder.routes()
          .route("dynamicRewrite", r ->
             r.path("/v2/zip/**")
              .filters(f -> f.filter((exchange, chain) -> {
                  ServerHttpRequest req = exchange.getRequest();
                  addOriginalRequestUrl(exchange, req.getURI());
                  String path = req.getURI().getRawPath();
                  String newPath = path.replaceAll(
                    "/v2/zip/(?<zipcode>.*)", 
                    "/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());
              }))
              .uri(backendUri))
          .build();
    }
}

在这里,动态部分只是将一个随机数添加到替换字符串中。在实际应用中可能会有更复杂的逻辑,但基本机制如下所示。

首先,它调用了 addOriginalRequestUrl() 方法,该方法来自 ServerWebExchangeUtils 类,用于将原始 URL 存储在 exchange attribute GATEWAY_ORIGINAL_REQUEST_URL_ATTR 下。该属性的值是一个 List,我们将在进行任何修改之前将接收到的 URL 追加到该 List 中,并且网关在处理 X-Forwarded-For Header 时会内部使用该 List。

其次,应用重写逻辑后,必须将修改后的 URL 保存在 GATEWAY_REQUEST_URL_ATTR exchange attribute 中。这一步在文档中没有直接提及,但可以确保我们的自定义 Filter 与其他可用 Filter 良好地协同工作。

5、测试

使用标准的 JUnit 5 来测试我们的重写规则。

稍加改动:使用基于 Java SDK 的 com.sun.net.httpserver.HttpServer 类启动一个简单的服务器。使用随机端口,从而避免端口冲突。

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

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

测试 Handler 只需在响应体中回传接收到的 URI 即可。这样就能验证重写规则是否按预期运行。

复制代码
@Test
void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) {
    webClient.get()
      .uri("http://localhost:" + localPort + "/v1/customer/customer1")
      .exchange()
      .expectBody()
      .consumeWith((result) -> {
          String body = new String(result.getResponseBody());
          assertEquals("/api/customer1", body);
      });
}

6、总结

本文介绍了在 Spring Cloud Gateway 中如何通过配置文件和 DSL 来重写路由 URL。

相关推荐
苏渡苇14 小时前
Spring Insight 里如何把 Span 画成瀑布时间线
后端·spring·spring cloud·springboot·监控·apm
叶总没有会14 小时前
Nacos—注册中心
spring cloud·微服务
苏渡苇2 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring·spring cloud·系统监控·apm
叶总没有会2 天前
微服务--分布式基础
java·spring·spring cloud·微服务
孙启超2 天前
【AI开发之Rust】第 7 课:错误处理 —— panic、Result 与 `?`
人工智能·分布式·后端·爬虫·spring cloud·架构·rust
行百里er3 天前
Spring Insight 里如何把 Span 收成一条链路
spring boot·spring cloud·监控
砍材农夫4 天前
spring|spring web|拦截器、过滤器、aop
java·spring boot·spring·spring cloud
weixin_BYSJ19874 天前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
行百里er5 天前
Spring Insight 里如何画出服务拓扑
spring boot·spring cloud·监控