WebSocket 클라이언트

websocket 모듈은 WebSocket 서버에 대한 양방향 클라이언트 연결을 생성합니다.

이 페이지는 부분적인 연결 및 구독 예제를 제공하는 API 레퍼런스입니다. 엔드포인트 URL, 토큰, 메시지 핸들러와 데이터는 애플리케이션이 제공합니다. 수명주기 예제는 모든 종료 및 확인된 오류 경로에서 클라이언트를 닫습니다.

로딩

local websocket = require("websocket")

불러오기 전에 실행 엔트리의 modules: 목록에 websocket을 추가하세요. 전역 channel은 항상 사용할 수 있으며 JSON과 타임아웃 예제에는 json과 time도 필요합니다.

연결

connect

기본 옵션으로 WebSocket 연결을 엽니다.

local client, err = websocket.connect("wss://api.example.com/ws")
if err then
    return nil, err
end

옵션 테이블을 전달하여 연결을 구성합니다.

local client, err = websocket.connect("wss://api.example.com/ws", {
    headers = {
        ["Authorization"] = "Bearer " .. token
    },
    protocols = {"graphql-ws"},
    dial_timeout = "10s",
    read_timeout = "30s",
    compression = websocket.COMPRESSION.CONTEXT_TAKEOVER
})
if err then
    return nil, err
end
파라미터 타입 설명
url string WebSocket URL (ws:// 또는 wss://)
options table 연결 옵션 (선택적)

반환: Client, error

연결 옵션

옵션 타입 설명
headers table 핸드셰이크용 HTTP 헤더
protocols table WebSocket 서브프로토콜
dial_timeout number/string 연결 타임아웃 (ms 또는 "5s")
read_timeout number/string 읽기 타임아웃
write_timeout number/string 쓰기 타임아웃
compression number/string 압축 모드 (상수 참조) 또는 "disabled", "context_takeover", "no_context_takeover"
compression_threshold number 압축 최소 크기 (0-100MB)
read_limit number 최대 메시지 크기 (0-128MB)
channel_capacity number 수신 채널 버퍼 (1-10000)

타임아웃 형식: 숫자는 밀리초이며 문자열은 "5s", "1m" 같은 Go duration 형식을 사용합니다.

잘못된 타임아웃 문자열과 범위를 벗어나거나 지원되지 않는 옵션 값은 무시되어 기본값이 유지됩니다.

메시지 보내기

텍스트 메시지

client:send("Hello, Server!")

client:send("Hello, Server!")

-- Send JSON
local payload, encode_err = json.encode({
    type = "subscribe",
    channel = "orders"
})
if encode_err then return nil, encode_err end
client:send(payload)

바이너리 메시지

client:send(binary_data, websocket.BINARY)
파라미터 타입 설명
data string 메시지 내용
type number websocket.TEXT (1) 또는 websocket.BINARY (2)

메시지가 전송될 때까지 양보(yield)합니다. 반환값이 없습니다.

Ping

client:ping()

핑이 전송될 때까지 양보(yield)합니다. 반환값이 없습니다.

메시지 받기

channel()은 수신 channel을 반환하고 receive()는 별칭입니다. 첫 호출은 런타임이 구독을 만드는 동안 yield하며, 이후 호출은 같은 channel을 즉시 반환합니다. 구독 실패는 nil, error를 반환하며 channel은 channel.select와 함께 사용할 수 있습니다.

기본 수신

local ch, err = client:channel()
if err then
    client:close()
    return nil, err
end

local msg, ok = ch:receive()
if ok then
    print("Type:", msg.type)  -- "text" or "binary"
    print("Data:", msg.data)
end

local _, close_err = client:close()
if close_err then return nil, close_err end

메시지 루프

local json = require("json")

local ch, err = client:channel()
if err then
    client:close()
    return nil, err
end

while true do
    local msg, ok = ch:receive()
    if not ok then
        break  -- Connection closed
    end

    if msg.type == "text" then
        local data, decode_err = json.decode(msg.data)
        if decode_err then
            client:close()
            return nil, decode_err
        end
        handle_message(data)
    end
end

local _, close_err = client:close()
if close_err then return nil, close_err end

Select와 함께

local json = require("json")
local time = require("time")

local ch, ch_err = client:channel()
if ch_err then
    client:close()
    return nil, ch_err
end

local timeout, timeout_err = time.after("30s")
if timeout_err then
    client:close()
    return nil, timeout_err
end

while true do
    local r = channel.select {
        ch:case_receive(),
        timeout:case_receive()
    }

    if r.channel == timeout then
        client:ping()  -- Keep-alive
        timeout, timeout_err = time.after("30s")
        if timeout_err then
            client:close()
            return nil, timeout_err
        end
    elseif not r.ok then
        break
    else
        local data, decode_err = json.decode(r.value.data)
        if decode_err then
            client:close()
            return nil, decode_err
        end
        process(data)
    end
end

local _, close_err = client:close()
if close_err then return nil, close_err end

메시지 객체

필드 타입 설명
type string "text" 또는 "binary"
data string? 메시지 내용(알 수 없는 페이로드 타입의 경우 nil)

연결 닫기

local _, close_err = client:close(websocket.CLOSE_CODES.NORMAL, "Session ended")
if close_err then return nil, close_err end

-- Omitting both arguments also uses normal close code 1000.
-- Use INTERNAL_ERROR with an application-owned reason for a failed session.
파라미터 타입 설명
code number 닫기 코드 (1000-4999), 기본값 1000
reason string 닫기 이유 (선택적)

닫기 프레임이 전송될 때까지 양보(yield)합니다.

상수

메시지 타입

-- Numeric (for send)
websocket.TEXT    -- 1
websocket.BINARY  -- 2

-- Compatibility string constants
websocket.TYPE_TEXT    -- "text"
websocket.TYPE_BINARY  -- "binary"
websocket.TYPE_PING    -- "ping"
websocket.TYPE_PONG    -- "pong"
websocket.TYPE_CLOSE   -- "close"

수신 메시지 객체는 "text"와 "binary"만 사용합니다. ping/pong은 전송 계층이 처리하고 종료 이벤트는 "close" 메시지 대신 channel을 닫습니다.

압축 모드

websocket.COMPRESSION.DISABLED         -- 0 (no compression)
websocket.COMPRESSION.CONTEXT_TAKEOVER -- 1 (sliding window)
websocket.COMPRESSION.NO_CONTEXT       -- 2 (per-message)

닫기 코드

상수 코드 설명
NORMAL 1000 정상 종료
GOING_AWAY 1001 서버 종료 중
PROTOCOL_ERROR 1002 프로토콜 에러
UNSUPPORTED_DATA 1003 지원되지 않는 데이터 타입
RESERVED 1004 예약됨
NO_STATUS 1005 상태 수신되지 않음
ABNORMAL_CLOSURE 1006 연결 끊김
INVALID_PAYLOAD 1007 잘못된 프레임 페이로드
POLICY_VIOLATION 1008 정책 위반
MESSAGE_TOO_BIG 1009 메시지 너무 큼
MANDATORY_EXTENSION 1010 필수 확장이 협상되지 않음
INTERNAL_ERROR 1011 서버 에러
SERVICE_RESTART 1012 서버 재시작 중
TRY_AGAIN_LATER 1013 서버 과부하
BAD_GATEWAY 1014 게이트웨이 에러
TLS_HANDSHAKE 1015 TLS 핸드셰이크 실패
local _, close_err = client:close(websocket.CLOSE_CODES.NORMAL, "Done")
if close_err then return nil, close_err end

예제

실시간 채팅

local json = require("json")

local function connect_chat(room_id, token, on_message)
    local client, err = websocket.connect("wss://chat.example.com/ws", {
        headers = {["Authorization"] = "Bearer " .. token}
    })
    if err then
        return nil, err
    end

    -- Join room. Runtime v0.3.32a does not expose transport send failures.
    local join_payload, encode_err = json.encode({
        type = "join",
        room = room_id
    })
    if encode_err then
        client:close()
        return nil, encode_err
    end
    client:send(join_payload)

    -- Message loop
    local ch, channel_err = client:channel()
    if channel_err then
        client:close()
        return nil, channel_err
    end
    while true do
        local msg, ok = ch:receive()
        if not ok then break end

        local data, decode_err = json.decode(msg.data)
        if decode_err then
            client:close()
            return nil, decode_err
        end
        on_message(data)
    end

    local _, close_err = client:close()
    if close_err then return nil, close_err end
    return true
end

Keep-Alive와 함께 가격 스트림

local json = require("json")
local time = require("time")

local client, err = websocket.connect("wss://stream.example.com/prices")
if err then
    return nil, err
end

local subscribe_payload, encode_err = json.encode({
    action = "subscribe",
    symbols = {"BTC-USD", "ETH-USD"}
})
if encode_err then
    client:close()
    return nil, encode_err
end
client:send(subscribe_payload)

local ch, channel_err = client:channel()
if channel_err then
    client:close()
    return nil, channel_err
end

local heartbeat, heartbeat_err = time.after("30s")
if heartbeat_err then
    client:close()
    return nil, heartbeat_err
end

while true do
    local r = channel.select {
        ch:case_receive(),
        heartbeat:case_receive()
    }

    if r.channel == heartbeat then
        client:ping()
        heartbeat, heartbeat_err = time.after("30s")
        if heartbeat_err then
            client:close()
            return nil, heartbeat_err
        end
    elseif not r.ok then
        break  -- Connection closed
    else
        local price, decode_err = json.decode(r.value.data)
        if decode_err then
            client:close()
            return nil, decode_err
        end
        update_price(price.symbol, price.value)
    end
end

local _, close_err = client:close()
if close_err then return nil, close_err end

권한

WebSocket 연결은 보안 정책 평가 대상입니다.

보안 액션

액션 리소스 설명
websocket.connect - WebSocket 연결 허용/거부
websocket.connect.url URL 특정 URL 연결 허용/거부

정책 설정은 보안 모델을 참조하세요.

에러

조건 종류 재시도 가능
연결 비활성화됨 errors.PERMISSION_DENIED 아니오
URL 허용되지 않음 errors.PERMISSION_DENIED 아니오
컨텍스트 없음 errors.INTERNAL 아니오
연결 실패 errors.INTERNAL 예
잘못된 연결 ID errors.INTERNAL 아니오
구독 실패 errors.INTERNAL 예
구독 중 프로세스 컨텍스트 없음 errors.INTERNAL 아니오
닫기 실패 errors.INTERNAL 아니오

빈 URL, 테이블이 아닌 옵션, 잘못된 인자 타입, 수신 channel 요청 시 실행 컨텍스트나 PID가 없는 경우는 구조화된 에러가 아니라 Lua 에러를 발생시킵니다.

local client, err = websocket.connect(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

에러 처리는 에러 처리를 참조하세요.