ルーティング
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メソッド: GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE、または任意のメソッドを表す* |
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_idとapp: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