ルーティング

http.routerはURLプレフィックス配下にエンドポイントをまとめ、共通のミドルウェアを適用します。各http.endpointはHTTPハンドラを定義します。

分類:ルーティングリファレンス。 設定ブロックは、名前空間と参照されるすべてのエントリを含む場合を除き、レジストリの一部分です。ハンドラブロックでは、データ層を定義する代わりにアプリケーション所有の関数IDを使用します。

アーキテクチャ

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]

エントリはメタデータを介して親を参照します:

  • ルーター: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メソッド: GETPOSTPUTDELETEPATCHHEADOPTIONSTRACE、または任意のメソッドを表す*
path string URLパスパターン(/で開始)
func Registry ID ハンドラ関数

パスパラメータ

URLパラメータには{param}構文を使用します:

- 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, req_err = http.request()
    if req_err then return nil, req_err end
    local user_id, user_err = req:param("user_id")
    if user_err then return nil, user_err end
    local post_id, post_err = req:param("post_id")
    if post_err then return nil, post_err end

    return {user_id = user_id, post_id = 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...}または/はサブツリー全体にマッチし、その部分集合にマッチするパターンには負けます。
  • マッチングは正規化・デコード済みのパスに対して行われ、具体性が登録順に依存することはありません。

2つのパターンが正面から衝突することもあります。どちらも他方より具体的ではないのに重なり合う場合で、/users/{id}/settings/users/admin/{section}がその例です。これは設定エラーです。ルーターは再構築時にこれを検出し、再構築は失敗し、以前のルートセットがそのまま稼働し続けます。

ハンドラ関数

エンドポイントハンドラはhttpモジュールを使用して、リクエストオブジェクトとレスポンスオブジェクトにアクセスします。リクエストとレスポンスのAPIリファレンスについては、HTTPモジュールを参照してください。

local http = require("http")

local function handler()
    local req, req_err = http.request()
    if req_err then return nil, req_err end
    local res, res_err = http.response()
    if res_err then return nil, res_err end

    local user_id, param_err = req:param("id")
    if param_err then return nil, param_err end
    local user, call_err = funcs.call("app.users:get_user", user_id)
    if call_err then return nil, call_err end

    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の検出
  • トークン認証(コンテキストの拡充)

マッチ後post_middleware)は、ルートパラメータとエンドポイントメタデータが付与された後に実行されます:

  • エンドポイントファイアウォール(認可にルート情報が必要)
  • リソースファイアウォール
  • WebSocketリレー
middleware:        # Before endpoint metadata: matched routes only
  - cors
  - compress
  - token_auth     # Enriches context with actor/scope

post_middleware:   # Post-match: matched routes only
  - endpoint_firewall  # Uses actor from token_auth
トークン認証は、認可より前にリクエストコンテキストを拡充するため、プリハンドラチェーンに置きます。endpoint_firewallなどの認可ミドルウェアは、一致したエンドポイントIDを必要とするため、マッチ後チェーンに置きます。一致しないリクエストでは、どちらのルーターチェーンも実行されません。

ルーターとエンドポイントの接続

次の例では一覧ハンドラのエントリを定義しています。app:get_user_by_idapp:create_userの関数IDは、同じ名前空間の別の場所で定義されたハンドラを参照します。

version: "1.0"
namespace: app

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

  # API Router
  - 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"

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

  # Endpoints
  - 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:
  # Public routes (no auth)
  - name: public
    kind: http.router
    meta:
      server: gateway
    prefix: /api/public
    middleware:
      - cors

  # Protected routes
  - name: protected
    kind: http.router
    meta:
      server: gateway
    prefix: /api
    middleware:
      - cors
      - token_auth
    options:
      token_auth.store: app:tokens
    post_middleware:
      - endpoint_firewall

関連項目