Endpoints HTTP
Uma entrada http.endpoint associa um método e um caminho HTTP a uma função handler Lua.
Classificação: referência de configuração e API. Os blocos YAML são fragmentos do registro que pressupõem a existência das entradas de servidor, roteador, middleware, função e políticas de segurança referenciadas. Os blocos Lua se concentram nos contratos do handler e identificam explicitamente as chamadas pertencentes à aplicação.
Definição
- name: get_user
kind: http.endpoint
meta:
router: app:api_router
method: GET
path: /users/{id}
func: app.users:get_user
Configuração
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
meta.router |
registry.ID | Não | Roteador pai (padrão: o único roteador se exatamente um estiver registrado) |
method |
string | Sim | Método HTTP, ou "*" para qualquer método |
path |
string | Sim | Padrão de caminho URL |
func |
registry.ID | Sim | Função a executar |
Métodos HTTP
Métodos compatíveis:
| Método | Caso de uso |
|---|---|
GET |
Recuperar recursos |
POST |
Criar recursos |
PUT |
Substituir recursos |
PATCH |
Atualização parcial |
DELETE |
Remover recursos |
HEAD |
Somente headers |
OPTIONS |
Preflight CORS, tratado automaticamente |
TRACE |
Loopback de diagnóstico |
* |
Qualquer método |
Nomes de métodos são em maiúsculas; method é obrigatório, e qualquer valor fora desse conjunto é rejeitado como erro de configuração.
Endpoints Agnósticos de Método
method: "*" registra o caminho para todos os métodos HTTP, e o handler lê o método real com req:method():
- name: proxy
kind: http.endpoint
method: "*"
path: /proxy/{path...}
func: proxy_handler
Para um endpoint normal, o roteador também registra um handler OPTIONS no mesmo caminho, para que o middleware de CORS possa responder a um preflight sem executar o endpoint. Um endpoint * não recebe esse handler: ele já corresponde a OPTIONS. O middleware do roteador ainda o envolve, então um middleware de CORS configurado responde a um preflight permitido com 204 antes de o endpoint executar; qualquer outra requisição OPTIONS chega à própria função do endpoint, que deve respondê-la.
Parâmetros de caminho
Use a sintaxe {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
Acesse-os no 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
Caminhos curinga
Use {path...} para corresponder a todos os segmentos de caminho restantes:
- name: file_handler
kind: http.endpoint
method: GET
path: /files/{path...}
func: serve_file
Esse segmento catch-all faz a rota corresponder a requisições como /files/docs/readme.md. A cauda capturada é lida como qualquer outro parâmetro, sob o nome sem os pontos finais:
local req = http.request()
local tail = req:param("path") -- "docs/readme.md"
Função handler
As funções de endpoint obtêm os objetos de requisição e resposta pelo 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 | Descrição |
|---|---|---|
req:method() |
string | Método HTTP |
req:path() |
string | Caminho da requisição |
req:param(name) |
string | Parâmetro de URL |
req:params() |
table | Todos os parâmetros de caminho |
req:query(name) |
string | Parâmetro de query |
req:query_params() |
table | Todos os parâmetros de query |
req:header(name) |
string | Header da requisição |
req:headers() |
table | Todos os headers da requisição |
req:body() |
string | Corpo da requisição |
req:body_json() |
table, error | Analisa corpo JSON |
req:has_body() |
boolean | Verifica se existe corpo |
req:content_type() |
string | Tipo de conteúdo |
req:content_length() |
number | Tamanho do corpo em bytes |
req:host() |
string | Nome do host |
req:remote_addr() |
string | Endereço IP do cliente |
req:accepts(type) |
boolean | Negociação de conteúdo |
req:is_content_type(type) |
boolean | Verifica tipo de conteúdo |
req:stream() |
Stream | Corpo como stream para arquivos grandes |
req:parse_multipart(max?) |
table, error | Analisa formulário multipart |
Objeto Response
| Método | Descrição |
|---|---|
res:set_status(code) |
Define o status HTTP; retorna um erro se os headers já tiverem sido enviados |
res:set_header(name, value) |
Define um header de resposta; retorna um erro se os headers já tiverem sido enviados |
res:set_content_type(type) |
Define o tipo de conteúdo; retorna um erro se os headers já tiverem sido enviados |
res:write(data) |
Escreve o corpo bruto; retorna um erro em caso de falha |
res:write_json(data) |
Escreve uma resposta JSON; retorna um erro em caso de falha |
res:write_event(data) |
Envia e descarrega um evento SSE; retorna um erro em caso de falha |
res:set_transfer(encoding) |
Define o modo de transferência chunked ou sse; retorna um erro se os headers já tiverem sido enviados |
res:flush() |
Descarrega a resposta; retorna um valor de erro |
Padrão de API JSON
Um handler de API JSON pode analisar o corpo da requisição, rejeitar entradas inválidas e escrever um 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 }
Respostas de erro
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 }
Exemplos
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
O middleware de autorização é configurado no roteador pai, não no endpoint. O middleware de pós-match, como endpoint_firewall, é executado depois da correspondência da rota e se aplica a todos os endpoints sob o roteador:
O middleware de autorização é configurado no roteador pai, não no endpoint. Middleware pós-match (como endpoint_firewall) executa após o match de rota e se aplica a todos os endpoints sob o roteador:
- 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
Veja também
- Roteador - Agrupamento de rotas
- Módulo HTTP - API de requisição e resposta
- Middleware - Processamento de requisições