WebSocket Server

Astra offers a WebSocket server powered by axum. Server creation takes a route similar to any other normal routes:

local server = require("astra.http").server:new()

local function handle_socket(socket)
  print("Connection opened!")
end

server:websocket("/", handle_socket)

server:run()

After creating our Astra server, we have to create a function to handle WebSocket behaviour, this function is then used as a callback by the server:websocket() function, which first takes the route for our websocket server, then the callback function.

We can print out received messages from our client like this:


local function handle_socket(socket)
  print("Connection opened!")

  while true do
    pprint(socket:recv())
  end
end

For a minimal websocket client, you can paste this code in your browser's console.

// Replace this with your appropriate address and route
const socket = new WebSocket("http://localhost:8080/");

socket.addEventListener("open", (event) => {
  console.log("WebSocket connection opened");

  // Example: send a greeting or trigger a ping behavior
  socket.send("Hello from JS client!");
});

// Listen for messages
socket.addEventListener("message", (event) => {
  console.log("Message from server:", event.data);
  // You can add your logic here to handle incoming messages
});

// Optional: Listen for errors
socket.addEventListener("error", (event) => {
  console.error("WebSocket error:", event);
});

// Optional: Listen for connection close
socket.addEventListener("close", (event) => {
  console.log("WebSocket connection closed:", event.code, event.reason);
});

NOTE: All further example code will be placed inside the while loop of our callback function

You can send 5 types of messages from a WebSocket: text, bytes, ping, pong, and a close frame. The first 4 all take a string as their message, with bytes, ping, and pong also being able to take a table of 8-bit unsigned bytes, a close frame takes a close code and a reason, which is an optional string, both of which should be places inside a lua table.

-- The first parameter is a type:
  -- text
  -- bytes
  -- ping
  -- pong
  -- close

socket:send("text", "hey there, this is a very informative message.")

-- There are also specialized functions for each type, which skip a lot of type checking to be more direct and concise:
socket:send_text("yet another informative message")

-- I can send a string of bytes:
socket:send_bytes("this will be illegible soon")
-- Or a table of them:
socket:send_ping({0, 88, 14, 67, 45})
socket:send_pong({17, 38, 80, 0, 85})

-- I can also send a close frame, first with the close code, and then the reason:
socket:send_close({code = 1000, reason = "finally, done with everything"})
-- The reason is optional, so you can also just go with this:
socket:send_close(1000)
-- If you're in a rush, you can close it uncleanly too, like this:
socket:send_close()
socket