路由

路由器将端点组织在 URL 前缀下,并应用共享的中间件。端点定义 HTTP 处理器。

架构

flowchart TB
    S[http.service
:8080] --> R1[http.router
/api] S --> R2[http.router
/admin] S --> ST[http.static
/] R1 --> E1[GET /users] R1 --> E2[POST /users] R1 --> E3["GET /users/{id}"] R2 --> E4[GET /stats] R2 --> E5[POST /config]

条目通过 metadata 引用父级:

  • 路由器:meta.server: app:gateway
  • 端点:meta.router: app:api

路由器配置

- name: api
  kind: http.router
  meta:
    server: gateway
  prefix: /api/v1
  middleware:
    - cors
    - compress
  options:
    cors.allow.origins: "*"
  post_middleware:
    - endpoint_firewall
字段 类型 说明
meta.server Registry ID 父级 HTTP 服务器
prefix string 所有路由的 URL 前缀
middleware []string 匹配前中间件
options map 中间件选项
post_middleware []string 匹配后中间件
post_options map 匹配后中间件选项

端点配置

- name: get_user
  kind: http.endpoint
  meta:
    router: api
  method: GET
  path: /users/{id}
  func: app.users:get_user
字段 类型 说明
meta.router Registry ID 父级路由器
method string HTTP 方法:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,或用 * 匹配任意方法
path string URL 路径模式 (以 / 开头)
func Registry ID 处理函数

路径参数

使用 {param} 语法定义 URL 参数:

- name: get_post
  kind: http.endpoint
  meta:
    router: api
  method: GET
  path: /users/{user_id}/posts/{post_id}
  func: get_user_post

在处理器中访问:

local http = require("http")

local function handler()
    local req = http.request()
    local user_id = req:param("user_id")
    local post_id = req:param("post_id")

    -- ...
end

通配符路径

使用 {param...} 捕获剩余路径段:

- name: serve_files
  kind: http.endpoint
  meta:
    router: api
  method: GET
  path: /files/{filepath...}
  func: serve_file

通配符匹配剩余的路径段,因此像 GET /api/v1/files/docs/guides/readme.md 这样的请求会被派发到处理器。捕获到的尾部通过 req:param 以去掉末尾点号的名称读取:

local filepath = req:param("filepath")  -- "docs/guides/readme.md"

通配符必须是路径中的最后一个段。

路由优先级

所有路由器都把各自的端点注册到同一个模式集合中,并以路由器的 prefix 作为前缀,由 Go 的 ServeMux 决定哪个模式服务某个请求。它的规则原样适用:

  • 最具体的模式获胜。当一个模式匹配的请求是另一个模式所匹配请求的严格子集时,它就更具体,因此 /users/admin 胜过 /users/{id},/files/{name} 胜过 /files/{path...}。
  • 带方法的模式比同一路径上不带方法的模式更具体,因此对于 GET 请求,同一路径上的 GET 端点优先于 * 端点。
  • 末尾的 {path...} 或 / 匹配整个子树,会输给任何匹配其子集的模式。
  • 匹配基于清理并解码后的路径进行;具体程度从不取决于注册顺序。

两个模式也可能直接冲突:彼此都不比对方更具体,却又相互重叠,例如 /users/{id}/settings 和 /users/admin/{section}。这属于配置错误。路由器会在重建时暴露该错误,重建失败,先前的路由集合继续提供服务。

处理函数

端点处理器使用 http 模块访问请求和响应对象。完整 API 请参阅 HTTP 模块。

local http = require("http")

local function handler()
    local req = http.request()
    local res = http.response()

    local user_id = req:param("id")
    local user = get_user(user_id)

    res:set_status(http.STATUS.OK)
    res:write_json(user)
end

return { handler = handler }

中间件选项

中间件选项使用点号表示法,以中间件名称作为前缀:

middleware:
  - cors
  - ratelimit
  - token_auth
options:
  cors.allow.origins: "https://app.example.com"
  cors.allow.methods: "GET,POST,PUT,DELETE"
  ratelimit.requests: "100"
  ratelimit.window: "1m"
  token_auth.store: "app:tokens"
  token_auth.header.name: "Authorization"

匹配后中间件使用 post_options:

post_middleware:
  - endpoint_firewall
post_options:
  endpoint_firewall.action: "access"

匹配前与匹配后中间件

匹配前 (middleware) 在路由匹配之前运行:

  • CORS (处理 OPTIONS 预检请求)
  • 压缩
  • 限流
  • 真实 IP 检测
  • Token 认证 (上下文丰富)

匹配后 (post_middleware) 在路由匹配之后运行:

  • 端点防火墙 (需要路由信息进行授权)
  • 资源防火墙
  • WebSocket relay
middleware:        # 匹配前: 此路由器的所有请求
  - cors
  - compress
  - token_auth     # 用 actor/scope 丰富上下文

post_middleware:   # 匹配后: 仅匹配的路由
  - endpoint_firewall  # 使用 token_auth 设置的 actor
Token 认证可以作为匹配前中间件,因为它只丰富上下文而不阻止请求。授权在匹配后中间件如 endpoint_firewall 中进行,该中间件使用 token_auth 设置的 actor。

完整示例

version: "1.0"
namespace: app

entries:
  # 服务器
  - name: gateway
    kind: http.service
    addr: ":8080"
    lifecycle:
      auto_start: true

  # API 路由器
  - name: api
    kind: http.router
    meta:
      server: gateway
    prefix: /api/v1
    middleware:
      - cors
      - compress
      - ratelimit
    options:
      cors.allow.origins: "https://app.example.com"
      ratelimit.requests: "100"
      ratelimit.window: "1m"

  # 处理函数
  - name: get_users
    kind: function.lua
    source: file://handlers/users.lua
    method: list
    modules:
      - http
      - json
      - sql

  # 端点
  - name: list_users
    kind: http.endpoint
    meta:
      router: api
    method: GET
    path: /users
    func: get_users

  - name: get_user
    kind: http.endpoint
    meta:
      router: api
    method: GET
    path: /users/{id}
    func: app:get_user_by_id

  - name: create_user
    kind: http.endpoint
    meta:
      router: api
    method: POST
    path: /users
    func: app:create_user

受保护路由

带认证的常见模式:

entries:
  # 公开路由 (无认证)
  - name: public
    kind: http.router
    meta:
      server: gateway
    prefix: /api/public
    middleware:
      - cors

  # 受保护路由
  - name: protected
    kind: http.router
    meta:
      server: gateway
    prefix: /api
    middleware:
      - cors
      - token_auth
    options:
      token_auth.store: app:tokens
    post_middleware:
      - endpoint_firewall

参见