How to do redirects 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/.

This tutorial explains how to redirect an AllegroServe request to a different URI. It refers to portions of the HTTP 1.1 protocol as described in RFC2616. This tutorial assumes the user is familiar with form processing in AllegroServe. If you are not, you may find these additional AllegroServe tutorials to be of use: Tutorial on GET requests in AllegroServe and Tutorial on POST requests in AllegroServe.

An HTTP redirect describes a class of server responses (status code 3xx) that inform the client that some or all of the request should be accessed via a different URI, which is passed in the response to the client via the "Location" header.

NOTE: This tutorial is highly dependent on the extent to which client agents support the HTTP/1.1 protocol. It is expected that there should be similar behavior amongst modern HTTP/1.1 compliant browsers. In older browsers, your mileage may vary. The second example below, for example, makes use of the 303 status code. It is mentioned in the HTTP/1.1 spec that this code is not well supported in pre 1.1 client agents.

Below, we'll show how to send different response codes and modify response headers. First, let's get AllegroServe up and running. 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

We will create some content next, as part of our discussion of redirects.

Creating content and redirecting users

We create some content for a company StuffCo that has a webpage describing various types of "new stuff".

(defun new-stuff-entity-1 (req ent)
  (with-http-response (req ent)
    (with-http-body (req ent)
      (html (:html (:head (:title "StuffCo, the leader in new stuff!"))
		   (:body (:h2 "Important new info on new stuff 1")
			  (:p "Here's the latest update on our new stuff 1.")))))))

(defun new-stuff-entity-2 (req ent)
  (with-http-response (req ent)
    (with-http-body (req ent)
      (html (:html (:head (:title "StuffCo, the leader in new stuff!"))
		   (:body (:h2 "Great new info on new stuff 2")
			  (:p "Finally, new stuff 2 is here!")))))))

(publish :path "/new-1"
         :content-type "text/html"
	 :function #'new-stuff-entity-1)

(publish :path "/new-2"
	 :content-type "text/html"
	 :function #'new-stuff-entity-2)

The web designers at StuffCo want users to be able to visit http://localhost:8000/new and always see info on the latest and greatest new stuff. We'll set up a redirect to accomplish this.

(defparameter *new-stuff-rel-uri* "new-1")

(publish :path "/new"
	 :content-type "text/html"
	 :function 
	 #'(lambda (req ent)
	     (with-http-response (req ent :response *response-found*)
	       (setf (reply-header-slot-value req :location)
		     (format nil "http://localhost:8000/~a" *new-stuff-rel-uri*))
	       (with-http-body (req ent)
		 ;; empty body. must be called as this is what
		 ;; actually pushes response out to client.
		 ))))

Evaluate the above code in your lisp session and visit http://localhost:8000/new. You will be automatically redirected to http://localhost:8000/new-1. If you rebind *new-stuff-rel-uri* to "new-2" and revisit the page, you will see that AllegroServe now redirects you to http://localhost:8000/new-2.

The first thing of note in the function above is that we add ":response *response-found*" in the with-http-response wrapper. This tells AllegroServe how we want to respond to the incoming request. *response-found* is exported from the :net.aserve package and is a response struct containing the status code and a description of the code.

   DEMO> (describe *response-found*)
   # is a structure of type
   NET.ASERVE::RESPONSE.  It has these slots:
    NUMBER             302
    DESC               "Found"
   ; No value
   DEMO> 

Next, we must add the appropriate URI in the Location header. This is done by calling the setf of reply-header-slot-value.

AllegroServe creates a number of response structs for use by response handlers. The responses for redirects are bound to the variables:

Code

Description

Variable

300 Multiple Choices [none]
301 Moved Permanently net.aserve:*response-moved-permanently*
302 Found net.aserve:*response-found*
303 See Other net.aserve:*response-see-other*
304 Not Modified net.aserve:*response-not-modified*
305 Use Proxy [none]
306 [unused] [unused]
307 Temporary Redirect net.aserve:*response-temporary-redirect*

As another example, StuffCo web designers have set up a survey form that visitors of the site can fill out. Creation and processing of the submitted survey are handled by a single URL, but the response sent after the survey is submitted will be handled by another. In particular, a thank you page will be generated for each survey submitted, but a special thank you page with a StuffCo coupon will be returned for those that give the preferred answer.

(publish :path "/thanks"
	 :content-type "text/html"
	 :function
	 #'(lambda (req ent)
	     (with-http-response (req ent)
	       (with-http-body (req ent)
		 (html (:html (:head (:title "Thanks from StuffCo."))
			      (:body (:b "StuffCo ")
				     "Thanks you for your time in filling out our survey.")))))))

(publish :path "/bigthanks"
	 :content-type "text/html"
	 :function
	 #'(lambda (req ent)
	     (with-http-response (req ent)
	       (with-http-body (req ent)
		 (html
		  (:html (:head (:title "StuffCo. wants to give you more stuff!"))
			 (:body (:h3 "We like the way you fill out surveys")
				(:p "Please accept this complimentary 10% off coupon "
				    "for your next purchase of "
				    (:b "StuffCo") " stuff."))))))))

(publish :path "/survey"
	 :content-type "text/html"
	 :function
	 #'(lambda (req ent)
	     (let* ((answer (request-query-value "answer" req)))
	       (if* answer
		  then ;; process and generate response.
		       ;; this would be a good place to do something with the survey results.
		       (if* (string-equal answer "nice-stuff")
			  then ;; nice answer. give them a big thanks
			       (with-http-response (req ent :response *response-see-other*)
				 (setf (reply-header-slot-value req :location)
				   "http://localhost:8000/bigthanks")
				 (with-http-body (req ent) ))
			  else ;; else, just thank 'em.
			       (with-http-response (req ent :response *response-see-other*)
				 (setf (reply-header-slot-value req :location)
				   "http://localhost:8000/thanks")
				 (with-http-body (req ent) ))
			       )
		  else ;; generate survey form
		       (with-http-response (req ent)
			 (with-http-body (req ent)
			   (html
			    (:html
			     (:head (:title "StuffCo Survey"))
			     (:body
			      (:h3 "Please fill out the following survey")
			      ((:form :action "survey" :method "post")
			       "What do you think of our site?" :br
			       ((:select :name "answer")
				((:option :selected "1" :value "none")"Please choose one")
				((:option :value "good") "Good")
				((:option :value "bad") "Bad")
				((:option :value "nice-stuff") "Nice Stuff!"))
			       :br
			       ((:input :type "submit" :name "submit" :value "Submit"))
			       )))))) ))))

Visit http://localhost:8000/survey to fill out the survey. If you select "Nice Stuff!" you'll be rewarded. Note that this example uses the *response-see-other* response code (303) for the redirect code. This tells the client that the current URI is the correct place for the request, but it should proceed to the URI in the Location header for the rest of the response.

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.