HTTP 클라이언트
HTTP 클라이언트
섹션 제목: “HTTP 클라이언트”외부 서비스에 HTTP 요청을 보냅니다. 모든 HTTP 메서드, 헤더, 쿼리 파라미터, 폼 데이터, 파일 업로드, 스트리밍 응답, 동시 배치 요청을 지원합니다.
local http_client = require("http_client")HTTP 메서드
섹션 제목: “HTTP 메서드”모든 메서드는 동일한 시그니처를 공유합니다: method(url, options?) 반환 Response, error.
GET 요청
섹션 제목: “GET 요청”local resp, err = http_client.get("https://api.example.com/users")if err then return nil, errend
print(resp.status_code) -- 200print(resp.body) -- 응답 본문POST 요청
섹션 제목: “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 요청
섹션 제목: “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 요청
섹션 제목: “PATCH 요청”local resp, err = http_client.patch("https://api.example.com/users/123", { body = json.encode({status = "active"})})DELETE 요청
섹션 제목: “DELETE 요청”local resp, err = http_client.delete("https://api.example.com/users/123", { headers = {["Authorization"] = "Bearer " .. token}})HEAD 요청
섹션 제목: “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 | 쿼리 파라미터 {key = "value"} |
form | table | 폼 데이터 (Content-Type 자동 설정) |
files | table | 파일 업로드 (파일 정의 배열) |
cookies | table | 요청 쿠키 {name = "value"} |
auth | table | Basic auth {user = "name", pass = "secret"} |
timeout | number/string | 타임아웃: 초 단위 숫자 또는 "30s", "1m" 같은 문자열 |
stream | boolean | 버퍼링 대신 응답 본문 스트리밍 |
max_response_body | number | 최대 응답 크기 바이트 (0 = 기본값) |
unix_socket | string | Unix 소켓 경로로 연결 |
tls | table | 요청별 TLS 설정 (TLS 옵션 참조) |
쿼리 파라미터
섹션 제목: “쿼리 파라미터”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 인증서를 위한 요청별 TLS 설정을 구성합니다.
| 필드 | 타입 | 설명 |
|---|---|---|
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 인증
섹션 제목: “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
섹션 제목: “커스텀 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 검증을 건너뜁니다. 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 | 응답 쿠키 |
url | string | 최종 URL (리다이렉트 후) |
stream | Stream | 스트림 객체 (stream = true인 경우) |
local resp, err = http_client.get("https://api.example.com/data")if err then return nil, errend
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, errend
-- 청크로 처리while true do local chunk, err = resp.stream:read(65536) if err or not chunk then break end -- 청크 처리endresp.stream:close()| 스트림 메서드 | 반환 | 설명 |
|---|---|---|
read(n?) | string, error | 최대 n 바이트 읽기 (기본값: 구현 버퍼) |
close() | boolean, error | 스트림 닫기 |
resp.stream은 완전한 스트림 객체입니다 — seek, stat, scanner도 사용할 수 있습니다.
배치 요청
섹션 제목: “배치 요청”여러 요청을 동시에 실행합니다.
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 endelse -- 모두 성공 for i, resp in ipairs(responses) do print("Response " .. i .. ":", resp.status_code) endend| 파라미터 | 타입 | 설명 |
|---|---|---|
requests | table | {method, url, options?} 배열 |
반환: responses, errors - 요청 위치별로 인덱싱된 배열
참고:
- 요청은 동시에 실행됨
- 배치에서는 스트리밍(
stream = true)이 지원되지 않음 - 결과 배열은 요청 순서와 일치 (1-인덱싱)
URL 인코딩
섹션 제목: “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")endSSRF 보호
섹션 제목: “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정책 설정은 보안 모델을 참조하세요.
| 조건 | 종류 | 재시도 가능 |
|---|---|---|
| 보안 정책 거부 | 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, errend에러 처리는 에러 처리를 참조하세요.