guildenstern/websocketserver

Websocket server

Example

import locks
import guildenstern/[dispatcher, websocketserver]

var lock: Lock
initLock(lock)
var sockets = newSeq[SocketHandle]()

proc onUpgrade(): bool =
  {.gcsafe.}:
    withLock(lock):
      sockets.add(ws.socketdata.socket)
  true

proc afterUpgrade() =
  wsserver.send(ws.socketdata.socket, "websocket connected!")

proc onMessage() =
  {.gcsafe.}:
    withLock(lock):
      let reply = getMessage()
      discard wsserver.send(sockets, reply)

proc onClose(socketdata: ptr SocketData, cause: SocketCloseCause, msg: string) =
  {.gcsafe.}:
    withLock(lock):
      let index = sockets.find(socketdata.socket)
      sockets.del(index)

proc onRequest() =
  let html = """<!doctype html><title>Web socket chat</title>
  <script>
    let websocket = new WebSocket("ws://" + location.host.slice(0, -1) + '1')
    websocket.onmessage = function(evt) {
    document.getElementById("ul").appendChild(document.createElement("li")).innerHTML = evt.data }
  </script>
  <body><form action="" onsubmit="return false"><input id="input">
  <button type=submit onclick="websocket.send(getElementById('input').value+'.')">Send</button>
  </form><ul id="ul"></ul></body>"""
  reply(Http200, html)

let server = newWebsocketServer(onUpgrade, afterUpgrade, onMessage, onClose)
server.start(5051)
let httpserver = newHttpServer(onRequest)
httpserver.start(5050)
joinThreads(server.thread, httpserver.thread)
deinitLock(lock)

Types

Opcode = enum
  Cont = 0,                 ## continuation frame
  Text = 1,                 ## text frame
  Binary = 2,               ## binary frame
  Close = 8,                ## connection close
  Ping = 9,                 ## ping
  Pong = 10,                ## pong
  WsFail = 14                ## protocol failure / connection lost in flight
WebsocketContext = ref object of HttpContext
  opcode*: Opcode
WebsocketServer = ref object of HttpServer
  upgradeCallback*: WsUpgradeCallback
  afterUpgradeCallback*: WsAfterUpgradeCallback
  messageCallback*: WsMessageCallback
  
WsAfterUpgradeCallback = proc () {....gcsafe, nimcall, ...raises: [].}
WsDelivery = tuple[sockets: seq[posix.SocketHandle], message: ptr string,
                   binary: bool, states: seq[State]]
send takes pointer to this as parameter.

sockets: the websockets that should receive this message
message: the message to send
binary: whether the message contains bytes or chars

WsMessageCallback = proc () {....gcsafe, nimcall, ...raises: [].}
WsUpgradeCallback = proc (): bool {....gcsafe, nimcall, ...raises: [].}

Procs

proc getMessage(): string {....raises: [], tags: [], forbids: [].}
proc isMessage(message: string): bool {....raises: [], tags: [], forbids: [].}
proc isWebsocketContext(): bool {....raises: [], tags: [], forbids: [].}
proc newWebsocketServer(upgradecallback: WsUpgradeCallback;
                        afterupgradecallback: WsAfterUpgradeCallback;
                        onwsmessagecallback: WsMessageCallback;
                        onclosesocketcallback: OnCloseSocketCallback;
                        loglevel = LogLevel.WARN): WebsocketServer {....raises: [],
    tags: [], forbids: [].}
proc send(server: GuildenServer; delivery: ptr WsDelivery; timeoutsecs = 20;
          sleepmillisecs = 100): int {.discardable, ...raises: [], tags: [],
                                       forbids: [].}
Sends message to multiple websockets at once. Uses non-blocking I/O so that slow receivers do not slow down fast receivers.

Can be called from multiple threads in parallel.
timeoutsecs: a timeout after which sending is given up and all sockets with messages in-flight are closed
sleepmillisecs: if all in-flight receivers are blocking, will sleep for (sleepmillisecs * current thread load) milliseconds

Returns amount of websockets that had to be closed
proc send(server: GuildenServer; socket: posix.SocketHandle; message: string;
          timeoutsecs = 20; sleepmillisecs = 100): bool {.discardable,
    ...raises: [], tags: [], forbids: [].}
proc send(server: GuildenServer; sockets: seq[posix.SocketHandle];
          message: string; timeoutsecs = 20; sleepmillisecs = 100): bool {.
    discardable, ...raises: [], tags: [], forbids: [].}
proc sendPong(server: GuildenServer; socket: posix.SocketHandle) {....raises: [],
    tags: [], forbids: [].}

Templates

template ws(): untyped
Casts the socketcontext thread local variable into a WebsocketContext
template wsserver(): untyped
Casts the socketcontext.socketdata.server into a WebsocketServer