HTTP
Обработка HTTP-запросов и формирование ответов. Доступ к данным запроса, параметрам маршрута, заголовкам и телу. Формирование ответов с кодами статуса, заголовками и поддержкой потоковой передачи.
Настройку сервера см. в HTTP-сервер.
Загрузка
local http = require("http")
Доступ к запросу
Получить контекст текущего HTTP-запроса:
local req = http.request()
-- С опциями
local req = http.request({
timeout = 5000, -- таймаут чтения тела 5 секунд
max_body = 10485760 -- макс. размер тела 10MB
})
| Параметр | Тип | Описание |
|---|---|---|
options.timeout |
integer | Таймаут чтения тела в мс (по умолчанию: 300000 / 5 мин) |
options.max_body |
integer | Макс. размер тела в байтах (по умолчанию: 120MB) |
Возвращает: Request, error
Доступ к ответу
Получить контекст текущего HTTP-ответа:
local res = http.response()
Возвращает: Response, error
Методы запроса
method
local method = req:method()
if method == http.METHOD.GET then
return get_resource(id)
elseif method == http.METHOD.POST then
return create_resource(req:body_json())
elseif method == http.METHOD.PUT then
return update_resource(id, req:body_json())
elseif method == http.METHOD.DELETE then
return delete_resource(id)
end
path
local path = req:path()
print(path) -- "/api/users/123"
-- Маршрутизация по пути
if path:match("^/api/") then
return handle_api(req)
end
query
Получает один query-параметр.
-- GET /search?q=hello&page=2&limit=10
local query = req:query("q") -- "hello"
local page = req:query("page") -- "2"
local missing = req:query("foo") -- nil
-- Со значениями по умолчанию
local page = tonumber(req:query("page")) or 1
local limit = tonumber(req:query("limit")) or 20
local sort = req:query("sort") or "created_at"
query_params
Получает все query-параметры. Несколько значений для одного ключа объединяются через запятую.
-- GET /search?tags=lua&tags=go&active=true
local params = req:query_params()
-- {tags = "lua,go", active = "true"}
for key, value in pairs(params) do
print(key .. ": " .. value)
end
header
local auth = req:header("Authorization")
if not auth then
res:set_status(401)
return res:write_json({error = "Missing authorization"})
end
local user_agent = req:header("User-Agent")
local correlation_id = req:header("X-Correlation-ID") or uuid.v4()
content_type
Получает заголовок Content-Type.
local ct = req:content_type() -- "application/json; charset=utf-8" или nil
content_length
Получает значение заголовка Content-Length.
local length = req:content_length() -- количество байт
host
Получает заголовок Host.
local host = req:host() -- "example.com:8080"
param
Получает параметры URL-маршрута (из паттернов типа /users/:id).
-- Маршрут: /users/:id/posts/:post_id
local user_id = req:param("id")
local post_id = req:param("post_id")
-- Валидация параметра
local id = req:param("id")
if not id or not uuid.validate(id) then
res:set_status(400)
return res:write_json({error = "Invalid ID format"})
end
params
Получает все параметры маршрута.
-- Маршрут: /orgs/:org/repos/:repo/issues/:issue
local p = req:params()
-- {org = "acme", repo = "widget", issue = "123"}
local issue = get_issue(p.org, p.repo, p.issue)
body
Читает всё тело запроса как строку.
local body = req:body()
-- Ручной разбор XML
if req:is_content_type("application/xml") then
local data = parse_xml(body)
end
-- Логирование сырого тела для отладки
logger.debug("Request body", {body = body, length = #body})
body_json
Читает и разбирает тело как JSON.
local data, err = req:body_json()
if err then
res:set_status(400)
return res:write_json({error = "Invalid JSON: " .. err:message()})
end
-- Валидация обязательных полей
if not data.name or not data.email then
res:set_status(400)
return res:write_json({error = "Missing required fields"})
end
local user = create_user(data)
has_body
if req:has_body() then
local data = req:body_json()
process(data)
else
res:set_status(400)
return res:write_json({error = "Request body required"})
end
is_content_type
if not req:is_content_type("application/json") then
res:set_status(415)
return res:write_json({error = "Content-Type must be application/json"})
end
accepts
if req:accepts("application/json") then
res:write_json(data)
elseif req:accepts("text/html") then
res:set_content_type("text/html")
res:write(render_html(data))
else
res:set_status(406)
res:write_json({error = "Cannot produce acceptable response"})
end
remote_addr
local addr = req:remote_addr() -- "192.168.1.100:54321"
-- Извлечение только IP
local ip = addr:match("^([^:]+)")
-- Rate limiting по IP
if rate_limiter:is_limited(ip) then
res:set_status(429)
return res:write_json({error = "Too many requests"})
end
parse_multipart
Разбирает multipart-данные формы (загрузка файлов).
local form, err = req:parse_multipart()
if err then
res:set_status(400)
return res:write_json({error = "Invalid form data"})
end
-- Доступ к значениям формы
local title = form.values.title
local description = form.values.description
-- Доступ к загруженным файлам
if form.files.avatar then
local file = form.files.avatar[1]
local filename = file:name() -- "photo.jpg"
local size = file:size() -- 102400
local content_type = file:header("Content-Type") -- "image/jpeg"
-- Чтение содержимого файла
local stream = file:stream()
local content = stream:read_all()
stream:close()
-- Сохранение в хранилище
storage.write("avatars/" .. filename, content)
end
-- Обработка нескольких файлов
if form.files.documents then
for _, file in ipairs(form.files.documents) do
process_document(file)
end
end
stream
Получает тело запроса как поток для больших файлов.
local stream = req:stream()
-- Обработка порциями
while true do
local chunk, err = stream:read(65536) -- порции по 64KB
if err or not chunk then break end
process_chunk(chunk)
end
stream:close()
Методы ответа
set_status
res:set_status(200)
res:set_status(http.STATUS.CREATED)
-- Типичные паттерны
res:set_status(201) -- Created
res:set_status(204) -- No Content (для DELETE)
res:set_status(400) -- Bad Request
res:set_status(401) -- Unauthorized
res:set_status(403) -- Forbidden
res:set_status(404) -- Not Found
res:set_status(500) -- Internal Server Error
set_header
res:set_header("X-Request-ID", correlation_id)
res:set_header("Cache-Control", "max-age=3600")
res:set_header("X-RateLimit-Remaining", tostring(remaining))
-- CORS-заголовки
res:set_header("Access-Control-Allow-Origin", "*")
res:set_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
res:set_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
set_content_type
res:set_content_type("application/json")
res:set_content_type(http.CONTENT.JSON)
res:set_content_type("text/html; charset=utf-8")
res:set_content_type("application/pdf")
write
Записывает в тело ответа.
res:write("Hello, World!")
-- Формирование ответа порциями
res:write("<html><body>")
res:write("<h1>Title</h1>")
res:write("<p>Content</p>")
res:write("</body></html>")
write_json
Кодирует значение в JSON и записывает.
-- Успешный ответ
res:set_status(200)
res:write_json({
data = users,
total = count,
page = page
})
-- Ответ с ошибкой
res:set_status(400)
res:write_json({
error = "Validation failed",
details = {
{field = "email", message = "Invalid format"},
{field = "age", message = "Must be positive"}
}
})
flush
Сбрасывает буферизованные данные клиенту.
set_transfer
Устанавливает кодировку передачи для потоковой отправки.
-- Chunked-передача
res:set_transfer(http.TRANSFER.CHUNKED)
for chunk in get_chunks() do
res:write(chunk)
res:flush()
end
-- Server-Sent Events
res:set_transfer(http.TRANSFER.SSE)
write_event
Записывает Server-Sent Event.
-- Обновления в реальном времени
res:set_transfer(http.TRANSFER.SSE)
res:write_event({name = "connected", data = {client_id = client_id}})
for progress in task:progress() do
res:write_event({name = "progress", data = {percent = progress}})
end
res:write_event({name = "complete", data = {result = result}})
-- Сообщения чата
res:write_event({name = "message", data = {
from = "alice",
text = "Hello!",
timestamp = time.now():unix()
}})
Константы
HTTP-методы
http.METHOD.GET
http.METHOD.POST
http.METHOD.PUT
http.METHOD.DELETE
http.METHOD.PATCH
http.METHOD.HEAD
http.METHOD.OPTIONS
Коды статуса
-- Успех (2xx)
http.STATUS.OK -- 200
http.STATUS.CREATED -- 201
http.STATUS.ACCEPTED -- 202
http.STATUS.NO_CONTENT -- 204
http.STATUS.PARTIAL_CONTENT -- 206
-- Перенаправление (3xx)
http.STATUS.MOVED_PERMANENTLY -- 301
http.STATUS.FOUND -- 302
http.STATUS.SEE_OTHER -- 303
http.STATUS.NOT_MODIFIED -- 304
http.STATUS.TEMPORARY_REDIRECT -- 307
http.STATUS.PERMANENT_REDIRECT -- 308
-- Ошибки клиента (4xx)
http.STATUS.BAD_REQUEST -- 400
http.STATUS.UNAUTHORIZED -- 401
http.STATUS.PAYMENT_REQUIRED -- 402
http.STATUS.FORBIDDEN -- 403
http.STATUS.NOT_FOUND -- 404
http.STATUS.METHOD_NOT_ALLOWED -- 405
http.STATUS.NOT_ACCEPTABLE -- 406
http.STATUS.CONFLICT -- 409
http.STATUS.GONE -- 410
http.STATUS.UNPROCESSABLE -- 422
http.STATUS.TOO_MANY_REQUESTS -- 429
-- Ошибки сервера (5xx)
http.STATUS.INTERNAL_ERROR -- 500
http.STATUS.NOT_IMPLEMENTED -- 501
http.STATUS.BAD_GATEWAY -- 502
http.STATUS.SERVICE_UNAVAILABLE -- 503
http.STATUS.GATEWAY_TIMEOUT -- 504
http.STATUS.VERSION_NOT_SUPPORTED -- 505
Типы содержимого
http.CONTENT.JSON -- "application/json"
http.CONTENT.FORM -- "application/x-www-form-urlencoded"
http.CONTENT.MULTIPART -- "multipart/form-data"
http.CONTENT.TEXT -- "text/plain"
http.CONTENT.STREAM -- "application/octet-stream"
Режимы передачи
http.TRANSFER.CHUNKED -- "chunked"
http.TRANSFER.SSE -- "sse"
Типы ошибок
Константы типов ошибок модуля для точной обработки.
http.ERROR.PARSE_FAILED -- Ошибка разбора формы/multipart
http.ERROR.INVALID_STATE -- Некорректное состояние ответа
http.ERROR.WRITE_FAILED -- Ошибка записи ответа
http.ERROR.STREAM_ERROR -- Ошибка потока тела
Ошибки
| Условие | Kind | Повторяемо |
|---|---|---|
| Нет HTTP-контекста | errors.INTERNAL |
нет |
| Тело слишком большое | errors.INVALID |
нет |
| Таймаут чтения | errors.INTERNAL |
нет |
| Некорректный JSON | errors.INVALID |
нет |
| Не multipart | errors.INVALID |
нет |
| Заголовки уже отправлены | errors.INVALID |
нет |
| Ошибка записи | errors.INTERNAL |
нет |
См. Обработка ошибок для работы с ошибками.