Skip to content

RewriteLocationResponseHeader 网关过滤器工厂

概述

RewriteLocationResponseHeader 是 Spring Cloud Gateway 提供的一个网关过滤器工厂,专门用于修改 HTTP 响应中的 Location 头部值。这个过滤器在微服务架构中特别有用,可以帮助我们隐藏后端服务的具体实现细节,向客户端提供统一的访问入口。

解决的业务问题

在微服务架构中,经常会遇到以下场景:

  1. 后端服务重定向暴露内部地址:后端服务返回的 Location 头包含内部服务地址
  2. 版本管理问题:不同版本的 API 在 Location 头中暴露了版本信息
  3. 域名统一性:需要将所有重定向都指向统一的网关域名

IMPORTANT

这个过滤器主要处理 HTTP 重定向响应(如 301、302 状态码),确保客户端收到的重定向地址是经过网关统一处理的,而不是后端服务的内部地址。

参数说明

Mode 模式

Mode 参数控制如何处理路径中的版本信息:

  • NEVER_STRIP:永不剥离版本信息,即使原始请求路径不包含版本
  • AS_IN_REQUEST(默认):仅当原始请求路径不包含版本时才剥离版本
  • ALWAYS_STRIP:始终剥离版本信息,即使原始请求路径包含版本

其他参数

  • locationHeaderName:要修改的响应头名称(通常是 "Location")
  • hostValue:用于替换响应 Location 头中的 host:port 部分
  • protocols:协议匹配的正则表达式,默认为 https?|ftps?

配置示例

YAML 配置方式

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: rewritelocationresponseheader_route
          uri: http://example.org
          filters:
            - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,

Java 配置方式

kotlin
@Configuration
@EnableConfigurationProperties
class GatewayConfig {

    @Bean
    fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("rewrite_location_route") { r ->
                r.path("/api/**")
                    .filters { f ->
                        f.rewriteLocationResponseHeader(
                            RewriteLocationResponseHeaderGatewayFilterFactory.Mode.AS_IN_REQUEST,
                            "Location",
                            null,  // 使用默认的 Host 头值
                            "https?|ftps?"
                        )
                    }
                    .uri("http://backend-service:8080")
            }
            .build()
    }
}
java
@Configuration
@EnableConfigurationProperties
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("rewrite_location_route", r -> r.path("/api/**")
                .filters(f -> f.rewriteLocationResponseHeader(
                    RewriteLocationResponseHeaderGatewayFilterFactory.Mode.AS_IN_REQUEST,
                    "Location",
                    null,  // 使用默认的 Host 头值
                    "https?|ftps?"
                ))
                .uri("http://backend-service:8080"))
            .build();
    }
}

实际业务场景

场景 1:隐藏后端服务地址

假设我们有一个用户管理服务,当创建用户成功后,后端服务返回 201 Created 状态码和 Location 头:

场景 2:版本管理

kotlin
// 原始后端响应
// Location: http://user-service.internal:8080/v2/users/12345

// 经过网关处理后(AS_IN_REQUEST 模式,原请求无版本)
// Location: https://api.example.com/users/12345

// 经过网关处理后(NEVER_STRIP 模式)
// Location: https://api.example.com/v2/users/12345

详细配置示例

完整的路由配置

kotlin
@Component
class CustomGatewayFilterFactory : AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config>() {

    data class Config(
        var mode: String = "AS_IN_REQUEST",
        var locationHeaderName: String = "Location",
        var hostValue: String? = null,
        var protocols: String = "https?|ftps?"
    )

    override fun apply(config: Config): GatewayFilter {
        return GatewayFilter { exchange, chain ->
            chain.filter(exchange).then(
                Mono.fromRunnable {
                    val response = exchange.response
                    val headers = response.headers

                    // 处理 Location 头部重写逻辑
                    val locationValues = headers[config.locationHeaderName]
                    if (!locationValues.isNullOrEmpty()) {
                        val originalLocation = locationValues[0]
                        val rewrittenLocation = rewriteLocation(originalLocation, config, exchange)

                        // 更新 Location 头部
                        headers.remove(config.locationHeaderName)
                        headers.add(config.locationHeaderName, rewrittenLocation)
                    }
                }
            )
        }
    }

    private fun rewriteLocation(
        originalLocation: String,
        config: Config,
        exchange: ServerWebExchange
    ): String {
        // 实现 Location 重写逻辑
        val request = exchange.request
        val hostValue = config.hostValue ?: request.headers.getFirst("Host")

        // 解析和重写逻辑...
        return "https://$hostValue/api/users/12345"
    }
}

多环境配置

yaml
spring:
  cloud:
    gateway:
      routes:
        # 开发环境
        - id: dev_rewrite_location
          uri: http://dev-backend:8080
          predicates:
            - Host=dev-api.example.com
          filters:
            - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, dev-api.example.com,

        # 生产环境
        - id: prod_rewrite_location
          uri: http://prod-backend:8080
          predicates:
            - Host=api.example.com
          filters:
            - RewriteLocationResponseHeader=ALWAYS_STRIP, Location, api.example.com, https

实际应用示例

电商订单服务

kotlin
@RestController
@RequestMapping("/orders")
class OrderController {

    @PostMapping
    fun createOrder(@RequestBody order: CreateOrderRequest): ResponseEntity<Order> {
        val createdOrder = orderService.createOrder(order)

        // 后端服务返回的 Location 可能是内部地址
        // Location: http://order-service.internal:8080/v1/orders/ORD123456

        return ResponseEntity.created(
            URI.create("/orders/${createdOrder.id}")
        ).body(createdOrder)
    }
}

经过网关的 RewriteLocationResponseHeader 过滤器处理后:

原始: Location: http://order-service.internal:8080/v1/orders/ORD123456
处理后: Location: https://api.shop.com/orders/ORD123456

注意事项

WARNING

使用此过滤器时需要注意以下几点:

  1. 协议匹配:确保 protocols 参数能正确匹配目标协议
  2. 路径映射:注意路径重写可能影响后续的路由匹配
  3. 性能考虑:频繁的字符串操作可能影响性能

TIP

建议在测试环境中充分验证重写规则,确保不会破坏应用的正常重定向流程。

常见问题

Details

为什么 Location 头没有被重写?

  1. 检查 protocols 参数是否匹配响应中的协议
  2. 确认响应确实包含 Location 头部
  3. 验证过滤器是否正确配置在路由中
Details

如何处理相对路径的 Location?过滤器主要处理绝对路径的 Location 头,相对路径通常不需要重写,因为客户端会基于当前请求的域名来解析。

最佳实践

  1. 统一域名管理:在生产环境中使用固定的 hostValue 确保一致性
  2. 版本策略:根据 API 版本管理策略选择合适的 Mode
  3. 监控和日志:添加适当的日志记录来追踪重写过程
  4. 测试覆盖:确保不同场景下的重写逻辑都经过测试

通过合理使用 RewriteLocationResponseHeader 过滤器,我们可以有效地隐藏后端服务的实现细节,为客户端提供统一、简洁的 API 接口体验。