aboutsummaryrefslogtreecommitdiff

Server Algorithms

As a convenience for coding TCP/IP network servers some algorithms are provided.

All these server templates work by first creating an object, then configuring it via various setters, most importantly setting the event handler for new connections and finally starting the event loop. So far, all event loops are based on the epoll(7) facility.

net .alg .epollServer

net .alg .epollServer "+" via
 { 8000 } +port
 { ":" via
   # : provides methods to communicate with the new connection
   # :read, :write, :close, :ctl (as in EPOLL_CTL)

   "Welcome to server\n" :write

   <
     { 4096 :read _ dump "" eq { :close } rep } =*in
     { "Did't :ctl for outgoing data" die } =*out
     { "Error" die } =*err
   >
 } +accept
 +run

net .alg .epollServer creates a new server, on which the following options can be set, and accept and port must be set before starting the server.

port: Takes a function which returns the port number to bind.

accept: Takes a function which takes a scope containing capabilities to communicate over the newly accepted connection and returns a scope which provides functions to call on incoming data, outgoing kernel buffer space and errors in the connection.

The capabilities of the connection are read which takes the maximal number of bytes to read and returns a string of the actually read bytes or the empty string if the connection ended. The member write takes a string and writes it to the connection - returning the number of bytes written. close immediately closes the connection and ctl takes a new set of flags bor'ed together from sys .linux .epoll .EPOLLIN, .EPOLLOUT, and .EPOLLERR.

interval: Takes a function which provides the number of microseconds to sleep during the next epoll_wait(2) syscall.

net .alg .epollServer "+" via { 1000 } +interval
# server will abort epoll_wait after 1ms

reuseAddr: Takes a function which takes a socket file descriptor and performs whatever shenanigans it deems appropriate before the socket is passed to bind(2). The default for this option applies SO_REUSEADDR, hence the separate option, if you whish to disable it set reuseAddr to { -- }, i.e. just discard the socket descriptor.

net .alg .epollServer "+" via { -- } +reuseAddr
# server will not set SO_REUSEADDR

FIXME: There should be a general option setting option as well (and only the default-active ones should be available for disabling)

net .alg .bufferedEpollServer (what you actually want to use)

Just as net .alg .epollServer and actually based on it, but provides buffering for all in- and output so you don't have to track the number of bytes read and written and can instead handle stuff on a higher level. To this end, accept works slightly different.

The capabilities write, read, close are provided as in net .alg .epollServer, where close still immediately closes the connection, whereas the new capability finish will first write the remaining output buffer bytes before actually closing the connection.

The members of the accept returned scope are in, err, and end. The in handler gets the current input buffer and is expected to return whatever can not yet be handled (i.e. pars whatever prefix you like and return the rest), err is called when an error occurs on the connection and end is called when the remote end closes the connection.

net .alg .bufferedEpollServer "+" via
 { 8000 } +port
 { ":" via
   # available methods are :read, :write, :close, :finish

   "Welcome to server\n" :write

   <
     { ==s
       { s len 4 ge } { "4 chars by you: " 4 s str .prefix cat "\n" cat :write 4 s str .postfix =s } loop
       s
     } =*in
     { "Bye..." :write :finish } =*end
     { "Error" die } =*err
   >
 } +accept
 +run

FIXME: Fix the bug which produces two "Bye..." messages.

net .alg .bufferedEpollServer takes the same options as net .alg .epollServer and additionally provides outputBufferLimit which takes a function which returns the maximum number of output buffer bytes you are willing to keep around for slow reading clients.

net .alg .bufferedEpollServer "+" via { 16 } +outputBufferLimit
# server will raise an error if output buffer grows above 16 bytes

net .alg .httpServer

This is not yet a full featured HTTP server, but works a little bit. It is based on net .alg .bufferedEpollServer and takes all options of the former. Additionally inputBufferLimit also exists, which puts a limit on the total request size (including request body).

You are not supposed to provide an accept, but instead call request to provide a function which handles the requests.

net .alg .httpServer "+" via
  { 8080 } +port
  { ":" via
    # available methods are :ok, :fail, :close, :write, :finish
    # additional members are :headers, :method, :url, :args
    :headers keys dump
    :method dump
    :url dump
    :args keys dump
    :body dump
    "<html><body>O hi!</body></html>" "text/html" :ok
  } +request
  +run