Araneida is a fairly small free extensible HTTP server for SBCL and many other Common Lisp implementations. CLiki is an Araneida application.

Araneida has not been under active development for a long time. Seems like hunchentoot is the prime web development framework in recent years.

Salient features /

  • Free - BSD Licence, sans-advertising-clause, but for the modification that I'm not the Regents of the University of California
  • Works with or without multiple threads (depending on the lisp implementation). In single-threaded mode, hooks into the event loop so that you still have the top-level available while it listens for connections.
  • Designed to sit behind a caching proxy; can write appropriate bits of Apache config. If this bothers you, pretend that mod_proxy is a typo for mod_araneida. This includes playing nicely with SSL
  • Understands HTTP/1.1 Host: header for multiple host support
  • All content is dynamic and created by calling user-defined handlers (there is a handler in there already that outputs files, if you need it)
  • Pattern-matching tree rewrite functions and HTML-from-sexpr generation makes balancing tags suddenly trivially simple
  • Runs on multiple Lisps. As of version 0.9, these are SBCL, CMUCL, OpenMCL, ABCL, CLisp, Allegro Common Lisp, and LispWorks. MCL is supported in CVS. This makes Araneida the most portable Common Lisp web server.
Note: SBCL and CMUCL are confirmed to work as of araneida-0.9-a3. The others are believed to work, but are not currently in the test suite. Updates appreciated.

Alan Shields maintains a current Araneida linked here. Darcs repository and (some) older versions available for download.

It is not currently active. Latest patch to source tree is from january 2006. Seems that hunchentoot stole the show from araneida.

Discussion about Araneida takes place in the lispweb mailing list.

FWIW, to use SLIME to debug Araneida request handlers, I use :sigio
and the following method:

  (defmethod handle-request :around ((handler my-handler) request)
    (if *debug*
        (let ((*debugger-hook* 'swank:swank-debugger-hook))
          (handler-bind ((error #'invoke-debugger))

Because Araneida blocks any other serve-event handlers from running while it's handling a request, it doesn't mix well with :fd-handler if
you try to invoke the SLIME debugger.

(from Thomas F. Burdick on comp.lang.lisp, article id xcv3c0p6nhn.fsf@conquest.OCF.Berkeley.EDU)

Also see this paste

Araneida was originally designed to run behind a reverse-proxying Apache server, and still works best have also found that the reverse works well. Here is my pound.cfg:

ListenHTTP      *,3580
UrlGroup        "favicon"
UrlGroup        ".*"

This filters out the silly favicon.ico requests which are inevitably a 404. It's possibly by using the UrlGroup regexp syntax to redirect requests for static content to an Apache on the same host as well. Though I haven't tested it, Pound does HTTPS too.

I (Dmitri Hrapof) have just finished rewriting web interface for my dictionary server (Geiriadur) from Python to CL (I should say now it's two times smaller, two times faster and infinitely more manageable :-) As a result of this process I have two suggestions about Araneida + CLISP + Unicode.

First, remove the :external-format argument from listener-accept-stream in compat-clisp.lisp and make it look like:

(defun listener-accept-stream (listener)
  (socket:socket-accept (http-listener-socket listener)
                        :buffered nil
                        :element-type 'character))

The default format will be sufficient, and previously hard-coded iso-8859-1 doesn't permit use of UTF-8.

Second, make urlstring-unescape in url.lisp conditionally evaluated:

#-(or unicode sb-unicode allegro)
(defun urlstring-unescape (url-string)
  (do* ((n 0 (+ n 1))
        (out '()))
      ((not (< n (length url-string))) (coerce (reverse out) 'string ))
    (let ((c (elt url-string n)))
      (setf out 
            (cond ((eql c #\%)
                   (progn (setf n (+ 2 n))
                          (cons (code-char
				 (or (parse-integer
				      url-string :start (- n 1)
				      :end (+ n 1)
				      :junk-allowed t
				      :radix 16) 32))
                  ((eql c #\+)
                   (cons #\Space out))
                  (t (cons c out)))))))

(defun baityvstroku (baity)
  (ext:convert-string-from-bytes baity CUSTOM:*DEFAULT-FILE-ENCODING*))

(defun baityvstroku (baity)
  (sb-ext:octets-to-string (coerce baity '(vector (unsigned-byte 8)))))

(defun baityvstroku (baity)
   (coerce baity '(array (unsigned-byte 8) (*))) :external-format :utf-8))

#+(or unicode sb-unicode allegro)
(defun urlstring-unescape (url-string)
  (do* ((n 0 (+ n 1))
        (out nil) (len (length url-string)))
       ((not (< n len)) 
	(baityvstroku (apply #'vector (nreverse out))))
       (let ((c (elt url-string n)))
	 (cond ((eql c #\%)
		  (setf n (+ 2 n))
		  (if (< n len)
		      (push (or (parse-integer
				 url-string :start (- n 1)
				 :end (+ n 1)
				 :junk-allowed t
				 :radix 16) 32)
	       ((eql c #\+)
		(push (char-code #\Space) out))
	       (t (push (char-code c) out))))))

Again, this change will allow use of UTF-8 (or any other encoding) in get and post parameters.

This is my third attempt at getting this into the hands of someone who knows! :)

The Araneida request class has a body for POST requests. read-request-from-stream parses the HTTP body into an assoc list with the following line (line 27, daemon.lisp):

(parsed-body (if body (parse-body body '(#\&) len) nil))

This is perfectly sane behaviour for x-www-form-urlencoded POST bodies (e.g. first=value&second=value). However, for all other bodies it is destructive and nonsensical as (particularly XML, which uses = for attributes). Unfortunately, I'm working with those other bodies e.g. application/rdf+xml.

My initial hack was to put in a simple test on the Content-Type header:

(parsed-body (if body
                ;; Use member on the raw assoc list to make sure we catch requests
                ;; with multiple Content-Types.
			  (if (member "application/x-www-form-urlencoded"
				      (cdr (assoc :content-type headers)) :test #'string=)
			    (parse-body body '(#\&) len)

i.e. only parse the body if we have form-encoded data; otherwise just use the body string (one could also url-unencode the body string). This split has the unfortunate side effect for that for some requests (request-body request) will return an assoc list, and for others a string. I don't know what consequences this would have on existing code. (stringp) is one remedy; another would be to provide body-data and body-parsed (or similar names) slots on request, rather than just body. I would think that if Araneida-based applications are using the parsed data (and it's valid/correct) then their clients must be sending the correct headers (e.g. wget sends x-www-form-urlencoded by default), so putting this check in wouldn't break anything, but I might be wrong.

Thoughts, anyone?

Well, both the parsed and uparsed bodies seem to be passed in as part of your request. Access the one you need. Although it woulud seem quite possible to invoke different parsing methods via a quick lookup and of the content-type in the header - which has already been parsed at this stage.

I (Arnaud Diederen) have been using Araneida to set up a webservice that receives XML requests (based on specs). To parse the XML I'm using CXML, that accepts a byte stream.

Therefore, I hacked araneida a tad so it doesn't parse it's body content anymore but rather provides the byte stream instead of parsed data. I actually don't think it is be a bad idea to have an access to the raw stream. (Still, I built a set of tools to build 'helpers' on top of the request about (basically, the request's content-type defines what helper to build)).

.. but I keep wondering if I should go on using araneida to do that kind of work, or rather use another web server.

Arnaud Diederen, 12 July 2005

If you use Araneida with recent clisp (2.37), you may want to modify some behaviour to

  • Make it correctly bind the local interface
  • Accept request while working with REPL (using readline)
  • Allow setting of length of queue of incoming tcp connections (backlog of listen)
The proposed changes to functions defined in compat/compat.clisp are as follows:

(defun convert-address (address)
  "Convert IP address to string."
  (etypecase address
     (string address)
     (vector (format nil "~{~d.~^~}" (map 'list #'identity address)))))

(defparameter *default-backlog* 10)
(defun host-make-listener-socket (address port)
  (socket:socket-server port :backlog *default-backlog*
                             :interface (convert-address address)))

(defun host-serve-events-once (&optional block)
  "one-shot duplicate of host-serve-events. Returns integer."
  (aif (mapcar #'car *fd-handlers*)
       (loop for i in *fd-handlers*
             for j in (socket:socket-status it (if block nil 0))
             do (if j
                    (funcall (cdr i) (car i))))) 0)

;  Uncomment this to handle connections while in repl
; (setf readline:event-hook #'araneida::host-serve-events-once)

Few further notes:

  • you can use either "" and #(127 0 0 1) as :address parameter when creating listener; araneida only passes it verbatim. THe vector form is more compatible with other implementations.