Endpoints HTTP
Un http.endpoint asigna un método y una ruta HTTP a una función handler de Lua.
Clasificación: referencia de configuración y API. Los bloques YAML son fragmentos de registro que presuponen que ya existen el servidor, router, middleware, entradas de función y políticas de seguridad referenciados. Los bloques Lua se centran en los contratos del handler e identifican explícitamente las llamadas de la aplicación.
Definición
- name: get_user
kind: http.endpoint
meta:
router: app:api_router
method: GET
path: /users/{id}
func: app.users:get_user
Configuración
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
meta.router |
registry.ID | No | Router padre (por defecto, el único router si exactamente uno está registrado) |
method |
string | Sí | Método HTTP, o "*" para cualquier método |
path |
string | Sí | Patrón de ruta URL |
func |
registry.ID | Sí | Función a ejecutar |
Métodos HTTP
Métodos soportados:
| Método | Caso de Uso |
|---|---|
GET |
Recuperar recursos |
POST |
Crear recursos |
PUT |
Reemplazar recursos |
PATCH |
Actualización parcial |
DELETE |
Eliminar recursos |
HEAD |
Solo headers |
OPTIONS |
Preflight CORS (auto-manejado) |
TRACE |
Loopback de diagnóstico |
* |
Cualquier método |
Los nombres de método van en mayúsculas; method es obligatorio, y cualquier valor fuera de este conjunto se rechaza como error de configuración.
Endpoints Agnósticos al Método
method: "*" registra la ruta para todos los métodos HTTP, y el handler lee el método real con req:method():
- name: proxy
kind: http.endpoint
method: "*"
path: /proxy/{path...}
func: proxy_handler
Para un endpoint normal el router también registra un handler OPTIONS en la misma ruta, de modo que el middleware CORS puede responder un preflight sin que el endpoint se ejecute. Un endpoint * no obtiene ese handler: ya coincide con OPTIONS. El middleware del router sigue envolviéndolo, así que un middleware CORS configurado responde un preflight permitido con 204 antes de que el endpoint se ejecute; cualquier otra solicitud OPTIONS llega a la propia función del endpoint, que debe responderla.
Parámetros de Ruta
Use sintaxis {param} para parámetros de URL:
- name: get_user
kind: http.endpoint
meta:
router: api
method: GET
path: /users/{id}
func: get_user
- name: get_user_post
kind: http.endpoint
meta:
router: api
method: GET
path: /users/{user_id}/posts/{post_id}
func: get_user_post
Acceso en handler:
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
Rutas Comodín
Capture la ruta restante con {path...}:
- name: file_handler
kind: http.endpoint
method: GET
path: /files/{path...}
func: serve_file
Este segmento catch-all hace que la ruta coincida con solicitudes como /files/docs/readme.md. La cola capturada se lee como cualquier otro parámetro, bajo el nombre sin los puntos finales:
local req = http.request()
local tail = req:param("path") -- "docs/readme.md"
Función Handler
Las funciones de endpoint obtienen objetos de solicitud y respuesta del módulo http:
local http = require("http")
local funcs = require("funcs")
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
local type_err = res:set_content_type(http.CONTENT.JSON)
if type_err then return nil, type_err end
local status_err = res:set_status(http.STATUS.OK)
if status_err then return nil, status_err end
local write_err = res:write_json(user)
if write_err then return nil, write_err end
return true
end
return { handler = handler }
Objeto Request
| Método | Retorna | Descripción |
|---|---|---|
req:method() |
string | Método HTTP |
req:path() |
string | Ruta de solicitud |
req:param(name) |
string | Parámetro de URL |
req:params() |
table | Todos los parámetros de ruta |
req:query(name) |
string | Parámetro de query |
req:query_params() |
table | Todos los parámetros de query |
req:header(name) |
string | Header de solicitud |
req:headers() |
table | Todos los headers de la solicitud |
req:body() |
string | Cuerpo de solicitud |
req:body_json() |
table, error | Parsear cuerpo JSON |
req:has_body() |
boolean | Verifica si existe cuerpo |
req:content_type() |
string | Tipo de contenido |
req:content_length() |
number | Tamaño del cuerpo en bytes |
req:host() |
string | Nombre del host |
req:remote_addr() |
string | Dirección IP del cliente |
req:accepts(type) |
boolean | Negociación de contenido |
req:is_content_type(type) |
boolean | Verifica tipo de contenido |
req:stream() |
Stream | Cuerpo como stream para archivos grandes |
req:parse_multipart(max?) |
table, error | Parsear formulario multipart |
Objeto Response
| Método | Descripción |
|---|---|
res:set_status(code) |
Establecer el código de estado HTTP; devuelve un error si ya se enviaron las cabeceras |
res:set_header(name, value) |
Establecer una cabecera de respuesta; devuelve un error si ya se enviaron las cabeceras |
res:set_content_type(type) |
Establecer el tipo de contenido; devuelve un error si ya se enviaron las cabeceras |
res:write(data) |
Escribir el cuerpo sin procesar; devuelve un error si falla |
res:write_json(data) |
Escribir una respuesta JSON; devuelve un error si falla |
res:write_event(data) |
Enviar y vaciar un evento SSE; devuelve un error si falla |
res:set_transfer(encoding) |
Establecer el modo de transferencia chunked o sse; devuelve un error si ya se enviaron las cabeceras |
res:flush() |
Vaciar la respuesta; devuelve un valor de error |
Patrón de API JSON
Un handler de API JSON puede analizar el cuerpo de la solicitud, rechazar entradas no válidas y escribir un resultado JSON:
local http = require("http")
local funcs = require("funcs")
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 data, err = req:body_json()
if err then
local status_err = res:set_status(http.STATUS.BAD_REQUEST)
if status_err then return nil, status_err end
local write_err = res:write_json({error = "Invalid JSON"})
if write_err then return nil, write_err end
return true
end
local result, process_err = funcs.call("app.api:process_request", data)
if process_err then return nil, process_err end
local status_err = res:set_status(http.STATUS.OK)
if status_err then return nil, status_err end
local write_err = res:write_json(result)
if write_err then return nil, write_err end
return true
end
return { handler = handler }
Respuestas de Error
local http = require("http")
local funcs = require("funcs")
local function api_error(res, status, code, message)
local status_err = res:set_status(status)
if status_err then return nil, status_err end
local write_err = res:write_json({
error = {
code = code,
message = message
}
})
if write_err then return nil, write_err end
return true
end
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, err = funcs.call("app.users: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
local status_err = res:set_status(http.STATUS.OK)
if status_err then return nil, status_err end
local write_err = res:write_json(user)
if write_err then return nil, write_err end
return true
end
return { handler = handler }
Ejemplos
Endpoints CRUD
entries:
- name: users_router
kind: http.router
meta:
server: gateway
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
Endpoint Protegido
El middleware de autorización se configura en el router padre, no en el endpoint. El middleware post-match (como endpoint_firewall) se ejecuta después del matching de ruta y se aplica a todos los endpoints bajo el router:
- name: admin_router
kind: http.router
meta:
server: gateway
prefix: /admin
middleware:
- cors
- token_auth
post_middleware:
- endpoint_firewall
post_options:
endpoint_firewall.action: "admin"
- name: admin_endpoint
kind: http.endpoint
meta:
router: admin_router
method: POST
path: /settings
func: app.admin:update_settings
Véase también
- Router - Agrupación de rutas
- Módulo HTTP - API de solicitud y respuesta
- Middleware - Procesamiento de solicitudes