You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

197 lines
4.2 KiB
Lua

--[[lit-meta
name = "creationix/coro-websocket"
version = "3.1.1"
dependencies = {
"luvit/http-codec@3.0.0",
"creationix/websocket-codec@3.0.0",
"creationix/coro-net@3.3.0",
}
homepage = "https://github.com/luvit/lit/blob/master/deps/coro-websocket.lua"
description = "Websocket helpers assuming coro style I/O."
tags = {"coro", "websocket"}
license = "MIT"
author = { name = "Tim Caswell" }
]]
local uv = require('uv')
local httpCodec = require('http-codec')
local websocketCodec = require('websocket-codec')
local net = require('coro-net')
local function parseUrl(url)
local protocol, host, port, pathname = string.match(url, "^(wss?)://([^:/]+):?(%d*)(/?[^#?]*)")
local tls
if protocol == "ws" then
port = tonumber(port) or 80
tls = false
elseif protocol == "wss" then
port = tonumber(port) or 443
tls = true
else
return nil, "Sorry, only ws:// or wss:// protocols supported"
end
return {
host = host,
port = port,
tls = tls,
pathname = pathname
}
end
local function wrapIo(rawRead, rawWrite, options)
local closeSent = false
local timer
local function cleanup()
if timer then
if not timer:is_closing() then
timer:close()
end
timer = nil
end
end
local function write(message)
if message then
message.mask = options.mask
if message.opcode == 8 then
closeSent = true
rawWrite(message)
cleanup()
return rawWrite()
end
else
if not closeSent then
return write({
opcode = 8,
payload = ""
})
end
end
return rawWrite(message)
end
local function read()
while true do
local message = rawRead()
if not message then
return cleanup()
end
if message.opcode < 8 then
return message
end
if not closeSent then
if message.opcode == 8 then
write {
opcode = 8,
payload = message.payload
}
elseif message.opcode == 9 then
write {
opcode = 10,
payload = message.payload
}
end
return message
end
end
end
if options.heartbeat then
local interval = options.heartbeat
timer = uv.new_timer()
timer:unref()
timer:start(interval, interval, function ()
coroutine.wrap(function ()
local success, err = write {
opcode = 10,
payload = ""
}
if not success then
timer:close()
print(err)
end
end)()
end)
end
return read, write
end
-- options table to configure connection
-- options.path
-- options.host
-- options.port
-- options.tls
-- options.pathname
-- options.subprotocol
-- options.headers (as list of header/value pairs)
-- options.timeout
-- options.heartbeat
-- returns res, read, write (res.socket has socket)
local function connect(options)
options = options or {}
local config = {
path = options.path,
host = options.host,
port = options.port,
tls = options.tls,
encoder = httpCodec.encoder,
decoder = httpCodec.decoder,
}
local read, write, socket, updateDecoder, updateEncoder
= net.connect(config, options.timeout or 10000)
if not read then
return nil, write
end
local res
local success, err = websocketCodec.handshake({
host = options.host,
path = options.pathname,
protocol = options.subprotocol
}, function (req)
local headers = options.headers
if headers then
for i = 1, #headers do
req[#req + 1] = headers[i]
end
end
write(req)
res = read()
if not res then error("Missing server response") end
if res.code == 400 then
-- p { req = req, res = res }
local reason = read() or res.reason
error("Invalid request: " .. reason)
end
return res
end)
if not success then
return nil, err
end
-- Upgrade the protocol to websocket
updateDecoder(websocketCodec.decode)
updateEncoder(websocketCodec.encode)
read, write = wrapIo(read, write, {
mask = true,
heartbeat = options.heartbeat
})
res.socket = socket
return res, read, write
end
return {
parseUrl = parseUrl,
wrapIo = wrapIo,
connect = connect,
}