documentation for lisp-network-server
======================================

Overview
--------

The lisp-network-server (or short: LNS) is a Common Lisp package that
provides the basic infrastructure to easily write network servers (for
TCP) in Common Lisp. You register your service handler for a specified
port and LNS does the work of connecting your code to the network by
starting a network listener for the TCP port you specified, listening
for incoming connections, accepting those connections and (if the
server isn't overloaded) starting your handler in a new thread with
the ready-to-use bidirectional stream. It even implements basic, if
somewhat brutal, error handling: if any unhandled condition pops up in
your handler code, the thread is terminated, the socket closed and the
event written to the log file.


Theory of operation
-------------------

LNS works on the principle of service handlers. You register a service
handler (a function) for a specific port. Doing so automagically
starts a network listener thread on that port. If a connection comes
in on that port, the network listener will accept it, and create a new
thread from the handler function you registered. Your function gets
handed three arguments:
 - stream: this is a two-way stream set up for both input and output,
    without any buffering, created from the network socket
 - peer-ip: the IP address of the connecting peer
 - peer-port: the port on the peer originating the connection
You function can then happily read/write from/to the stream. When you
are done, simply exit the handler function, the socket will be closed
and cleaned up automagically.

If this sounds somehow familiar: Yes, the LNS was inspired by the
principle of the Unix inetd (the Internet Superserver daemon).

To stop serving request on a port, you simply unregister that port
with the LNS. The associated network listener thread is terminated
automagically and the listening port is closed.

Usage
-----

The Common Lisp package of LNS is called lns. There are two key
functions you will need for using it:
 - register-service-handler and
 - unregister-service-handler

However, the first thing you have to do is call (lns:start-framework)
as this will do any necessary initialization, such as setting up and
opening log files.

If you decide to run your code as a daemon, enter daemon mode first
(by calling (lns:daemon)) and _then_ initialize the framework with
(lns:start-framework).

examples:
 - to register the service handler my-package:foobar for port 4242 on
   localhost (127.0.0.1):
   (lns:register-service-handler "127.0.0.1" 4242 #'my-package:foobar)
   The function will return t if registering the handler succeeded and
   NIL if not. If registering the handler failed, see the log file for
   details. Usually, you either have not enough permissions (such as
   trying to register a port < 1024 while not running as root) or the
   socket is (still/already) in use.
   Registering a service handler automagically starts an associated
   network listener thread for that port.
 - to unregister a service handler for port 4242 on localhost
   (lns:unregister-service-handler "127.0.0.1" 4242)
   This unregisters the service handler and automagically terminates
   the network listener thread for that port, thereby closing the
   listening socket.


Overload protection
-------------------

LNS features a very simple mechanism for overload protection. Every
time a connection is accepted, the number of currently running threads
is checked against a configured limit. If there are more threads
running than the configured limit, the connection is dropped and an
appropriate log entry ("too many threads, connection aborted") is
written to the log file. 

The default limit is 64 threads. To access this config item:
 - (lns:get-max-threads) returns the currently configured limit
 - (lns:set-max-threads 128) sets the current limit to 128

Logging
-------

By default, LNS logs all kinds of stuff into the file "lns.log" in the
current directory. To access this configuration setting:
 - (lns:get-log-file) returns the current log file path
 - (lns:set-log-file #P"/tmp/lns.log") sets the new log file path to
   "/tmp/lns.log", the log file path switch will be documented in both
   the old and the new log file
To use the LNS log facility for your own code:
 - (lns:write-log "stuff happend") will write the entry "stuff
   happened", prefixed with ISO8601 timestamp and current thread id,
   to the LNS log file

Character and binary I/O
------------------------

The stream your function gets handed is a bivalent stream. This means you can
freely mix character I/O (format, read-line, ...) and binary I/O (write-byte
and the like) in your code. Look at the time-server example.


Gotchas
-------

There are some things you need to keep in mind when using LNS:
 - LNS is multi-threaded, this means that at any one time, there can
   be an arbitrary number of instances of your code running in
   different threads. So if you have any shared data that _might_ be
   accessed concurrently read/write (as opposed to strictly read-only
   shared access), protect access to this data with mutexes or you
   will encounter strange and hard to find bugs. When doing so, try to
   minimize the mutex hold time to avoid this becoming a performance
   bottle-neck. 
 - When registering service handlers, you hand over a function
   object. When you load & compile a newer version of that same
   function, the old version LNS references will still remain valid
   and LNS won't use the new (and hopefully improved) version
   automagically. To achieve this, simply unregister the service and
   re-register with the new version of the handler function.
   Or you could work around this by registering as service handler
   a lambda function acting as proxy for your real service handler.
 - LNS installs very basic error handling for service handlers: It
   handles serious-condition and all of its children by closing the
   socket, writing a log entry (with the condition information) and
   terminating the thread. It is strongly recommended that you write
   and set up your own condition handlers.
 - socket errors caused by the client closing the socket or by the
   client disappearing off the network are handled by brute force:
   the stream is forcibly closed, discarding all pending I/O and the
   serving thread is terminated, a log entry is written.

Examples
--------

An incomplete implementation of simple TCP services (currently echo,
daytime and fortune) is supplied with LNS as an example. To start it:

  (load "lisp.lisp")
  (load "simple.lisp")
  (lns/simple-tcp:start-simple-tcp-servers)

If your fortune cookie files are _not_ living in the directory
/usr/share/games/fortunes, you will need to modify *fortune-path* in
simple-tcp.lisp to match your environment.


Portability
-----------

The portability of LNS is ... limited. It is written specifically for
a reasonably current version of Steel Bank Common Lisp (SBCL) - to be
precise, SBCL version 0.9.11 was used for initial development - with both
sb-bsd-sockets and sb-thread available. The later in turn requires (at
least at the time of this writing) either Linux 2.6 or Linux 2.4 with
appropriate kernel backports from 2.6 to enable the new threading
model.

It his, however, strongly recommended to use SBCL 1.0.6 or newer, because
starting with this version, SBCLs mutexes are now interrupt-safe.

Currently, work is underway to make SBCL run threaded on FreeBSD and
Solaris.

SBCL 0.9.2 as provided by Ubuntu Linux 5.10 "Breezy Badger" will _not_ 
work due to issues with the sb-thread package.



License
-------

The lisp-network-server (LNS) is available under the terms of the GNU
LGPL (Library GNU Public License) version 2.



Author
------

The lisp-network-server was written by Alexander Schreiber who can be
reached at als@thangorodrim.de. Ideas, suggestions and patches are
welcome ;-)


