Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add uloop (ubox) based websocket server #102

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -119,6 +152,7 @@ The client and server modules depend on:
- luasec
- copas (optionally)
- lua-ev (optionally)
- libubox-lua (optionally)

# Install

Expand Down
23 changes: 23 additions & 0 deletions examples/echo-server-uloop.lua
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions squishy
Original file line number Diff line number Diff line change
Expand Up @@ -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"
149 changes: 149 additions & 0 deletions src/websocket/server_uloop.lua
Original file line number Diff line number Diff line change
@@ -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
}