How to process post requests using AllegroServe

Go to the tutorial main page.

These other tutorials also deal with AllegroServe:

AllegroServe is the opensource webserver written by Franz Inc. You can download and read documentation on AllegroServe at http://opensource.franz.com/aserve/.

One of the main advantages to using AllegroServe is that it is possible to process forms directly in lisp rather than dispatching out to a shell or cgi script.

There are two methods by which a form can be submitted to a web server.

  1. get: With the HTTP get method, form data is appended to the URI specified by the action attribute and this new URI is sent to the server. Since the form data is appended to the URI, this method is more ideally suited to small bits of data. (See Tutorial on GET requests in AllegroServe.)
  2. post: With the HTTP post method, the form data is included in the body of the form and sent to the server. This method is more amenable to large data submissions, such as files or bulletin board messages.

This tutorial shows how to process a form submitted via the post method.

Follow the following three preliminary steps to get AllegroServe running and ready for the tutorial.

Preliminary step 1: Start allegro CL and load AllegroServe

Start Allegro CL in the usual way. Once you have a prompt, enter the following form to load AllegroServe (it is not an error to enter that form if AllegroServe happens to already be loaded):

   (require :aserve)

Preliminary step 2: Define a package to work in

We define a package in which to define the code for this tutorial. The AllegroServe functionality we will be using is exported from the :net.aserve package, so our package will :use it. Since we will also be generating html, we must also use the :net.html.generator package. This package is defined by the htmlgen module, which is loaded as part of AllegroServe. Evaluate the following forms to define our package, which we are calling :demo, and make it the current package:

(defpackage :demo (:use :excl :cl :net.aserve :net.html.generator))
(in-package :demo)

Preliminary step 3: Get a server running

Now let's get a server running. You will need to choose a port that is available and that the operating system will permit you to use. Web servers normally listen on port 80. On UNIX-like systems (macosx included) port 80 can only be allocated by the the superuser (called root). On windows any user can open port 80 as long as it's not yet allocated. In order to make this tutorial work on both Unix and Windows (and not require that you run as root on Unix), we'll put our web server on the localhost at port 8000. Evaluate the following form to start the server. (The function start is in the :net.aserve package. We do not need a package qualifier because we are in our :demo package which uses :net.aserve.

  (start :port 8000)

The webserver is now up and running, but we've provided it with no content to deliver to curious clients. If we visit http://localhost:8000/ right now, we'll get AllegroServe's standard "404 Error: Not found" response:

   Not Found
   The request for http://localhost:8000/ was not found on this server.
   
   AllegroServe 1.2.43

You instruct AllegroServe to make content available at a given URI by using one of the publish* routines. For this tutorial we want to dynamically generate a form, so we'll use publish in the code in the next section, which is the true start of the post tutorial.

Generating a form using the post method and processing post requests

Form data submitted using the post method can be encoded into the body of the request in two different ways. This choice of encoding is declared via the enctype attribute in the <form ...> element.

For this tutorial, we will create a page that submits form data in the body of the request via the application/x-www-form-urlencoded method.

The form we will create will request a body of text from the user. When submitted, it will reverse the words in the text and display it and the original. A link on the result page will provide the opportunity to repeat the process.

(publish :path "/reverse"
	 :content-type "text/html"
	 :function
	 #'(lambda (req ent)
	     (let ((text (request-query-value "text" req))
	           )
	       (with-http-response (req ent)
		 (with-http-body (req ent)
		   (if* text
		      then ;; display text and text reversed.
			   (html
			    (:head (:title "Reversi!"))
			    (:body (:p "You entered: " (:b (:princ-safe text)))
				   (:p "Reversed: " 
				       (:b (:princ-safe
					    (list-to-delimited-string
					     (nreverse (split-re "\\W+" text)) #\space))))
				   (:p ((:a :href "reverse") "Try again"))
				   ))
			   
		      else ;; display form for entering text
			   (html
			    (:head (:title "The great Reverse"))
			    (:body ((:form :action "reverse" :method "post"
					   :enctype "application/x-www-form-urlencoded")
				    "Enter text to be reversed" :br
				    ((:textarea :name "text" :rows "15" :cols "50")) :br
				    ((:input :type "submit" :name "submit" :value "Reverse it")))))
			   )
		   )))))

The :form element in entity handler above explicitly sets the :enctype to "application/x-www-form-urlencoded". This is not really necessary as this is the default encoding type for post methods, but it makes for more readable code as the reader does not need to recall the default.

You can view the form we've defined by visiting

   http://localhost:8000/reverse

Type in some text and hit the submit button to see the result. Notice that the URI does not change even though the output is different. If this were a get request, the URI would contain the query-data entered into the form. Depending on how much text you entered, this would make the URI very long.

The function net.aserve:get-request-body reads and returns the body of the request. For the encoding type used in this example, the result will be a urlencoded string of key=value pairs separated by an ampersand (that is, a &, see the note below) when there are more than one. The key will be a string that matches the :name of one of the input fields of the form. A more general way to retrieve and search the query-string is to use request-query-value, as we've done in the above example. This function by default checks both the URI for a query-string (as per the get method) and the request body (as per the default post method).

Note on use of an ampersand

Other transformations may be made on urlencoded data. Here, we just comment on the general format of the query-string. For more information see Section 2.2 URL Character Encoding Issues in RFC1738.

The function net.aserve:form-urlencoded-to-query converts this urlencoded string to an alist containing the same information, but in a way easy to search through in Lisp. For example, given a possible query-string from the current example:

   "text=a+man+a+plan+a+canal+panama"

   (net.aserve:form-urlencoded-to-query "text=a+man+a+plan+a+canal+panama")
will return '(("text" . "a man a plan a canal panama"))

This completes the tutorial. Additional AllegroServe tutorials are listed at the top of this page.

The AllegroServe documentation is available in doc/aserve/aserve.html. The HTML generator is described in doc/aserve/htmlgen.html.

Allegro Webactions is a framework on top of AllegroServe for developing entire web sites using the Model-View-Controller paradigm. Among other things, this paradigm separates web page design tasks from dynamic content programming tasks. It is described in doc/using-webactions.html and doc/webactions.html.

Go to the tutorial main page.