HTTP-Client
Das Modul http_client sendet HTTP-Anfragen mit Headern, Query-Parametern, Formularen, Datei-Uploads, Authentifizierung, TLS-Optionen, Streaming-Responses und parallelen Batches.
Diese Seite ist eine API-Referenz mit Teilrezepten. URLs, Tokens, Zugangsdaten, Request-Daten und Zertifikatsmaterial stammen aus der umgebenden Anwendung. Die Beispiele prüfen Response, error, bevor sie eine Response verwenden, und schließen gestreamte Bodies ausdrücklich.
Laden
local http_client = require("http_client")
HTTP-Methoden
Alle Methoden teilen dieselbe Signatur: method(url, options?) gibt Response, error zurück.
GET-Anfrage
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) -- response body
POST-Anfrage
local json = require("json")
local body, body_err = json.encode({name = "Alice", email = "alice@example.com"})
if body_err then return nil, body_err end
local resp, err = http_client.post("https://api.example.com/users", {
headers = {["Content-Type"] = "application/json"},
body = body
})
if err then return nil, err end
PUT-Anfrage
local body, body_err = json.encode({name = "Alice Smith"})
if body_err then return nil, body_err end
local resp, err = http_client.put("https://api.example.com/users/123", {
headers = {["Content-Type"] = "application/json"},
body = body
})
if err then return nil, err end
PATCH-Anfrage
local body, body_err = json.encode({status = "active"})
if body_err then return nil, body_err end
local resp, err = http_client.patch("https://api.example.com/users/123", {
headers = {["Content-Type"] = "application/json"},
body = body
})
if err then return nil, err end
DELETE-Anfrage
local resp, err = http_client.delete("https://api.example.com/users/123", {
headers = {["Authorization"] = "Bearer " .. token}
})
if err then return nil, err end
HEAD-Anfrage
Gibt nur Header zurück, keinen Body.
local resp, err = http_client.head("https://cdn.example.com/file.zip")
if err then return nil, err end
local size = resp.headers["Content-Length"]
Benutzerdefinierte Methode
local resp, err = http_client.request("PROPFIND", "https://dav.example.com/folder", {
headers = {["Depth"] = "1"}
})
if err then return nil, err end
| Parameter | Typ | Beschreibung |
|---|---|---|
method |
string | HTTP-Methode |
url |
string | Anfrage-URL |
options |
table | Anfrageoptionen (optional) |
Anfrageoptionen
| Feld | Typ | Beschreibung |
|---|---|---|
headers |
table | Anfrage-Header {["Name"] = "value"} |
body |
string | Anfrage-Body |
query |
table | Query-Parameter {key = "value"} |
form |
table | Formulardaten (setzt Content-Type automatisch) |
files |
table | Datei-Uploads (Array von Dateidefinitionen) |
cookies |
table | Anfrage-Cookies {name = "value"} |
auth |
table | Basic Auth {user = "name", pass = "secret"} |
timeout |
number/string | Timeout: Zahl in Sekunden oder String wie "30s", "1m" |
stream |
boolean | Response-Body streamen statt puffern |
max_response_body |
number | Max. Response-Größe in Bytes (0 = Standard) |
unix_socket |
string | Über Unix-Socket-Pfad verbinden |
tls |
table | TLS-Konfiguration pro Anfrage (siehe TLS-Optionen) |
overlay_network |
string | Über ein Netzwerk-Overlay routen — Registry-ID eines network.socks5- / network.tailscale- / network.i2p-Eintrags |
Query-Parameter
local resp, err = http_client.get("https://api.example.com/search", {
query = {
q = "lua programming",
page = "1",
limit = "20"
}
})
if err then return nil, err end
Header und Authentifizierung
local resp, err = http_client.get("https://api.example.com/data", {
headers = {
["Authorization"] = "Bearer " .. token,
["Accept"] = "application/json"
}
})
if err then return nil, err end
-- Or use basic auth
local resp, err = http_client.get("https://api.example.com/data", {
auth = {user = service_user, pass = service_password}
})
if err then return nil, err end
Formulardaten
local resp, err = http_client.post("https://api.example.com/login", {
form = {
username = username,
password = password
}
})
if err then return nil, err end
Datei-Upload
local resp, err = http_client.post("https://api.example.com/upload", {
form = {title = "My Document"},
files = {
{
name = "attachment", -- form field name
filename = "report.pdf", -- original filename
content = pdf_data, -- file content
content_type = "application/pdf"
}
}
})
if err then return nil, err end
| Dateifeld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
name |
string | ja | Formularfeldname |
filename |
string | nein | Originaler Dateiname |
content |
string | ja* | Dateiinhalt |
reader |
userdata | ja* | Alternative: io.Reader für Inhalt |
content_type |
string | nein | Derzeit ignoriert: jeder hochgeladene Part wird unabhängig von diesem Feld immer mit Content-Type: application/octet-stream gesendet |
*Entweder content oder reader ist erforderlich.
Die festgelegte Runtime liest einen reader vor dem Dispatch vollständig in den Speicher, schließt ihn nicht und meldet einen Leseabbruch ungleich EOF nicht separat; sie kann die bis zum Fehler gesammelten Bytes senden. Verwenden Sie für bereits begrenzte Daten vorzugsweise content und schließen Sie vom Aufrufer besessene Reader nach der Anfrage. content_type wird in Runtime v0.3.32a zwar geparst, aber nicht an den Transport weitergereicht; Upload-Parts verwenden daher dessen Standardwert.
Reader-basierte Dateien werden in dieser Version nur bei Einzelanfragen unterstützt. request_batch reicht das Feld content weiter, verwirft jedoch einen geparsten reader; Batch-Datei-Uploads müssen content bereitstellen.
Timeout
-- Number: seconds
local resp, err = http_client.get(url, {timeout = 30})
if err then return nil, err end
-- String alternatives use Go duration format: "30s", "1m30s", or "1h".
TLS-Optionen
Konfigurieren Sie TLS-Einstellungen pro Anfrage für mTLS (Mutual TLS) und benutzerdefinierte CA-Zertifikate.
| Feld | Typ | Beschreibung |
|---|---|---|
cert |
string | Client-Zertifikat im PEM-Format |
key |
string | Privater Schlüssel des Clients im PEM-Format |
ca |
string | Benutzerdefiniertes CA-Zertifikat im PEM-Format |
server_name |
string | Servername für SNI-Verifizierung |
insecure_skip_verify |
boolean | TLS-Zertifikatsverifizierung überspringen |
cert und key müssen für mTLS zusammen angegeben werden. Das Feld ca ersetzt den System-Zertifikatspool durch eine benutzerdefinierte CA.
mTLS-Authentifizierung
local fs = require("fs")
local certs, volume_err = fs.get("app:certs")
if volume_err then return nil, volume_err end
local cert_pem, cert_err = certs:readfile("client.crt")
if cert_err then return nil, cert_err end
local key_pem, key_err = certs:readfile("client.key")
if key_err then return nil, key_err end
local resp, err = http_client.get("https://secure.example.com/api", {
tls = {
cert = cert_pem,
key = key_pem,
}
})
if err then return nil, err end
Benutzerdefinierte CA
local fs = require("fs")
local certs, volume_err = fs.get("app:certs")
if volume_err then return nil, volume_err end
local ca_pem, ca_err = certs:readfile("internal-ca.crt")
if ca_err then return nil, ca_err end
local resp, err = http_client.get("https://internal.example.com/api", {
tls = {
ca = ca_pem,
server_name = "internal.example.com",
}
})
if err then return nil, err end
Unsichere Verifizierung überspringen
TLS-Verifizierung für Entwicklungsumgebungen überspringen. Erfordert die Sicherheitsberechtigung http_client.insecure_tls.
local resp, err = http_client.get("https://localhost:8443/api", {
tls = {
insecure_skip_verify = true,
}
})
if err then return nil, err end
Response-Objekt
| Feld | Typ | Beschreibung |
|---|---|---|
status_code |
number | HTTP-Statuscode |
body |
string | Response-Body (wenn nicht streaming) |
body_size |
number | Body-Größe in Bytes (-1 wenn streaming) |
headers |
table | Response-Header |
cookies |
table | Response-Cookies |
url |
string | Finale URL (nach Weiterleitungen) |
stream |
Stream | Stream-Objekt (wenn 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, decode_err = json.decode(resp.body)
if decode_err then return nil, decode_err end
print("Content-Type:", resp.headers["Content-Type"])
end
Streaming-Responses
Setzen Sie stream = true, um eine Response inkrementell zu verarbeiten, statt den vollständigen Body zu puffern.
local resp, err = http_client.get("https://cdn.example.com/large-file.zip", {
stream = true
})
if err then
return nil, err
end
-- Process in chunks
local read_err
while true do
local chunk
chunk, read_err = resp.stream:read(65536)
if read_err or not chunk then break end
-- process chunk
end
local _, close_err = resp.stream:close()
if read_err then return nil, read_err end
if close_err then return nil, close_err end
| Stream-Methode | Gibt zurück | Beschreibung |
|---|---|---|
read(n?) |
string, error | Bis zu n Bytes lesen (Standard: Implementierungspuffer) |
close() |
boolean, error | Stream schließen |
resp.stream ist ein vollständiges Stream-Objekt; seek, stat und scanner stehen ebenfalls bereit. Der Aufrufer besitzt einen gestreamten Response-Body und sollte ihn auf jedem Rückkehrpfad schließen. Task-Cleanup ist nur ein Fallback, kein Ersatz für eine zeitnahe Freigabe.
Batch-Anfragen
request_batch führt mehrere Anfragen parallel aus.
local requests = {
{"GET", "https://api.example.com/users"},
{"GET", "https://api.example.com/products"},
{"POST", "https://api.example.com/log", {body = "event"}}
}
local responses, batch_errors = http_client.request_batch(requests)
if not responses then
return nil, batch_errors -- whole-batch dispatch or validation failure
end
if batch_errors then
for i = 1, #requests do
local err = batch_errors[i]
if err then
print("Request " .. i .. " failed:", err)
end
end
else
-- All succeeded
for i, resp in ipairs(responses) do
print("Response " .. i .. ":", resp.status_code)
end
end
| Parameter | Typ | Beschreibung |
|---|---|---|
requests |
table | Array von {method, url, options?} |
Gibt zurück: responses, errors - Arrays indiziert nach Anfrageposition
Hinweise:
- Anfragen werden gleichzeitig ausgeführt
- Streaming (
stream = true) wird in Batch nicht unterstützt - Reader-basierte Datei-Uploads werden im Batch nicht unterstützt; verwenden Sie
files[].content - Ergebnis-Arrays entsprechen der Anfragereihenfolge (1-indiziert)
URL-Kodierung
Kodieren
local encoded = http_client.encode_uri("hello world")
-- "hello+world"
local url = "https://api.example.com/search?q=" .. http_client.encode_uri(query)
Dekodieren
local decoded, err = http_client.decode_uri("hello+world")
if err then return nil, err end
-- "hello world"
Berechtigungen
HTTP-Anfragen unterliegen der Sicherheitsrichtlinienauswertung.
Sicherheitsaktionen
| Aktion | Ressource | Beschreibung |
|---|---|---|
http_client.request |
URL | Anfragen an bestimmte URLs erlauben/verweigern |
http_client.unix_socket |
Socket-Pfad | Unix-Socket-Verbindungen erlauben/verweigern |
http_client.private_ip |
IP-Adresse | Zugriff auf private IP-Bereiche erlauben/verweigern |
http_client.insecure_tls |
URL | Unsichere TLS-Verbindungen erlauben/verweigern (Verifizierung überspringen) |
network.select |
Netzwerk-Entry-ID | Routing über das in der Anfrage angegebene overlay_network erlauben/verweigern |
Zugriff prüfen
local security = require("security")
if security.can("http_client.request", "https://api.example.com/users") then
local resp, request_err = http_client.get("https://api.example.com/users")
if request_err then return nil, request_err end
end
SSRF-Schutz
Nicht-öffentliche IP-Bereiche sind standardmäßig blockiert. Zugriff erfordert die http_client.private_ip-Berechtigung auf der Adresse:
- Loopback, private Bereiche (10.x, 172.16-31.x, 192.168.x), Link-Local-Unicast und -Multicast sowie die unspezifizierte Adresse
- Carrier-Grade-NAT
100.64.0.0/10,192.0.0.0/24, Multicast224.0.0.0/4, reserviert240.0.0.0/4 - Dokumentations- und Benchmarking-Bereiche
192.0.2.0/24,198.18.0.0/15,198.51.100.0/24,203.0.113.0/24,2001:db8::/32 - IPv6-Multicast
ff00::/8
local resp, err = http_client.get("http://192.168.1.1/admin")
-- Error: not allowed: private IP 192.168.1.1
Die Prüfung läuft beim Verbindungsaufbau, nicht auf dem URL-String, und sie erfasst jede Adresse, auf die der Host auflöst. Ein Hostname, der auf mehrere Adressen auflöst, wird Adresse für Adresse geprüft: Eine verweigerte Adresse wird übersprungen und die nächste versucht, und die Anfrage schlägt erst fehl, wenn jeder Kandidat verweigert oder nicht erreichbar ist. Ein öffentlicher Hostname, der auf eine private Adresse auflöst, wird daher genau wie ein privates IP-Literal blockiert.
Redirects
Bis zu neun Redirects werden verfolgt; der zehnte schlägt mit stopped after 10 redirects fehl, wobei diese Zählung die ursprüngliche Anfrage einschließt.
Jeder Sprung wird für sich autorisiert. Bevor der Client einem Redirect folgt, wertet er http_client.request gegen die Ziel-URL aus und wendet die Private-IP-Prüfung darauf an, sodass eine erlaubte URL nicht per Weiterleitung zu einer verweigerten führen kann. Ein Sprung, der eine der beiden Prüfungen nicht besteht, bricht die Anfrage ab.
Siehe Sicherheitsmodell für Richtlinienkonfiguration.
Fehler
| Bedingung | Art | Wiederholbar |
|---|---|---|
| Sicherheitsrichtlinie verweigert | errors.PERMISSION_DENIED |
nein |
| Private IP blockiert | errors.PERMISSION_DENIED |
nein |
| Unix-Socket verweigert | errors.PERMISSION_DENIED |
nein |
| Unsichere TLS verweigert | errors.PERMISSION_DENIED |
nein |
| Ungültiges Batch-Element, Batch-Streaming oder ungültiges URI-Escape | errors.INVALID |
nein |
| Kein Kontext | errors.INTERNAL |
nein |
| Fehlerhafte Transport-URL oder Netzwerkfehler | errors.INTERNAL |
ja |
| Timeout | errors.INTERNAL |
ja |
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
Viele nicht unterstützte Optionswerte werden ignoriert, statt als strukturierte Fehler zurückgegeben zu werden. Ungültige Lua-Argumenttypen und ein leerer Batch lösen Lua-Argumentfehler aus. Validieren Sie von der Anwendung bereitgestellte Optionstabellen vor dem Aufruf des Clients.
Siehe Fehlerbehandlung für die Arbeit mit Fehlern.
Fügen Sie http_client zur Liste modules: des ausführbaren Eintrags hinzu, bevor Sie es per require laden. JSON- und Dateisystemrezepte erfordern außerdem json und fs.
Laden Sie Authentifizierungswerte aus einem anwendungseigenen Secret-Speicher und senden Sie sie nur über TLS.
Verwenden Sie insecure_skip_verify nur für kontrollierte Diagnose-Endpunkte. Es deaktiviert sowohl die Zertifikatsketten- als auch die Hostnamenprüfung.