HTTPクライアント
http_client モジュールは、ヘッダー、クエリパラメータ、フォーム、ファイルアップロード、認証、TLS オプション、ストリーミングレスポンス、並行バッチを使用して HTTP リクエストを送信します。
このページは部分的なリクエストレシピを含む API リファレンスです。URL、トークン、認証情報、リクエストデータ、証明書素材は周囲のアプリケーションから与えられます。例ではレスポンスを使用する前に Response, error を確認し、ストリームボディを明示的に閉じます。
ロード
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) -- response body
POSTリクエスト
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リクエスト
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リクエスト
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リクエスト
local resp, err = http_client.delete("https://api.example.com/users/123", {
headers = {["Authorization"] = "Bearer " .. token}
})
if err then return nil, err end
HEADリクエスト
ヘッダーのみを返し、ボディなし。
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"]
カスタムメソッド
local resp, err = http_client.request("PROPFIND", "https://dav.example.com/folder", {
headers = {["Depth"] = "1"}
})
if err then return nil, err end
| パラメータ | 型 | 説明 |
|---|---|---|
method |
string | HTTPメソッド |
url |
string | リクエストURL |
options |
table | リクエストオプション(オプション) |
リクエストオプション
| フィールド | 型 | 説明 |
|---|---|---|
headers |
table | リクエストヘッダー {["Name"] = "value"} |
body |
string | リクエストボディ |
query |
table | クエリパラメータ {key = "value"} |
form |
table | フォームデータ(Content-Typeを自動設定) |
files |
table | ファイルアップロード(ファイル定義の配列) |
cookies |
table | リクエストCookie {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オプションを参照) |
overlay_network |
string | ネットワークオーバーレイ経由でルーティング — network.socks5 / network.tailscale / network.i2p エントリのレジストリ ID |
クエリパラメータ
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
ヘッダーと認証
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
フォームデータ
local resp, err = http_client.post("https://api.example.com/login", {
form = {
username = username,
password = password
}
})
if err then return nil, err end
ファイルアップロード
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
| ファイルフィールド | 型 | 必須 | 説明 |
|---|---|---|---|
name |
string | はい | フォームフィールド名 |
filename |
string | いいえ | 元のファイル名 |
content |
string | yes* | ファイル内容 |
reader |
userdata | yes* | 代替: 内容用のio.Reader |
content_type |
string | no | 現在は無視されます: アップロードされる各パートは、このフィールドに関わらず常に Content-Type: application/octet-stream で送信されます |
*contentまたはreaderのいずれかが必須。
固定されたランタイムはディスパッチ前に reader 全体をメモリへ読み込み、閉じず、EOF 以外の読み取り失敗を個別には公開しません。その失敗までに蓄積したバイトを送信することがあります。サイズが制限済みのデータには content を優先し、呼び出し元所有の reader はリクエスト後に閉じてください。content_type は解析されますがランタイム v0.3.32a では転送されないため、アップロード part は transport のデフォルトを使用します。
reader ベースのファイルは、このリリースでは単一リクエスト呼び出しだけでサポートされます。request_batch は content を転送しますが解析済み reader を破棄するため、バッチファイルアップロードでは content を指定してください。
タイムアウト
-- 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オプション
リクエストごとのTLS設定で、mTLS(相互TLS)やカスタムCA証明書を構成する。
| フィールド | 型 | 説明 |
|---|---|---|
cert |
string | PEM形式のクライアント証明書 |
key |
string | PEM形式のクライアント秘密鍵 |
ca |
string | PEM形式のカスタムCA証明書 |
server_name |
string | SNI検証用のサーバー名 |
insecure_skip_verify |
boolean | TLS証明書検証をスキップ |
mTLSにはcertとkeyの両方を一緒に指定する必要がある。caフィールドはシステム証明書プールをカスタムCAで上書きする。
mTLS認証
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
insecure_skip_verify は管理下の診断 endpoint にのみ使用してください。証明書チェーン検証と hostname 検証の両方を無効にします。
カスタム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
安全でない検証スキップ
開発環境向けにTLS検証をスキップする。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
レスポンスオブジェクト
| フィールド | 型 | 説明 |
|---|---|---|
status_code |
number | HTTPステータスコード |
body |
string | レスポンスボディ(ストリーミングでない場合) |
body_size |
number | ボディサイズ(バイト単位)(ストリーミング時は-1) |
headers |
table | レスポンスヘッダー |
cookies |
table | レスポンスCookie |
url |
string | 最終URL(リダイレクト後) |
stream |
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, decode_err = json.decode(resp.body)
if decode_err then return nil, decode_err end
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
-- 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 メソッド | 戻り値 | 説明 |
|---|---|---|
read(n?) |
string, error | 最大nバイトを読み取り(デフォルト: 実装のバッファ) |
close() |
boolean, error | ストリームを閉じる |
resp.stream は完全な stream オブジェクトです — seek、stat、scanner も利用できます。
バッチリクエスト
複数のリクエストを並行して実行。
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
| パラメータ | 型 | 説明 |
|---|---|---|
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")
if err then return nil, err end
-- "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(検証スキップ)の許可/拒否 |
network.select |
ネットワークエントリID | リクエストで指定されたoverlay_network経由のルーティングの許可/拒否 |
アクセス確認
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保護
パブリックでないIP範囲はデフォルトでブロック。アクセスにはそのアドレスに対するhttp_client.private_ip権限が必要:
- ループバック、プライベート(10.x、172.16-31.x、192.168.x)、リンクローカルユニキャストおよびマルチキャスト、未指定アドレス
- キャリアグレードNAT
100.64.0.0/10、192.0.0.0/24、マルチキャスト224.0.0.0/4、予約済み240.0.0.0/4 - ドキュメント用およびベンチマーク用の範囲
192.0.2.0/24、198.18.0.0/15、198.51.100.0/24、203.0.113.0/24、2001:db8::/32 - IPv6マルチキャスト
ff00::/8
local resp, err = http_client.get("http://192.168.1.1/admin")
-- Error: not allowed: private IP 192.168.1.1
チェックはURL文字列に対してではなくダイヤル時に行われ、ホストが解決するすべてのアドレスを対象とする。複数のアドレスに解決されるホスト名はアドレスごとにチェックされ、拒否されたアドレスはスキップして次が試される。リクエストが失敗するのは、すべての候補が拒否されたか到達不能な場合のみ。したがって、プライベートアドレスに解決されるパブリックなホスト名は、プライベートIPリテラルとまったく同様にブロックされる。
リダイレクト
リダイレクトは最大9回まで追跡され、10回目はstopped after 10 redirectsで失敗する。この数には元のリクエストが含まれる。
各ホップは個別に認可される。リダイレクトを追跡する前に、クライアントは対象URLに対してhttp_client.requestを評価し、プライベートIPチェックを適用する。そのため、許可されたURLをリダイレクトで拒否されたURLへの到達に利用することはできない。いずれかのチェックに失敗したホップはリクエストを中止する。
ポリシー設定についてはセキュリティモデルを参照。
エラー
| 条件 | 種別 | 再試行可能 |
|---|---|---|
| セキュリティポリシーが拒否 | 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
エラーの処理についてはエラー処理を参照。