HTTP-клиент
Выполнение HTTP-запросов к внешним сервисам. Поддержка всех HTTP-методов, заголовков, query-параметров, данных форм, загрузки файлов, потоковых ответов и конкурентных пакетных запросов.
Загрузка
local http_client = require("http_client")
HTTP-методы
Все методы имеют одинаковую сигнатуру: method(url, options?), возвращают Response, error.
GET-запрос
local resp, err = http_client.get("https://api.example.com/users")
if err then
return nil, err
end
print(resp.status_code) -- 200
print(resp.body) -- тело ответа
POST-запрос
local resp, err = http_client.post("https://api.example.com/users", {
headers = {["Content-Type"] = "application/json"},
body = json.encode({name = "Alice", email = "alice@example.com"})
})
PUT-запрос
local resp, err = http_client.put("https://api.example.com/users/123", {
headers = {["Content-Type"] = "application/json"},
body = json.encode({name = "Alice Smith"})
})
PATCH-запрос
local resp, err = http_client.patch("https://api.example.com/users/123", {
body = json.encode({status = "active"})
})
DELETE-запрос
local resp, err = http_client.delete("https://api.example.com/users/123", {
headers = {["Authorization"] = "Bearer " .. token}
})
HEAD-запрос
Возвращает только заголовки, без тела.
local resp, err = http_client.head("https://cdn.example.com/file.zip")
local size = resp.headers["Content-Length"]
Произвольный метод
local resp, err = http_client.request("PROPFIND", "https://dav.example.com/folder", {
headers = {["Depth"] = "1"}
})
| Параметр | Тип | Описание |
|---|---|---|
method |
string | HTTP-метод |
url |
string | URL запроса |
options |
table | Опции запроса (опционально) |
Опции запроса
| Поле | Тип | Описание |
|---|---|---|
headers |
table | Заголовки запроса {["Name"] = "value"} |
body |
string | Тело запроса |
query |
table | Query-параметры {key = "value"} |
form |
table | Данные формы (Content-Type устанавливается автоматически) |
files |
table | Загрузка файлов (массив определений файлов) |
cookies |
table | Cookies запроса {name = "value"} |
auth |
table | Basic-авторизация {user = "name", pass = "secret"} |
timeout |
number/string | Таймаут: число в секундах или строка типа "30s", "1m" |
stream |
boolean | Потоковое получение тела вместо буферизации |
max_response_body |
number | Макс. размер ответа в байтах (0 = по умолчанию) |
unix_socket |
string | Подключение через Unix-сокет |
tls |
table | Настройки TLS для запроса (см. Параметры TLS) |
Query-параметры
local resp, err = http_client.get("https://api.example.com/search", {
query = {
q = "lua programming",
page = "1",
limit = "20"
}
})
Заголовки и авторизация
local resp, err = http_client.get("https://api.example.com/data", {
headers = {
["Authorization"] = "Bearer " .. token,
["Accept"] = "application/json"
}
})
-- Или через basic auth
local resp, err = http_client.get("https://api.example.com/data", {
auth = {user = "admin", pass = "secret"}
})
Данные формы
local resp, err = http_client.post("https://api.example.com/login", {
form = {
username = "alice",
password = "secret123"
}
})
Загрузка файлов
local resp, err = http_client.post("https://api.example.com/upload", {
form = {title = "My Document"},
files = {
{
name = "attachment", -- имя поля формы
filename = "report.pdf", -- исходное имя файла
content = pdf_data, -- содержимое файла
content_type = "application/pdf"
}
}
})
| Поле файла | Тип | Обязательно | Описание |
|---|---|---|---|
name |
string | да | Имя поля формы |
filename |
string | нет | Исходное имя файла |
content |
string | да* | Содержимое файла |
reader |
userdata | да* | Альтернатива: io.Reader для содержимого |
content_type |
string | нет | MIME-тип (по умолчанию: application/octet-stream) |
*Требуется либо content, либо reader.
Таймаут
-- Число: секунды
local resp, err = http_client.get(url, {timeout = 30})
-- Строка: формат Go duration
local resp, err = http_client.get(url, {timeout = "30s"})
local resp, err = http_client.get(url, {timeout = "1m30s"})
local resp, err = http_client.get(url, {timeout = "1h"})
Параметры TLS
Настройка TLS для отдельных запросов: mTLS (взаимный TLS) и пользовательские CA-сертификаты.
| Поле | Тип | Описание |
|---|---|---|
cert |
string | Клиентский сертификат в формате PEM |
key |
string | Закрытый ключ клиента в формате PEM |
ca |
string | Пользовательский CA-сертификат в формате PEM |
server_name |
string | Имя сервера для SNI-верификации |
insecure_skip_verify |
boolean | Пропустить проверку TLS-сертификата |
Для mTLS необходимо указать оба поля cert и key. Поле ca заменяет системный пул сертификатов пользовательским CA.
Аутентификация mTLS
local cert_pem = fs.read("/certs/client.crt")
local key_pem = fs.read("/certs/client.key")
local resp, err = http_client.get("https://secure.example.com/api", {
tls = {
cert = cert_pem,
key = key_pem,
}
})
Пользовательский CA
local ca_pem = fs.read("/certs/internal-ca.crt")
local resp, err = http_client.get("https://internal.example.com/api", {
tls = {
ca = ca_pem,
server_name = "internal.example.com",
}
})
Пропуск проверки TLS
Пропуск TLS-верификации для сред разработки. Требует разрешение безопасности http_client.insecure_tls.
local resp, err = http_client.get("https://localhost:8443/api", {
tls = {
insecure_skip_verify = true,
}
})
Объект ответа
| Поле | Тип | Описание |
|---|---|---|
status_code |
number | HTTP-код статуса |
body |
string | Тело ответа (если не потоковый) |
body_size |
number | Размер тела в байтах (-1 если потоковый) |
headers |
table | Заголовки ответа |
cookies |
table | Cookies ответа |
url |
string | Финальный URL (после редиректов) |
stream |
Stream | Объект потока (если stream = true) |
local resp, err = http_client.get("https://api.example.com/data")
if err then
return nil, err
end
if resp.status_code == 200 then
local data = json.decode(resp.body)
print("Content-Type:", resp.headers["Content-Type"])
end
Потоковые ответы
Для больших ответов используйте потоковый режим, чтобы не загружать всё тело в память.
local resp, err = http_client.get("https://cdn.example.com/large-file.zip", {
stream = true
})
if err then
return nil, err
end
-- Обработка порциями
while true do
local chunk, err = resp.stream:read(65536)
if err or not chunk then break end
-- обработка chunk
end
resp.stream:close()
| Метод потока | Возвращает | Описание |
|---|---|---|
read(size) |
string, error | Читает до size байт |
close() |
- | Закрывает поток |
Пакетные запросы
Выполнение нескольких запросов конкурентно.
local responses, errors = http_client.request_batch({
{"GET", "https://api.example.com/users"},
{"GET", "https://api.example.com/products"},
{"POST", "https://api.example.com/log", {body = "event"}}
})
if errors then
for i, err in ipairs(errors) do
if err then
print("Request " .. i .. " failed:", err)
end
end
else
-- Все успешны
for i, resp in ipairs(responses) do
print("Response " .. i .. ":", resp.status_code)
end
end
| Параметр | Тип | Описание |
|---|---|---|
requests |
table | Массив {method, url, options?} |
Возвращает: responses, errors — массивы, индексированные по позиции запроса
Замечания:
- Запросы выполняются конкурентно
- Потоковый режим (
stream = true) не поддерживается в пакетном режиме - Массивы результатов соответствуют порядку запросов (индексация с 1)
URL-кодирование
Кодирование
local encoded = http_client.encode_uri("hello world")
-- "hello+world"
local url = "https://api.example.com/search?q=" .. http_client.encode_uri(query)
Декодирование
local decoded, err = http_client.decode_uri("hello+world")
-- "hello world"
Разрешения
HTTP-запросы подчиняются вычислению политики безопасности.
Действия безопасности
| Действие | Ресурс | Описание |
|---|---|---|
http_client.request |
URL | Разрешить/запретить запросы к конкретным URL |
http_client.unix_socket |
Путь сокета | Разрешить/запретить подключения через Unix-сокет |
http_client.private_ip |
IP-адрес | Разрешить/запретить доступ к приватным IP-диапазонам |
http_client.insecure_tls |
URL | Разрешить/запретить небезопасный TLS (пропуск верификации) |
Проверка доступа
local security = require("security")
if security.can("http_client.request", "https://api.example.com/users") then
local resp = http_client.get("https://api.example.com/users")
end
Защита от SSRF
Приватные IP-диапазоны (10.x, 192.168.x, 172.16-31.x, localhost) заблокированы по умолчанию. Доступ требует разрешения http_client.private_ip.
local resp, err = http_client.get("http://192.168.1.1/admin")
-- Error: not allowed: private IP 192.168.1.1
См. Модель безопасности для настройки политик.
Ошибки
| Условие | Kind | Повторяемо |
|---|---|---|
| Запрещено политикой безопасности | errors.PERMISSION_DENIED |
нет |
| Приватный IP заблокирован | errors.PERMISSION_DENIED |
нет |
| Unix-сокет запрещён | errors.PERMISSION_DENIED |
нет |
| Небезопасный TLS запрещён | errors.PERMISSION_DENIED |
нет |
| Некорректный URL или опции | errors.INVALID |
нет |
| Нет контекста | errors.INTERNAL |
нет |
| Сетевая ошибка | errors.INTERNAL |
да |
| Таймаут | errors.INTERNAL |
да |
local resp, err = http_client.get(url)
if err then
if errors.is(err, errors.PERMISSION_DENIED) then
print("Access denied:", err:message())
elseif err:retryable() then
print("Temporary error:", err:message())
end
return nil, err
end
См. Обработка ошибок для работы с ошибками.