HTTP-эндпоинты

Эндпоинты (http.endpoint) определяют обработчики HTTP-маршрутов, которые выполняют Lua-функции.

Определение

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

Конфигурация

Поле Тип Обязательно Описание
meta.router registry.ID Нет Родительский роутер (по умолчанию используется единственный зарегистрированный роутер, если он один)
method string Да HTTP-метод
path string Да Шаблон URL-пути
func registry.ID Да Выполняемая функция

HTTP-методы

Поддерживаемые методы:

Метод Назначение
GET Получение ресурсов
POST Создание ресурсов
PUT Замена ресурсов
PATCH Частичное обновление
DELETE Удаление ресурсов
HEAD Только заголовки
OPTIONS CORS preflight (автоматически)
TRACE Диагностический loopback

Параметры пути

Используйте синтаксис {param} для URL-параметров:

- name: get_user
  kind: http.endpoint
  method: GET
  path: /users/{id}
  func: get_user

- name: get_user_post
  kind: http.endpoint
  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("id")
    local post_id = req:param("post_id")
end

Wildcard-пути

Захват оставшегося пути через {path...}:

- name: file_handler
  kind: http.endpoint
  method: GET
  path: /files/{path...}
  func: serve_file
local function handler()
    local req = http.request()
    local file_path = req:param("path")
    -- /files/docs/readme.md -> path = "docs/readme.md"
end

Функция-обработчик

Функции эндпоинтов получают объекты запроса и ответа из модуля http:

local http = require("http")
local json = require("json")

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

    -- Чтение запроса
    local body = req:body()
    local user_id = req:param("id")
    local page = req:query("page")
    local auth = req:header("Authorization")

    -- Обработка
    local user = get_user(user_id)

    -- Запись ответа
    res:set_content_type(http.CONTENT.JSON)
    res:set_status(http.STATUS.OK)
    res:write_json(user)
end

return { handler = handler }

Объект Request

Метод Возвращает Описание
req:method() string HTTP-метод
req:path() string Путь запроса
req:param(name) string URL-параметр
req:params() table Все параметры пути
req:query(name) string Query-параметр
req:query_params() table Все query-параметры
req:header(name) string Заголовок запроса
req:body() string Тело запроса
req:body_json() table, error Разбор JSON-тела
req:has_body() boolean Проверка наличия тела
req:content_type() string Content-Type
req:content_length() number Размер тела в байтах
req:host() string Имя хоста
req:remote_addr() string IP-адрес клиента
req:accepts(type) boolean Content negotiation
req:is_content_type(type) boolean Проверка content-type
req:stream() Stream Тело как поток для больших файлов
req:parse_multipart(max?) table, error Разбор multipart-формы

Объект Response

Метод Описание
res:set_status(code) Установить HTTP-статус
res:set_header(name, value) Установить заголовок ответа
res:set_content_type(type) Установить content-type
res:write(data) Записать raw-тело
res:write_json(data) Записать JSON-ответ
res:write_event(data) Отправить SSE-событие
res:set_transfer(encoding) Установить режим передачи (SSE, chunked)
res:flush() Сбросить ответ клиенту

Паттерн JSON API

Типовой паттерн для JSON API:

local http = require("http")

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

    local data, err = req:body_json()
    if err then
        res:set_status(http.STATUS.BAD_REQUEST)
        res:write_json({error = "Invalid JSON"})
        return
    end

    local result = process(data)

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

return { handler = handler }

Ответы об ошибках

local http = require("http")

local function api_error(res, status, code, message)
    res:set_status(status)
    res:write_json({
        error = {
            code = code,
            message = message
        }
    })
end

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

    local user_id = req:param("id")
    local user, err = db.get_user(user_id)

    if err then
        if errors.is(err, errors.NOT_FOUND) then
            return api_error(res, http.STATUS.NOT_FOUND, "USER_NOT_FOUND", "User not found")
        end
        return api_error(res, http.STATUS.INTERNAL_ERROR, "INTERNAL_ERROR", "Server error")
    end

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

return { handler = handler }

Примеры

CRUD-эндпоинты

entries:
  - name: users_router
    kind: http.router
    prefix: /api/users
    middleware:
      - cors
      - compress

  - name: list_users
    kind: http.endpoint
    meta:
      router: users_router
    method: GET
    path: /
    func: app.users:list

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

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

  - name: update_user
    kind: http.endpoint
    meta:
      router: users_router
    method: PUT
    path: /{id}
    func: app.users:update

  - name: delete_user
    kind: http.endpoint
    meta:
      router: users_router
    method: DELETE
    path: /{id}
    func: app.users:delete

Защищённый эндпоинт

- name: admin_endpoint
  kind: http.endpoint
  meta:
    router: admin_router
  method: POST
  path: /settings
  func: app.admin:update_settings
  post_middleware:
    - endpoint_firewall
  post_options:
    endpoint_firewall.action: "admin"

См. также