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/streamingserver. One server is associated with one TCP port.
GuildenServer mainly acts as a connection 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 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.)
Example
(In this example, port number is hardcoded into html just for demonstration purposes. In reality, use your reverse proxy to route requests to different ports.)
# nim r --d:threadsafe thisexample import cgi, guildenstern/[dispatcher, httpserver] proc handleGet() = let html = """ <!doctype html><title>GuildenStern Example</title><body> <form action="http://localhost:5051" method="post"> <input name="say" id="say" value="Hi"><button>Send""" reply(Http200, html) proc handlePost() = try: echo readData(getBody()).getOrDefault("say") reply(Http303, ["location: " & http.headers.getOrDefault("origin")]) except: reply(Http500) let getServer = newHttpServer(handleGet) let postServer = newHttpServer(handlePost) getServer.start(5050) postServer.start(5051) echo "getServer serving at localhost:5050" joinThreads(getServer.thread, postServer.thread)
Types
GuildenServer {.inheritable.} = ref object loglevel*: LogLevel port*: uint16 thread*: Thread[ptr GuildenServer] logCallback*: LogCallback onCloseSocketCallback*: OnCloseSocketCallback
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*: posix.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.
Vars
shuttingdown = false
- Global variable that all code is expected to observe and abide to.
socketcontext {.threadvar.}: SocketContext
Consts
GuildenSternVersion = "6.1.0"
Procs
proc closeOtherSocket(server: GuildenServer; socket: posix.SocketHandle; cause: SocketCloseCause = CloseCalled; msg: string = "") {. ...gcsafe, nimcall, ...raises: [], tags: [], 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: [], forbids: [].}
- Call this to close a socket yourself.
proc shutdown() {....raises: [], tags: [], forbids: [].}
- Sets shuttingdown to true and signals dispatcher loops to cease operation.
proc suspend(sleepmillisecs: int) {.inline, ...raises: [], tags: [], 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 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.