Net API
The Net API provides:
HTTP(S) helpers
net_http_get(url, timeout_ms?)net_http_post(url, content_type, body, timeout_ms?)
WebSocket client using WinHTTP’s WebSocket support
ws_connect(url, timeout_ms?) -> websocket userdataWebSocket methods:
send_text,send_binary,send_json,recv,poll,is_open,close
Supported URL schemes:
HTTP:
http://,https://WebSocket:
ws://,wss://(internally mapped tohttp:///https://for WinHTTP)Works with hostnames and IP addresses, with or without custom ports.
⚠ These are low-level primitives intended for advanced users. Higher-level helpers/wrappers can be built on top, but are not part of this page.
HTTP API
net_http_get
net_http_getok, status_code, body = net_http_get(url, timeout_ms?)Performs a synchronous HTTP/HTTPS GET.
Parameters
url : stringFull URL:
"https://example.com/api/test""http://127.0.0.1:8080/status"
timeout_ms : number (optional)Total timeout (ms) applied to resolve, connect, send, receive.
0ornil= default WinHTTP timeouts.
Returns
ok : booleantrueif the HTTP request succeeded at the transport level (WinHTTP OK).falseif there was a network/protocol error.
status_code : integerHTTP status code (200, 404, 500, ...)
0if the request failed before a response was received.
body : stringResponse body as raw bytes (Lua string).
Example
local ok, status, body = net_http_get("https://httpbin.org/get", 5000)
if not ok then
log("[HTTP] GET failed, status=" .. tostring(status))
return
end
log("[HTTP] GET status=" .. status)
log("[HTTP] GET body=" .. body)net_http_post
net_http_postok, status_code, body = net_http_post(url, content_type, body, timeout_ms?)Performs a synchronous HTTP/HTTPS POST with a request body.
Parameters
url : stringFull URL, same as
net_http_get.
content_type : stringMIME type, e.g.:
"application/json""application/x-www-form-urlencoded""text/plain; charset=utf-8"
body : stringRequest body as a Lua string (raw bytes).
timeout_ms : number (optional)Same semantics as
net_http_get.
Returns
ok : booleanstatus_code : integerbody : string(Same meaning asnet_http_get.)
Example – POST JSON
local payload = '{"hello":"world","value":123}'
local ok, status, resp = net_http_post(
"https://httpbin.org/post",
"application/json",
payload,
5000
)
if not ok then
log("[HTTP] POST failed, status=" .. tostring(status))
return
end
log("[HTTP] POST status=" .. status)
log("[HTTP] POST body=" .. resp)WebSocket API
The WebSocket API exposes:
A global constructor:
ws_connect(url, timeout_ms?)A userdata type
net_wswith methods:send_text,send_binary,send_jsonrecv,pollis_open,close
Internally, the implementation uses WinHTTP:
ws://→ internally mapped tohttp://wss://→ internally mapped tohttps://A background receive thread continuously reads frames and pushes complete messages into a queue.
Lua sees messages via
ws:recv()(blocking) orws:poll()(non-blocking).
Global: ws_connect
ws_connectws, err = ws_connect(url, timeout_ms?)Opens a client WebSocket connection.
Parameters
url : stringWebSocket URL, e.g.:
"wss://ws.postman-echo.com/raw""ws://127.0.0.1:9001/echo"
timeout_ms : number (optional)WinHTTP timeouts (resolve, connect, send, receive).
Returns
On success:
ws : userdata(typenet_ws)A WebSocket object with methods described below.
On failure:
nil, "error string"
Example
local ws, err = ws_connect("wss://ws.postman-echo.com/raw", 5000)
if not ws then
log("[WS] connect failed: " .. tostring(err))
return
end
log("[WS] connected: " .. tostring(ws)) -- e.g. "websocket(open)"WebSocket Object (net_ws)
net_ws)Once connected, you have a userdata with metatable "net_ws", exposing these methods:
ws:send_text(message)ws:send_binary(data)ws:send_json(value)ws:recv()ws:poll()ws:is_open()ws:close(code?)
And metamethods:
__gc– automatic cleanup__tostring–"websocket(open)"or"websocket(closed)"
ws:send_text
ws:send_textok = ws:send_text(message)Sends a UTF-8 text message.
Parameters
message : string– Lua string, treated as UTF-8.
Returns
ok : boolean–trueif the send call succeeded;falseotherwise.
Example
local ok = ws:send_text("hello from PCX")
log("[WS] send_text ok = " .. tostring(ok))ws:send_binary
ws:send_binaryok = ws:send_binary(data)Sends a binary WebSocket frame.
Parameters
data : string– raw bytes (Lua string).
⚠ Some public echo servers (e.g. Postman’s) close the connection when they receive binary frames. That’s a server behavior, not a bug in the API.
Returns
ok : boolean
Example
local bin = string.char(0,1,2,3,4,5)
local ok = ws:send_binary(bin)
log("[WS] send_binary ok = " .. tostring(ok))ws:send_json
ws:send_jsonok = ws:send_json(value)Sends a JSON text message. Supports:
value= string: sent directly as UTF-8.value= table: encoded via global Lua functionjson_encode.
Parameters
value : string | tablestring– assumed to already be valid JSON.table– will calljson_encode(value)to produce JSON.
Requirements
For
table:A global function
json_encodemust exist:function json_encode(tbl) -> stringIf
json_encodeis missing or throws,ws:send_jsonraises a Lua error.
Returns
ok : boolean
Example – string
local js = '{"type":"ping","time":123.45}'
ws:send_json(js)Example – table
-- Assuming json_encode is registered globally in this environment.
ws:send_json({
type = "ping",
time = perf_time(),
source = "pcx"
})ws:recv (blocking)
ws:recv (blocking)msg, is_text = ws:recv()Blocking receive that waits for a complete WebSocket message to be available in the queue.
Uses the internal message queue (filled by a background thread).
Does not block WinHTTP directly; it loops until:
A message is dequeued, or
The socket is closed and the queue is empty.
Returns
On message:
msg : string– message payload (text or binary).is_text : booleantruefor text framesfalsefor binary frames
On closed and empty:
nil
⚠ This is a blocking loop with a small sleep (
Sleep(1)). Best used in worker scripts or one-shot tests, not every frame in the UI thread.
Example
local msg, is_text = ws:recv()
if not msg then
log("[WS] recv: connection closed or error")
else
log("[WS] recv: kind=" .. (is_text and "text" or "binary")
.. " len=" .. #msg)
endws:poll (non-blocking)
ws:poll (non-blocking)msg, is_text_or_closed = ws:poll()Non-blocking receive from the internal message queue.
You get three possible outcomes:
Message available
msg : string– message payloadis_text_or_closed : boolean–true= text,false= binary
No message yet, still open
msg = nilis_text_or_closedis nil
Socket closed and queue empty
msg = nilis_text_or_closed = false
This makes it easy to integrate in per-frame loops:
msg != nil→ handle itmsg == nil and is_text_or_closed == nil→ nothing yetmsg == nil and is_text_or_closed == false→ closed
Example: per-frame polling
function on_frame()
if not ws then return end
local msg, flag = ws:poll()
if msg then
local kind = flag and "text" or "binary"
log("[WS] poll: " .. kind .. " msg=" .. msg)
elseif flag == false then
log("[WS] poll: socket closed")
ws = nil
end
endws:is_open
ws:is_openis_open = ws:is_open()Checks whether the socket is currently open.
Reflects the internal
is_openflag updated by the background thread and close logic.
Returns
trueif the socket is open.falseif it is closed or has encountered a fatal error.
Example
if ws and ws:is_open() then
ws:send_text("still here")
else
log("[WS] socket is closed")
endws:close
ws:closews:close(code?)Closes the WebSocket connection.
Signals the background receive thread to stop.
Sends a WebSocket close frame (best effort).
Waits for the thread to exit, then closes all WinHTTP handles.
Parameters
code : integer (optional)WebSocket close status. Defaults to
WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS.
Example
if ws then
ws:close() -- normal clean shutdown
ws = nil
endMetamethods
__gc
Called automatically when the Lua userdata is garbage collected.
Calls
ws_close_internal, deletes the critical section, freeslua_ws_t.You don’t call this directly; it’s tied to object lifetime.
__tostring
Used when you do:
tostring(ws)Returns
"websocket(open)"or"websocket(closed)".
Example:
log("WS object: " .. tostring(ws))Summary
Global HTTP:
ok, status, body = net_http_get(url, timeout_ms?)
ok, status, body = net_http_post(url, content_type, body, timeout_ms?)Global WebSocket:
ws, err = ws_connect(url, timeout_ms?)WebSocket object (net_ws):
ws:send_text(message) -- bool
ws:send_binary(data) -- bool
ws:send_json(value) -- bool (string or table with json_encode)
ws:recv() -- msg, is_text | nil (blocking)
ws:poll() -- msg, is_text | nil | nil,false
ws:is_open() -- bool
ws:close(code?) -- nil
tostring(ws) -- "websocket(open)" / "websocket(closed)"Last updated