GuildenServer is the abstract base class for web servers. The three concrete server implementations that currently ship with GuildenStern are guildenstern/httpserver, guildenstern/websocketserver and guildenstern/multipartserver. One server is associated with one TCP port.
GuildenServer mainly acts as the glue between everything else, offering set of callback hooks for others to fill in. In addition to GuildenServer, this module also introduces SocketContext, which is a container for data of one request in flight. SocketContext is inheritable, so concrete servers may add properties to it.
So, the overall architecture may be something like this: A reverse proxy (like https://caddyserver.com/) routes requests to multiple ports. Each of these ports is served by one concrete GuildenServer instance. To each server is attached one dispatcher, which listens to the port and triggers handlerCallbacks. The default guildenstern/dispatcher uses multithreading so that even requests arriving to the same port are served in parallel. During request handling, the default servers offer an inherited thread local SocketContext variable from which everything else is accessible, most notably the SocketData.server itself and the SocketData.socket that is being serviced.
Guides for writing your very own servers and dispatchers may appear later. For now, just study the source codes... (And if you invent something useful, please share it with us.)
Types
CloseOtherSocketCallback = proc (server: GuildenServer; socket: SocketHandle; cause: SocketCloseCause; msg: string = "") {. ...gcsafe, nimcall, ...raises: [].}
CloseSocketCallback = proc (socketdata: ptr SocketData; cause: SocketCloseCause; msg: string) {....gcsafe, nimcall, ...raises: [].}
GuildenServer {.inheritable.} = ref object port*: uint16 thread*: Thread[ptr GuildenServer] id*: int logCallback*: LogCallback loglevel*: LogLevel started*: bool internalThreadInitializationCallback*: ThreadInitializerCallback threadInitializerCallback*: ThreadInitializerCallback handlerCallback*: HandlerCallback suspendCallback*: SuspendCallback closeSocketCallback*: CloseSocketCallback closeOtherSocketCallback*: CloseOtherSocketCallback onCloseSocketCallback*: OnCloseSocketCallback
HandlerCallback = proc (socketdata: ptr SocketData) {.nimcall, ...gcsafe, raises: [].}
LogCallback = proc (loglevel: LogLevel; message: string) {....gcsafe, nimcall, ...raises: [].}
LogLevel = enum TRACE, DEBUG, INFO, NOTICE, WARN, ERROR, FATAL, NONE
OnCloseSocketCallback = proc (socketdata: ptr SocketData; cause: SocketCloseCause; msg: string) {....gcsafe, nimcall, ...raises: [].}
SocketCloseCause = enum Excepted = -1000, ## A Nim exception happened CloseCalled, ## Use this, when the server (your code) closes a socket AlreadyClosed, ## Another thread has closed the socket ClosedbyClient, ## Client closed the connection ConnectionLost, ## TCP/IP connection was dropped TimedOut, ## Client did not send/receive all expected data ProtocolViolated, ## Client was sending garbage NetErrored, ## Some operating system level error happened SecurityThreatened, ## Use this, when you decide to close socket for security reasosns DontClose ## Internal flag
- Parameter in close callbacks.
SocketContext {.inheritable.} = ref object socketdata*: ptr SocketData
SocketData = object server*: GuildenServer socket*: SocketHandle isserversocket*: bool flags*: int customdata*: pointer
-
Data associated with every incoming socket message.
This is available in SocketContext via socketdata pointer.
customdata pointer can be freely used in user code. SuspendCallback = proc (server: GuildenServer; sleepmillisecs: int) {.nimcall, ...gcsafe, raises: [].}
ThreadInitializerCallback = proc (server: GuildenServer) {.nimcall, ...gcsafe, raises: [].}
Vars
shutdownevent = newSelectEvent()
shuttingdown = false
- Global variable that all code is expected to observe and abide to.
socketcontext {.threadvar.}: SocketContext
Consts
GuildenSternVersion = "7.2.0"
Procs
proc `$`(x: SocketHandle): string {.inline, ...raises: [], tags: [], forbids: [].}
proc closeOtherSocket(server: GuildenServer; socket: posix.SocketHandle; cause: SocketCloseCause = CloseCalled; msg: string = "") {. ...gcsafe, nimcall, ...raises: [], tags: [RootEffect], forbids: [].}
- Call this to close an open socket that is not the socket currently being served.
proc closeSocket(cause = CloseCalled; msg = "") {....gcsafe, nimcall, ...raises: [], tags: [RootEffect], forbids: [].}
- Call this to close a socket yourself.
proc initialize(server: GuildenServer; loglevel: LogLevel) {....raises: [], tags: [], forbids: [].}
proc shutdown() {....raises: [], tags: [], forbids: [].}
- Sets shuttingdown to true and signals dispatcher loops to cease operation.
proc suspend(sleepmillisecs: int) {.inline, ...raises: [], tags: [RootEffect], forbids: [].}
- Instead of os.sleep, use this. This informs a dispatcher that your thread is waiting for something and therefore another thread should be allowed to run.
Templates
template handleRead(socketdata: ptr SocketData)
template initializeThread(server: ptr GuildenServer)
template log(theserver: GuildenServer; level: LogLevel; message: string)
- Calls logCallback, if it set. By default, the callback is set to echo the message, if level is same or higher than server's loglevel.