Skip to content

WebSocket

A connection to a service that stays open, so that both sides can send at any time.

A spell only reaches the hosts that the owner of the server has listed in config/wizards-of-lua/net.luaconf. See Http.

What the other side sends waits in the socket until the spell reads it with receive, from the moment the socket is connected, so a spell can't miss a message. When 1024 messages or 16 MB are waiting, the socket stops reading and the other side has to wait.

A socket belongs to the spell that connected it, and closes when that spell ends. Other spells can use it until then.

To read or send data as JSON, see Json.

Example: Ask a service and print the answer

local socket = assert(WebSocket:connect("wss://chat.example.org/"))
socket:send(Json:encode({ op = "hello", name = spell.owner.name }))
local answer, why = socket:receive(100)
if answer then
  print(Json:decode(answer).text)
else
  print("No answer: " .. why)
end
socket:close()

Example: Serve a socket and events in one loop

queue:next(0) and socket:receive(0) return at once, so a short wait in one of them lets a single loop serve both.

local socket = assert(WebSocket:connect("wss://chat.example.org/"))
local queue = spell:collect("ChatMessageEvent")
while true do
  local text, why, code = socket:receive(0)
  if text then
    spell:execute("/say " .. text)
  elseif why == "closed" then
    print("The connection ended: " .. code)
    break
  end
  local event = queue:next(text and 0 or 1) --[[@as ChatMessageEvent?]]
  if event then
    socket:send(event.senderName .. ": " .. event.message)
  end
end

Fields

host: string — Read-Only

The name of the host this socket is connected to. Never the whole URL, which can hold a password.

open: boolean — Read-Only

true while the socket can send: until close is called or the connection ends. Messages may still wait in the socket after that.

Methods

connect(url: string, options?: WebSocketOptions) → socket: WebSocket?, reason: NetReason?

Connects to a URL, and waits until the connection stands or the timeout has passed. Call it on the class: WebSocket:connect(url, options).

nil means that no connection was made, and the reason says why. Because it waits, this function can't be called in an event interceptor.

A wrong option raises an error. Error messages never show the URL or a header's value, as they can hold a password.

Example

local socket, reason = WebSocket:connect("wss://chat.example.org/", {
  headers = { Authorization = "Bearer " .. token },
  timeout = 100,
})
if not socket then
  print("Can't connect: " .. reason)
end

Parameters

  • url: string — Where to connect to, starting with ws:// or wss://.
  • options?: WebSocketOptions — How to connect.

Returns

  • socket: WebSocket? — The socket, or nil if there is no connection.
  • reason: NetReason? — Why there is no connection.

close(code?: integer, reason?: string)

Closes the socket: sends what is still waiting to be sent, then tells the other side. The other side has 3 seconds to answer, and then the connection just ends. The function doesn't wait for that, so it can be called in an event interceptor. Closing a closed socket does nothing.

Messages that have arrived can still be read. After them, receive tells that the socket is closed.

Example

socket:close(1000, "Back in a minute")

Parameters

  • code?: integer — Why it closes: 1000, 1001, 1011, or from 3000 to 4999. What the numbers from 3000 on mean is up to the service. Defaults to 1000, which means "all done".
  • reason?: string — Why it closes, in words, up to 123 bytes.

receive(timeout?: integer) → message: string?, why: "closed"|"timeout"|nil, code: integer?, reason: string?

Returns the next message, and waits for it if necessary.

Without a timeout it waits for as long as it takes. With a timeout of 0 it doesn't wait at all, so that is the only way to call this function in an event interceptor.

Text arrives as a string in UTF-8. Bytes arrive as a string of bytes. A message may have up to 16 MB, and a larger one ends the connection with the code 1009.

When the connection has ended, the messages that arrived before that come first, and from then on every call returns nil, "closed" with the code and the reason. The code 1006 means that the connection broke off, without a word from the other side.

Example

local text, why, code, reason = socket:receive(20)
if text then
  print("Got: " .. text)
elseif why == "timeout" then
  print("Nothing for a second")
else
  print("Closed: " .. code .. " " .. reason)
end

Parameters

  • timeout?: integer — How many game ticks to wait at most. If omitted, it waits until a message arrives or the socket is closed.

Returns

  • message: string? — The message, or nil if there is none.
  • why: "closed"|"timeout"|nil — Why there is no message.
  • code: integer? — The close code, if the socket is closed.
  • reason: string? — The other side's words for it, if the socket is closed. May be empty.

send(text: string) → ok: boolean?, reason: "busy"|"closed"|"too_large"|nil

Sends a text. It returns at once and the text goes out in the background, so it can be called in an event interceptor. Messages go out in the order they were sent.

The text must be valid UTF-8: that's the rule for text in a WebSocket, and anything else raises an error.

nil means that this text won't go out. "closed" is the end of the connection. "busy" means that 16 MB are already waiting to be sent because the other side doesn't read fast enough, and sending can work again later. "too_large" is a text of more than 16 MB.

Example

local ok, reason = socket:send(Json:encode({ op = "ping" }))
if not ok then
  print("Not sent: " .. reason)
end

Parameters

  • text: string — What to send.

Returns

  • ok: boolean? — true if the text will go out, nil if it won't.
  • reason: "busy"|"closed"|"too_large"|nil — Why it won't.