라우팅

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 레지스트리 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 레지스트리 ID 부모 라우터
method string HTTP 메서드: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, TRACE, 또는 모든 메서드를 뜻하는 *
path string URL 경로 패턴 (/로 시작)
func 레지스트리 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"

와일드카드는 경로의 마지막 세그먼트여야 합니다. 예를 들어 GET /api/v1/files/docs/guides/readme.md 요청은 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, 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가 필요하므로 매칭 후 체인에 둡니다. 매칭되지 않은 요청은 어느 라우터 체인도 실행하지 않습니다.

전체 예제

이 예제는 목록 handler 엔트리를 정의합니다. app:get_user_by_id와 app:create_user 함수 ID는 같은 namespace의 다른 위치에 정의된 handler를 가리킵니다.

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

참고