(in-package :cs325-user) (use-package :net.aserve) (use-package :net.html.generator) ;;; Update history: ;;; ;;; 09-23-09 Fixed APROPOS-RESULTS to treat empty package as "COMMON-LISP" [CKR] ;;; 01-15-07 Added PATHNAME-HOST and PATHNAME-DEVICE to *BASE-DIR* [CKR] ;;; 01-26-05 Added :PRESERVE to FIX-NAME [CKR] ;;; 01-25-05 Added a lot of comments [CKR] ;;; 01-23-05 Added PUBLISH-DIRECTORY, made more use of static HTML and CSS [CKR] ;;; 01-22-05 Fixed Critic page to not run #. and show error messages [CKR] ;;; 01-22-05 Fixed Apropos page to handle missing package [CKR] ;;; 02-16-04 Replaced *cs325-root* with *load-pathname* [CKR] ;;; Some AllegroServe (AServe hereafter) examples, with extended ;;; comments. These comments are not a replacement for the AServe ;;; documentation. The code is intended to exemplify recommended ;;; practice for maintainable code. Server code can be quite ;;; tricky to debug because each request is handled in a separate ;;; thread, not in the listener. You want as much of your code as ;;; possible to be testable in a listener window. ;;; Publishing URL's ;;; ---------------- ;;; In Java servlets, you map URL's to programs in a web.xml file. ;;; In AServe, you link URL's to Lisp functions in a Lisp file, ;;; like this, using calls to "publish" functions. I recommend ;;; putting all the mappings up front, to make it easy to see ;;; what URL's are defined, ;;; First, we define what AServe calls "exact" paths. If ;;; a browser asks for a URL exactly matching one of the ;;; paths below, the associated code will be called. ;;; ;;; The simplest path, "/", will match a request for ;;; http://localhost:8000/ (if 8000 is the port used). ;;; Hence, this is a good path for a page describing ;;; the server itself. (publish :path "/" :content-type "text/html" :function 'show-root-page) ;;; The remaining URL's are for specific web applications ;;; that are described later in this file. These mappings ;;; say that, for example, the URL http://localhost:8000/apropos ;;; will cause the function SHOW-APROPOS to be called. (publish :path "/apropos" :content-type "text/html" :function 'show-apropos) (publish :path "/critic" :function 'critique-code) (publish :path "/display" :function 'show-fields) ;;; We also want to make available static content, ;;; such as HTML files, images, and stylesheets. In general, ;;; the more you can put in static content, the better, because ;;; such content can be edited and tested by non-Lisp programmers ;;; using HTML editors. ;;; ;;; Although you can publish files one by one, using PUBLISH-FILE, ;;; it's simpler to make a directory with the content, and publish ;;; the entire directory. ;;; ;;; Accompanying this file should be a subdirectory called contents/. ;;; To publish that directory, we don't want to hardwire the full ;;; path, because then every one using this file would need to modify ;;; it for their installation. ;;; Instead, we construct the pathname by adding "contents/" to the ;;; name of the directory containing this file. We store the result ;;; in the global *BASE-DIR* in case you want to see what it looks ;;; like in a listener window. (defparameter *base-dir* (namestring (merge-pathnames "content/" (make-pathname :device (pathname-device *load-truename*) :host (pathname-host *load-truename*) :directory (pathname-directory *load-truename*))))) ;;; Now we tell AServe that any URL starting with ;;; http://localhost:8000/base/... should retrieve files from ;;; the directory contents/. ;;; ;;; In AServe /base/ is called a "prefix" path. If, and only if, ;;; no "exact" path matches the incoming URL, then AServe checks ;;; the prefix paths. If more than one matches, it picks the longest. ;;; ;;; I deliberately used a prefix different from the directory ;;; name to demonstrate that the two are independent. ;;; Normally, however, you'd probably use the same names. ;;; ;;; CAUTIONS: Be sure that ;;; - the prefix begins and ends with / ;;; - the destination is a directory ending with /, e.g., "webapps/" ;;; - the destination is a string, not a pathname structure (publish-directory :prefix "/base/" :destination *base-dir* :indexes '("index.html")) ;;; The :indexes keyword tells AServe to send back ;;; index.html from the destination directory if the URL ;;; doesn't specify a file, e.g., the URL is just ;;; http://localhost:8000/base/. The default value for ;;; this keyword is the list '("index.html" "index.htm). ;;; ;;; Typically, index pages are used as home pages for a web ;;; site. In our case, index.html has links to the ;;; demo applications. ;;; ;;; NOTE: portableaserve with LispWorks 4.2 Personal Edition ;;; for Windows does not send index.html on my machine. ;;; However the URL http://localhost:8000/base/index.html ;;; works fine. ;;; Defining Lisp URL handlers ;;; --------------------------- ;;; Generating a Server Root Page in Lisp ;;; ------------------------------------- ;;; ;;; As noted above, the URL http://localhost:8000/ ;;; will call the Lisp function SHOW-ROOT-PAGE. ;;; ;;; It's often useful to have a page for the server itself. ;;; Tomcat for example has a page about the Tomcat server and ;;; links to documentation that displays at the server root. ;;; ;;; Type http://localhost:8000/ into your browser. Then use ;;; your browser's "show page source" menu option to see ;;; what the browser sees. Note that there's no Lisp there, ;;; just regular HTML. ;;; All functions that handle HTTP requests take two parameters: ;;; - a request (as in Java), containing the form data and ;;; other input information, ;;; - a entity (similar to Java's response object), containing ;;; the stream to write HTML output to ;;; ;;; Such functions should start with with-http-response and ;;; with-http-body, as shown below. These macros do some basic ;;; HTTP processing, and set things up so that output from ;;; the HTML macro will be sent back to the client that requested ;;; the URL. (defvar *hit-counter* 0) (defun show-root-page (req ent) (with-http-response (req ent) (with-http-body (req ent) (make-root-page (header-slot-value req :host))))) ;;; I recommend that you put all the page-generating code in a ;;; separate function. Pass that function any form data it needs, ;;; not the request or entity. That way, you'll be able to test it in a ;;; listener window, like this: ;;; ;;; (html-stream *standard-output* (make-root-page "my-host")) ;;; ;;; This function uses the HTML macro to construct HTML text ;;; with a little bit about the server. Anything can be used ;;; to print, as long as the output goes to *HTML-STREAM*. But ;;; the HTML macro is easier to maintain in the long haul. (defun make-root-page (host) (html (:html (:head (:title "Welcome to Portable AllegroServe on " (:princ (lisp-implementation-type))) ((:link :rel "stylesheet" :type "text/css" :href "base/style.css")) ) (:body (:center ((:img :src "base/images/aservelogo.gif"))) ;;; Note the links to static content via base/ URL's. (:h1 "Welcome to Portable AllegroServe on " (:princ (lisp-implementation-type))) (:i "This server's host name is " (:princ-safe host)) #+unix (:i ", running on process " (:princ (net.aserve::getpid))) :br (:princ (incf *hit-counter*)) " hits so far in this run" ;;; Hit counters used to be popular, when being popular ;;; was popular on the web. A hit counter is a ;;; simple example of something that can only be done using ;;; some kind of dynamically generated HTML. )))) ;;; Static Form Example ;;; ------------------- ;;; ;;; Forms are the heart of web applications. We have a ;;; simple, and silly, static form in contents/fields.html, ;;; which can be viewed with the URL ;;; ;;; http://locahost:8000/base/fields.html ;;; ;;; It's meant to show some common form elements and how ;;; the data a user enters into a form can be sent to and ;;; processed by a Lisp function. ;;; ;;; The form has ;;; ;;; - a hidden input field called "email" ;;; - a text field called "name" ;;; - a pair of radio buttons name "agrees" ;;; - a checkbox named "spam" and a checkbox named "calls" ;;; - three input fields called "favorites" ;;; ;;; Radio buttons that go together always have the same name. ;;; Clicking one such button will uncheck all the others with ;;; the same name. But any input element can have the same name ;;; as another, and we do it here for demonstration purposes. ;;; ;;; Forms specify a URL to send to the server when the form is ;;; is submitted. In fields.html, the form is defined thus: ;;; ;;;