How to process get 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.
  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. (See Tutorial on POST requests in AllegroServe.)

This tutorial shows how to process a simple form submitted via the get 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 get tutorial.

Generating a form using the get method and processing get requests

The form we intend to create is a simple one that will ask the user to enter their name. When the form is submitted, it will then tell them what their name is. A link on the result page will give them the opportunity to do it again.

The call to publish in the form below defines an entity that will dynamically generate the form we want, as well as produce the result when the form is submitted.

(publish :path "/queryform"
    :content-type "text/html"
    :function
    #'(lambda (req ent)
       (let ((name (request-query-value "name" req)))
         (with-http-response (req ent)
           (with-http-body (req ent)
             (if* name
               then ; form was filled out, just say name
                    (html (:html
                            (:head (:title "Hi to " (:princ-safe name)))
                            (:body "Your name is "
                                   (:b (:princ-safe name))
				   (:p ((:a :href "/queryform")
					"Click here to try again"))
				   )
			    ))
               else ; put up the form
                    (html (:html
                            (:head (:title "Tell me your name"))
                            (:body
                              ((:form :action "queryform")
                                "Your name is "
                                ((:input :type "text"
                                         :name "name"
                                         :maxlength "20"))))))))))))

By default, forms are submitted using the get method. If we were to explicitly state that the form uses the get method, we would have written

   ((:form :action "queryform" :method "get") ...rest of form...)

where we have

   ((:form :action "queryform")  ...rest of form...)

Once this is defined, we can view the form we've created at

   http://localhost:8000/queryform

Type in a name and press "ENTER" to see the result. If you look at the URI in your web browser, you will see something like the following:

   http://localhost:8000/queryform?name=Bruce

The form data, referred to as the query-string, is appended to the URI separated by a question-mark. Multiple field=value pairs are separated in the query-string by an ampersand: (that is, a &, see the note below). The function request-query called on the request object will return the query-string converted to an alist. the car of each entry will be a string naming one of the input fields in the submitted forms (e.g. "name"). The cdr will be the value submitted in that field (in this case, "Bruce"). The AllegroServe function request-query-value automatically fetches the query using request-query and searches the alist on key using assoc. The cdr of the result of this search is returned. By default, request-query will search for the urlencoded query-string for both a get and post form submission.

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.

Our entity handler above checks the request-query alist for a value in the name slot. If found, then the result your name is ... page is produced. If not found, then the form is generated to request a name.

One could bypass the form page by manually generating a query-string in the URI.

   http://localhost:8000/queryform?name=Bruce%20Lee

We discuss this in more detail in the next section.

Manually generating URI's for get requests

It is sometimes necessary to dynamically generate a URI with a query-string attached.

AllegroServe exports the two functions for this purpose: net.aserve:form-urlencoded-to-query and net.aserve:query-to-form-urlencoded.

The former accepts a query-string like "name=bruce" and will return an alist containing the same information.

The latter accepts an alist, such as '(("name" . "bruce")) and will return the equivalent query-string, "name=bruce", which could then be appended to a URI when generating a link.

Both functions accept an :external-format keyword argument in order to correctly parse/create the string with the correct character set.

The below example publishes a new entity at the URI http://localhost:8000/queryform2 that remembers the query-strings from each visit and will display them on subsequent submissions of the form. *past-names* is used to store the alist of each query, and query-to-form-urlencoded is used to convert them to query-strings before appending them to the URI to generate a link.

(let ((*past-names* nil))
  (publish :path "/queryform2"
	   :content-type "text/html"
	   :function
	   #'(lambda (req ent)
	       (let ((name (request-query-value "name" req :post nil)))
		 (with-http-response (req ent)
		   (with-http-body (req ent)
		     (if* name
			then		; form was filled out, just say name
			     (html (:html
			       (:head (:title "Hi to " (:princ-safe name)))
			       (:body "Your name is "
				      (:b (:princ-safe name))
				      (:p ((:a :href "queryform2")
					   "Click here to try again"))
				      (:p "Other people that have visited this site"
					  :br
					  (dolist (p *past-names*)
					    (html 
					     ((:a :href 
						  (format nil "queryform2?~a"
							  (query-to-form-urlencoded p)))
					      (:princ-safe 
					       (cdr (assoc "name" p
							   :test #'string-equal))) :br)))
					  ))
			       ))
			     (push (request-query req) *past-names*)
			else		; put up the form
			(html (:html
				    (:head (:title "Tell me your name"))
				    (:body
				     ((:form :action "queryform2")
				      "Your name is "
				      ((:input :type "text"
					       :name "name"
					       :maxlength "20"))))))))))))
  )

To see how it works, visit http://localhost:8000/queryform2 and enter a name. Then click the try again link and enter a new name. The list will grow each time you do this. You can mouse over a link to see the URI generated.

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.