Skip to content

SetStatus GatewayFilter 工厂

概述

SetStatus GatewayFilter 工厂是 Spring Cloud Gateway 提供的一个重要过滤器,用于修改 HTTP 响应的状态码。这个过滤器在微服务架构中非常有用,特别是在需要统一处理响应状态或者进行状态码转换的场景中。

解决的问题

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

  • 服务降级:当下游服务不可用时,需要返回特定的状态码
  • 统一错误处理:将不同的错误统一转换为标准的 HTTP 状态码
  • API 版本兼容:在 API 升级过程中,需要返回特定的状态码来保持兼容性
  • 安全控制:隐藏真实的服务状态,返回统一的错误码

配置参数

SetStatus 过滤器接受一个参数 status,它必须是一个有效的 Spring HttpStatus

  • 整数值:如 404401500
  • 枚举字符串:如 NOT_FOUNDUNAUTHORIZEDINTERNAL_SERVER_ERROR

基本使用

YAML 配置方式

yaml
spring:
  cloud:
    gateway:
      routes:
        # 使用枚举字符串配置
        - id: setstatusstring_route
          uri: https://example.org
          filters:
            - SetStatus=UNAUTHORIZED
        # 使用整数值配置
        - id: setstatusint_route
          uri: https://example.org
          filters:
            - SetStatus=401

Kotlin 编程式配置

kotlin
import org.springframework.cloud.gateway.route.RouteLocator
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus

@Configuration
class GatewayConfig {

    @Bean
    fun customRouteLocator(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            // 使用枚举配置状态码
            .route("setstatusstring_route") { r ->
                r.path("/api/unauthorized/**")
                    .filters { f ->
                        f.setStatus(HttpStatus.UNAUTHORIZED)
                    }
                    .uri("https://example.org")
            }
            // 使用整数配置状态码
            .route("setstatusint_route") { r ->
                r.path("/api/forbidden/**")
                    .filters { f ->
                        f.setStatus(403)
                    }
                    .uri("https://example.org")
            }
            .build()
    }
}
java
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
            .route("setstatusstring_route", r ->
                r.path("/api/unauthorized/**")
                    .filters(f -> f.setStatus(HttpStatus.UNAUTHORIZED))
                    .uri("https://example.org")
            )
            .route("setstatusint_route", r ->
                r.path("/api/forbidden/**")
                    .filters(f -> f.setStatus(403))
                    .uri("https://example.org")
            )
            .build();
    }
}

保留原始状态码

有时候我们需要在设置新状态码的同时,保留原始的 HTTP 状态码信息。SetStatus 过滤器支持将原始状态码添加到响应头中:

配置原始状态码头部

yaml
spring:
  cloud:
    gateway:
      set-status:
        original-status-header-name: original-http-status
      routes:
        - id: preserve_original_status
          uri: https://example.org
          filters:
            - SetStatus=503

TIP

配置 original-status-header-name 后,原始状态码将会添加到响应头中,便于调试和监控。

实际业务场景

场景 1:服务降级处理

当下游服务不可用时,我们希望返回服务不可用的状态码:

kotlin
@Configuration
class ServiceDegradationConfig {

    @Bean
    fun serviceDownRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("service_degradation") { r ->
                r.path("/api/payment/**")
                    .filters { f ->
                        f.setStatus(HttpStatus.SERVICE_UNAVAILABLE) // 503 状态码
                    }
                    .uri("https://payment-service.com")
            }
            .build()
    }
}

场景 2:API 权限控制

对于需要权限验证的 API,返回统一的未授权状态:

kotlin
@Configuration
class AuthControlConfig {

    @Bean
    fun authRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("admin_api") { r ->
                r.path("/admin/**")
                    .and()
                    .not { it.header("Authorization") } // 没有 Authorization 头部
                    .filters { f ->
                        f.setStatus(HttpStatus.UNAUTHORIZED) // 返回 401
                    }
                    .uri("https://admin-service.com")
            }
            .build()
    }
}

场景 3:维护模式

当系统处于维护状态时,返回维护状态码:

kotlin
@Configuration
class MaintenanceConfig {

    @Bean
    fun maintenanceRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("maintenance_mode") { r ->
                r.path("/**")
                    .and()
                    .header("X-Maintenance-Mode", "true")
                    .filters { f ->
                        f.setStatus(HttpStatus.SERVICE_UNAVAILABLE) // 503 状态码
                        f.setResponseHeader("Retry-After", "3600") // 1小时后重试
                    }
                    .uri("https://maintenance-page.com")
            }
            .build()
    }
}

工作流程

常用状态码示例

完整配置示例

kotlin
@Configuration
class StatusCodeConfig {

    @Bean
    fun statusRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            // 客户端错误 4xx
            .route("bad_request") { r ->
                r.path("/api/invalid/**")
                    .filters { f -> f.setStatus(HttpStatus.BAD_REQUEST) } // 400
                    .uri("https://service.com")
            }
            .route("unauthorized") { r ->
                r.path("/api/auth/**")
                    .filters { f -> f.setStatus(HttpStatus.UNAUTHORIZED) } // 401
                    .uri("https://service.com")
            }
            .route("forbidden") { r ->
                r.path("/api/admin/**")
                    .filters { f -> f.setStatus(HttpStatus.FORBIDDEN) } // 403
                    .uri("https://service.com")
            }
            .route("not_found") { r ->
                r.path("/api/deprecated/**")
                    .filters { f -> f.setStatus(HttpStatus.NOT_FOUND) } // 404
                    .uri("https://service.com")
            }
            // 服务器错误 5xx
            .route("server_error") { r ->
                r.path("/api/error/**")
                    .filters { f -> f.setStatus(HttpStatus.INTERNAL_SERVER_ERROR) } // 500
                    .uri("https://service.com")
            }
            .route("service_unavailable") { r ->
                r.path("/api/maintenance/**")
                    .filters { f -> f.setStatus(HttpStatus.SERVICE_UNAVAILABLE) } // 503
                    .uri("https://service.com")
            }
            .build()
    }
}

高级用法

条件性状态设置

结合其他过滤器实现条件性状态设置:

kotlin
@Configuration
class ConditionalStatusConfig {

    @Bean
    fun conditionalRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("conditional_status") { r ->
                r.path("/api/data/**")
                    .filters { f ->
                        f.circuitBreaker { config ->
                            config.name = "data-service-cb"
                            config.fallbackUri = "forward:/fallback"
                        }
                        .setStatus(HttpStatus.SERVICE_UNAVAILABLE) // 熔断时返回 503
                    }
                    .uri("https://data-service.com")
            }
            .build()
    }
}

自定义状态码处理器

kotlin
@Component
class CustomStatusHandler {

    fun handleCustomStatus(exchange: ServerWebExchange): Mono<Void> {
        val response = exchange.response
        val request = exchange.request

        // 根据请求路径设置不同状态码
        when {
            request.path.value().contains("/v1/") -> {
                response.statusCode = HttpStatus.GONE // 410 - API 版本已弃用
            }
            request.path.value().contains("/beta/") -> {
                response.statusCode = HttpStatus.ACCEPTED // 202 - Beta 版本
            }
            else -> {
                response.statusCode = HttpStatus.OK
            }
        }

        return response.setComplete()
    }
}

注意事项

WARNING

设置状态码后,原始响应的状态码会被覆盖,确保这是你想要的行为。

IMPORTANT

如果需要保留原始状态码信息,务必配置 original-status-header-name 属性。

TIP

在生产环境中,建议记录状态码变更的日志,便于排查问题。

监控和调试

添加日志记录

kotlin
@Configuration
class StatusLoggingConfig {

    @Bean
    fun loggingRoutes(builder: RouteLocatorBuilder): RouteLocator {
        return builder.routes()
            .route("logged_status") { r ->
                r.path("/api/monitored/**")
                    .filters { f ->
                        f.requestRateLimiter { config ->
                            config.rateLimiter = RedisRateLimiter(10, 20)
                            config.keyResolver = IpKeyResolver()
                        }
                        .setStatus(HttpStatus.TOO_MANY_REQUESTS) // 429 - 限流
                    }
                    .uri("https://monitored-service.com")
            }
            .build()
    }
}

健康检查集成

kotlin
@Component
class GatewayHealthIndicator : HealthIndicator {

    override fun health(): Health {
        return try {
            // 检查网关状态
            Health.up()
                .withDetail("status", "Gateway is running")
                .withDetail("setStatusFilter", "active")
                .build()
        } catch (e: Exception) {
            Health.down()
                .withDetail("error", e.message)
                .build()
        }
    }
}

总结

SetStatus GatewayFilter 工厂是一个简单但强大的工具,它可以帮助我们:

  • 🎯 统一状态码管理:在网关层统一处理和转换 HTTP 状态码
  • 🛡️ 安全控制:隐藏真实的服务状态,提供统一的错误响应
  • 🔄 服务降级:在服务不可用时返回合适的状态码
  • 📊 监控友好:通过保留原始状态码便于监控和调试
  • 🚀 灵活配置:支持 YAML 和编程式两种配置方式

通过合理使用 SetStatus 过滤器,可以显著提升微服务架构中的错误处理和用户体验。

Details

扩展阅读