From 9906a0eb46d4b44863280ef6db494d8e8fa46a9c Mon Sep 17 00:00:00 2001 From: kcsoft Date: Sat, 12 Nov 2016 17:40:22 +0200 Subject: [PATCH] add uloop (ubox) based websocket server --- README.md | 36 +++++++- examples/echo-server-uloop.lua | 23 +++++ squishy | 1 + src/websocket/server_uloop.lua | 149 +++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 examples/echo-server-uloop.lua create mode 100644 src/websocket/server_uloop.lua diff --git a/README.md b/README.md index 815ba24..74f29ca 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,11 @@ Clients are available in three different flavours: - coroutine based ([copas](http://keplerproject.github.com/copas)) - asynchronous ([lua-ev](https://github.com/brimworks/lua-ev)) -Servers are available as two different flavours: +Servers are available as three different flavours: - coroutine based ([copas](http://keplerproject.github.com/copas)) - asynchronous ([lua-ev](https://github.com/brimworks/lua-ev)) + - asynchronous ([libubox-lua](https://wiki.openwrt.org/doc/techref/libubox)) A webserver is NOT part of lua-websockets. If you are looking for a feature rich webserver framework, have a look at [orbit](http://keplerproject.github.com/orbit/) or others. It is no problem to work with a "normal" webserver and lua-websockets side by side (two processes, different ports), since websockets are not subject of the 'Same origin policy'. @@ -91,6 +92,38 @@ ev.Loop.default:loop() ``` + +## libubox-lua echo server +This implements a basic echo server via Websockets protocol. Once you are connected with the server, all messages you send will be returned ('echoed') by the server immediately. + +```lua +require'uloop' + +uloop.init() + +local server = require'websocket'.server.uloop +server.listen +{ + port = 8080, + protocols = { + echo = function(ws) + local message = ws:receive() + if message then + ws:send(message) + else + ws:close() + return + end + end + }, + default = echo_handler +} + +uloop.run() + +``` + + ## Running test-server examples The folder test-server contains two re-implementations of the [libwebsocket](http://git.warmcat.com/cgi-bin/cgit/libwebsockets/) test-server.c example. @@ -119,6 +152,7 @@ The client and server modules depend on: - luasec - copas (optionally) - lua-ev (optionally) + - libubox-lua (optionally) # Install diff --git a/examples/echo-server-uloop.lua b/examples/echo-server-uloop.lua new file mode 100644 index 0000000..3c1d817 --- /dev/null +++ b/examples/echo-server-uloop.lua @@ -0,0 +1,23 @@ +require'uloop' + +uloop.init() + +local server = require'websocket'.server.uloop +server.listen +{ + port = 8080, + protocols = { + echo = function(ws) + local message = ws:receive() + if message then + ws:send(message) + else + ws:close() + return + end + end + }, + default = echo_handler +} + +uloop.run() \ No newline at end of file diff --git a/squishy b/squishy index 68c43de..eea519a 100644 --- a/squishy +++ b/squishy @@ -10,6 +10,7 @@ Module "websocket.bit" "src/websocket/bit.lua" Module "websocket.client_ev" "src/websocket/client_ev.lua" Module "websocket.ev_common" "src/websocket/ev_common.lua" Module "websocket.server_ev" "src/websocket/server_ev.lua" +Module "websocket.server_uloop" "src/websocket/server_uloop.lua" Main "src/websocket.lua" Output "websocket.lua" diff --git a/src/websocket/server_uloop.lua b/src/websocket/server_uloop.lua new file mode 100644 index 0000000..bf57acb --- /dev/null +++ b/src/websocket/server_uloop.lua @@ -0,0 +1,149 @@ + +local socket = require'socket' +local tools = require'websocket.tools' +local frame = require'websocket.frame' +local handshake = require'websocket.handshake' +local sync = require'websocket.sync' +require'uloop' +local tconcat = table.concat +local tinsert = table.insert + +local clients = {} +local sock_clients = {} +local sock_events = {} + +local client = function(sock,protocol) + + local self = {} + + self.state = 'OPEN' + self.is_server = true + + self.sock_send = function(self,...) + return sock:send(...) + end + + self.sock_receive = function(self,...) + return sock:receive(...) + end + + self.sock_close = function(self) + sock_clients[sock:getfd()] = nil + sock_events[sock:getfd()]:delete() + sock_events[sock:getfd()] = nil + sock:shutdown() + sock:close() + end + + self = sync.extend(self) + + self.on_close = function(self) + clients[protocol][self] = nil + end + + self.broadcast = function(self,...) + for client in pairs(clients[protocol]) do + if client ~= self then + client:send(...) + end + end + self:send(...) + end + + return self +end + + +local listen = function(opts) + + assert(opts and (opts.protocols or opts.default)) + local on_error = opts.on_error or function(s) print(s) end + local listener = socket.tcp() + listener:settimeout(0) + listener:bind("*", opts.port or 80) + listener:listen() + + local protocols = {} + if opts.protocols then + for protocol in pairs(opts.protocols) do + clients[protocol] = {} + tinsert(protocols,protocol) + end + end + -- true is the 'magic' index for the default handler + clients[true] = {} + + tcp_event = uloop.fd_add(listener, function(tfd, events) + tfd:settimeout(3) + local new_conn = assert(tfd:accept()) + if new_conn ~= nil then + local request = {} + repeat + local line,err = new_conn:receive('*l') + if line then + request[#request+1] = line + else + new_conn:close() + if on_error then + on_error('invalid request') + end + return + end + until line == '' + local upgrade_request = tconcat(request,'\r\n') + local response,protocol = handshake.accept_upgrade(upgrade_request,protocols) + if not response then + new_conn:send(protocol) + new_conn:close() + if on_error then + on_error('invalid request') + end + return + end + new_conn:send(response) + local handler + local new_client + local protocol_index + if protocol and opts.protocols[protocol] then + protocol_index = protocol + handler = opts.protocols[protocol] + elseif opts.default then + -- true is the 'magic' index for the default handler + protocol_index = true + handler = opts.default + else + new_conn:close() + if on_error then + on_error('bad protocol') + end + return + end + new_client = client(new_conn, protocol_index) + sock_clients[new_conn:getfd()] = new_client + clients[protocol_index][new_client] = true + + sock_events[new_conn:getfd()] = uloop.fd_add(new_conn, function(csocket, events) + handler(sock_clients[csocket:getfd()]) + end, uloop.ULOOP_READ) + end + end, uloop.ULOOP_READ) + + local self = {} + self.close = function(_,keep_clients) + tcp_event:delete() + tcp_event = nil + if not keep_clients then + for protocol,clients in pairs(clients) do + for client in pairs(clients) do + client:close() + end + end + end + end + return self +end + +return { + listen = listen, + clients = clients +}