go-zero icon indicating copy to clipboard operation
go-zero copied to clipboard

Feature Request: Support route rewrite in http gateway mappings

Open maomao94 opened this issue 8 months ago • 1 comments

Currently, the http gateway in go-zero only supports Path and Method in Mappings, but there is no way to rewrite the route before forwarding to the upstream service.

In many real-world cases, the public API path exposed by the gateway is different from the actual path required by the downstream service. Supporting Rewrite would make the gateway much more flexible.

Example
Upstreams:
  - Name: external-cable
    Http:
      Target: localhost:19103
      Timeout: 15000
    Mappings:
      - Method: POST
        Path: /api/abandoned/external/cable/workList
        Rewrite: /cable/workList

With this configuration:

The gateway exposes:

POST /api/abandoned/external/cable/workList

But it rewrites and forwards the request to:

POST http://localhost:19103/cable/workList

maomao94 avatar Aug 30 '25 04:08 maomao94

这个功能近期会考虑开发。不过要先总体设计下。 计划是yaml的使用伪代码会在这个issue下面讨论下。 先捋一捋大致需要实现的功能点。 比如根据host/qury string/url 各种维度的重写,中间件的引用上。

概述

本设计旨在为 go-zero 设计新的 HTTP Gateway 组件,采用路由匹配 + 过滤器链(Filter Chain)设计理念,提供灵活、声明式的路由与请求/响应处理能力。所有功能通过 YAML 配置声明,支持细粒度控制请求生命周期。


核心概念

1. Upstream(上游服务)

定义后端服务目标。

Upstreams:
  - Name: userservice
    Http:
      Target: localhost:8080
      Timeout: 5000  # ms

2. Mapping(路由映射)

定义匹配规则 + 处理链。一个 Mapping = 一组 Match 规则 + 一组 Filters。

Mappings:
  - Match:
      Path: /api/v1/users/:id
      Method: GET
      Header:
        X-Version: v1
    Filters:
      - StripPrefix=2
      - AddResponseHeader=X-Handled-By, go-zero-gateway

3. Match(匹配器)

用于判断当前请求是否命中该路由。支持多条件 AND 逻辑组合。

4. Filters(过滤器)

在请求转发前/响应返回前执行操作。支持链式调用,顺序执行。

支持的 Match 类型(路由匹配条件)

1. Path(路径匹配)

支持通配符和路径参数提取。

Match:
  Path: /api/v1/users/:id  # 提取 :id 供 Filter 使用

2. Method(HTTP 方法匹配)

支持单个或多个方法。

Match:
  Method: GET
# 或
Match:
  Method: [GET, POST]

3. Query(查询参数匹配)

支持参数存在性检查或值匹配。

Match:
  Query:
    token:                 # 存在 token 参数即可
    debug: "true"          # debug 参数值必须为 "true"

4. Host(主机名匹配)

支持通配符匹配。

Match:
  Host: "*.example.com"
# 或
Match:
  Host: ["api.example.com", "admin.example.com"]

5. 时间范围匹配

用于维护窗口或灰度发布。

Match:
  After: "2025-01-01T00:00:00+08:00"
  Before: "2025-12-31T23:59:59+08:00"

6. 权重匹配(灰度/AB测试)

用于流量分配。

Match:
  Weight:
    Group: "experiment-A"
    Value: 30  # 30% 流量

7. Client IP 匹配(RemoteAddr)

支持 CIDR 格式。

Match:
  RemoteAddr: "192.168.1.0/24"

Filters 过滤器

请求阶段 Filters

Filter 名称 参数 说明
AddRequestHeader key, value 新增请求头
SetRequestHeader key, value 设置或替换指定名称的请求头(若已存在则覆盖)
RemoveRequestHeader key 删除指定名称的请求头
AddRequestParameter key, value 新增查询参数(URL 中的 query string)
RemoveRequestParameter key 删除指定名称的查询参数
PrefixPath /prefix 在原始路径前添加指定前缀(如:/api/prefix/api
RewritePath regex, replacement 使用正则表达式重写路径(支持捕获组替换)
StripPrefix count (整数) 移除路径前 N 段前缀(如:/v1/user/profilecount=2/profile

响应阶段 Filters

Filter 名称 参数 说明
AddResponseHeader key, value 新增响应头
SetResponseHeader key, value 设置或替换指定名称的响应头(若已存在则覆盖)
RemoveResponseHeader key 删除指定名称的响应头
SetStatus code(如 401, 500 强制设置 HTTP 响应状态码,可触发短路响应(直接返回,不再继续下游处理)

注意:所有响应阶段过滤器均支持 URI 模板变量引用,例如:${id}${path} 等,可在值中动态插入请求上下文中的变量值。

自定义 Match 和 Filter 的 Go 接口规范

为支持扩展性,提供标准接口,允许开发者实现自定义逻辑。

1. 自定义 Match 接口

// MatchPredicate 定义匹配器接口
type MatchPredicate interface {
    // Match 判断请求是否匹配,返回 true/false
    // ctx: 上下文,可用于传递提取的变量(如 Path 参数)
    // req: 原始 HTTP 请求
    Match(ctx context.Context, req *http.Request) (bool, context.Context, error)
}

// 示例:自定义 JWT 验证匹配器
type JWTMatchPredicate struct {
    Secret string
}

func (j *JWTMatchPredicate) Match(ctx context.Context, req *http.Request) (bool, context.Context, error) {
    token := req.Header.Get("Authorization")
    claims, err := parseJWT(token, j.Secret)
    if err != nil {
        return false, ctx, nil // 不匹配,不报错
    }
    // 将解析后的 claims 注入上下文,供后续 Filter 使用
    ctx = context.WithValue(ctx, "jwt_claims", claims)
    return true, ctx, nil
}

全局默认 Filters 配置

支持为所有路由设置默认过滤器,减少重复配置。

配置方式

在根级别添加 DefaultFilters 字段:

# gateway.yaml
DefaultFilters:
  - AddResponseHeader=X-Powered-By, go-zero-gateway
  - AddResponseHeader=Server, MyCompany/1.0
  - CustomFilter=request_id_injector  # 注入全局请求 ID

Upstreams:
  - Name: userservice
    Http:
      Target: localhost:8080
    Mappings:
      - Match:
          Path: /api/v1/users
        # 此路由自动继承上述 3 个 Filter
        Filters:
          - StripPrefix=2  # 追加特定 Filter

执行顺序

  • 全局 Filter 先于路由局部 Filter 执行
  • 可在局部 Filter 中覆盖全局行为(如再次 SetResponseHeader 会覆盖全局设置)
  • 全局 Filter 适用于:安全头、追踪 ID、通用日志、CORS 等横切关注点

完整配置示例

DefaultFilters:
  - AddResponseHeader=X-Gateway-Version, v2.0
  - CustomFilter=trace_id_injector

Upstreams:
  - Name: userservice
    Http:
      Target: localhost:8080
      Timeout: 5000

    Mappings:
      # 示例1:JWT 认证 + 路径重写
      - Match:
          Path: /v2/users/:id
          Method: GET
        Filters:
          - CustomFilter=jwt_auth
          - RewritePath=/v2/users/(?<id>.+), /user/profile/$\{id}

      # 示例2:灰度发布 + 全局 Filter 继承
      - Match:
          Path: /api/v1/experiment
          Weight:
            Group: "canary"
            Value: 10
        Filters:
          - AddRequestHeader=X-Experiment, true

      # 示例3:IP 白名单 + 自定义 Filter
      - Match:
          Path: /admin/**
          RemoteAddr: "10.0.0.0/8"
        Filters:
          - CustomFilter=admin_audit_log
          - StripPrefix=1

全局 CORS 配置 (GlobalCors)

提供声明式、细粒度的跨域资源共享(CORS)控制。

配置结构

在根级别添加 GlobalCors 字段,支持按路径模式配置不同 CORS 策略。

GlobalCors:
  AddToSimpleUrlHandlerMapping: true
  CorsConfigurations:
    '/*': # 路径匹配模式,使用 Gin 风格的通配符
      AllowedOrigins: ["https://myapp.com"]
      AllowedMethods: [GET, POST, PUT, DELETE, OPTIONS]
      AllowedHeaders: ["*"]
      ExposedHeaders: ["X-Request-ID"]
      AllowCredentials: true
      MaxAge: 3600

配置项说明

配置项 类型 说明 备注
AllowedOrigins 字符串数组 允许跨域的源 * 表示允许所有源(不推荐在生产环境使用)
AllowedMethods 字符串数组 允许的 HTTP 方法 必须包含 OPTIONS 以支持预检请求
AllowedHeaders 字符串数组 允许客户端发送的请求头 * 表示允许所有
ExposedHeaders 字符串数组 允许浏览器访问的响应头 -
AllowCredentials 布尔值 是否允许发送 Cookie 或认证头 若为 true,AllowedOrigins 不能为 *
MaxAge 整数(秒) 预检请求(Preflight)结果的缓存时间 -

高级选项

为处理未被任何路由匹配的 OPTIONS 预检请求(常见于前端开发),提供兜底开关:

GlobalCors: AddToSimpleUrlHandlerMapping: true # 默认 false CorsConfigurations: '/*': AllowedOrigins: ["https://myapp.com"] AllowedMethods: [GET, POST, OPTIONS]

实现机制

网关在路由匹配之前,优先检查 CORS 配置。

  • 对于 OPTIONS 预检请求,直接根据配置返回 200 OK 及 CORS 头,不转发到后端服务。
  • 对于普通请求,在响应阶段自动注入 Access-Control-* 头。

上游服务 (Upstreams)

Upstreams:
  - Name: userservice
    Http:
      Target: localhost:8080
      Timeout: 5000
    Mappings:
      # 示例1:JWT 认证 + 路径重写
      - Match:
          Path: /v2/users/:id
          Method: GET
        Filters:
          - CustomFilter=jwt_auth
          - RewritePath=/v2/users/(?<id>.+), /user/profile/$\{id}

      # 示例2:灰度发布 + 全局 Filter 继承
      - Match:
          Path: /api/v1/experiment
          Weight:
            Group: "canary"
            Value: 10
        Filters:
          - AddRequestHeader=X-Experiment, true

      # 示例3:IP 白名单 + 自定义 Filter
      - Match:
          Path: /admin/*
          RemoteAddr: "10.0.0.0/8"
        Filters:
          - CustomFilter=admin_audit_log
          - StripPrefix=1

路由映射 (Mappings) 详解

配置项 说明
Match 定义请求匹配条件
Path 请求路径。支持正则表达式和占位符 :id
Method HTTP 方法(如 GET, POST)
Weight 用于灰度发布或流量分片。Group 指定分组,Value 指定权重
RemoteAddr 根据客户端 IP 地址进行匹配,支持 CIDR 格式(如 10.0.0.0/8)
Filters 在匹配的请求上应用的过滤器
CustomFilter 应用自定义过滤器
RewritePath 重写请求路径。格式为 旧路径, 新路径(?<id>.+) 是捕获组,$\{id} 是替换引用
AddRequestHeader 向请求头中添加键值对
StripPrefix 移除请求路径的前缀。StripPrefix=1 表示移除第一个路径段

更多Example

filter

新增http header

AddRequestHeader命令,必须接收两个参数,key/value

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - AddRequestHeader =X-Request-red, blue

新增查询字符串

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - AddRequestParameter=red, blue

新增http 响应头

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - AddResponseHeader=X-Response-Red, Blue

给path加上前缀

这会在所有匹配请求的路径前加上 /mypath 前缀。因此,对 /hello 的请求会被发送至 /mypath/hello。

这点和现有的Prefix字段有点重合,目前的想法是慢慢不使用Pefix字段。

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - PrefixPath=/mypath

删除请求http header

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - RemoveRequestHeader=X-Request-Foo

删除响应http header

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - RemoveResponseHeader=X-Response-Foo

删除请求参数(query string)

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - RemoveRequestParameter=red

替换请求http header里面的值

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - SetRequestHeader=X-Request-Red, Blue

替换响应http header里面的值

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - SetResponseHeader=X-Response-Red, Blue

响应http 状态码

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - SetStatus=401

重写path

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /users
      - Method: POST
        Path: /users/create
        Filters:
        - RewritePath=/red(?<segment>/?.*), $\{segment}

去掉路径前缀

访问网关的/api/v1/users/create,到后端服务就变成/users/create

Upstreams:
  - Name: userservice  # 可选,如果未指定则使用 target
    Http:
      Target: localhost:8080
      #Prefix: /api/v1  # 可选
      Timeout: 3000    # 单位为毫秒,默认值 3000
    Mappings:
      - Method: GET
        Path: /api/v1/users
      - Method: POST
        Path: /api/v1/users/create
        Filters:
        - StripPrefix=2

guonaihong avatar Aug 31 '25 12:08 guonaihong