|
Allegro CL version 7.0 Minimally revised from 6.2. |
This is the Webactions tutorial documentation as of the release of Allegro CL 7.0 (in October, 2004). It is provided for convenience but the Webactions documentation is regularly updated. We recommend that users use the latest version, available at http://opensource.franz.com/aserve/aserve-dist/webactions/doc/using-webactions.html. That version is always the most up-to-date. AllegroServe, including Webactions, is available from http://opensource.franz.com/. The Webactions manual is webactions.html. See also the AllegroServe documentation aserve/aserve.html.
The web sites of the past began as a way to display static data. Today's web sites, however are graphical user interfaces to services or collections of data. They are no longer simply web sites, but rather web applications. From banking tasks to airline reservations to news sites, consumers are accessing content-specific data via an internet browser.
Creating a web application is, in many ways, more difficult than creating a standalone application -- and it will become even more so as users demand increasingly customized features/services. This paper will discuss some of the key challenges web application developers face, and how Allegro Webactions solves these problems. Allegro Webactions sits on top of AllegroServe (Franz Inc.'s Lisp-based web server) and provides a framework for building web applications that are easy to maintain and update.
Customers are continuously demanding more, better, and faster services from web
applications. Unfortunately, many developers are still attempting to layer dynamic
features on top of a static structure. In order to successfully cater to user
demands, we must change the paradigm of how web sites are built and structured. What
worked for a 10 page web site two years ago, will not work for a 100 page site that
includes numerous databases and an e-commerce application. Some key challenges that
need to be addressed include the following:
The web server must track individual visitors as they navigate through the web site. The first time a visitor comes to the web site the server assigns that visitor a session object. Subsequent requests by the same visitor must be assigned the same session object so that the code processing the request does work for that specific visitor. For example a visitor entering a shopping site is assigned a virtual shopping cart. As the visitor adds items to the cart he expects to see previously added items still in the cart. Without a session object the visitor would find that his shopping cart was always empty which, needless to say, would destroy the concept of online shopping.
The http protocol on which the web is built is a connectionless protocol. A visitor does not connect to a web site and stay connected until moving to another web site. Thus there needs to be some means for each request to identify the visitor behind the request. Cookies were designed for this purpose. A web server can ask a web browser to store a value on the visitor's machine that will be returned to the web server on subsequent requests.
Cookies are a great tool for tracking sessions however some visitors disable cookies
due to privacy concerns. Therefore, a web application must be prepared to fall back
on other methods for tracking sessions.
Building a web application requires two types of talent: a web page designer and a programmer. Once the web site is up and running the daily maintenance can be done by the web page designer. If significant new functionality is needed the programmer may have to return to write new code.
A successful web application framework should allow
for the web site to be modified by either the designer or the programmer, depending on the
task. Requiring a programmer to be involved every time a product or text change
occurs is inefficient and expensive.
Web sites evolve constantly -- some even need to be updated hourly. And, if a
site is being accessed many times a second, the site can't go down while these
modifications are made. A site's framework must support changing the static content
and the dynamic content generation functions.
A web application usually consists of a large set of pages with various links between
them. When new features are added, new pages are also added, which cause links to
be added or modified. Plus, there are often several paths through the web site that
must yield equivalent results. For example, in a shopping site a visitor can just
start adding items to the shopping cart, and then identify himself at check-out.
Alternatively, a visitor can first identify himself and then start adding items to
his cart. In either case, the same products are ordered..
Many web sites are like unstructured programs with "goto's" all over the place.
As more products and features are added, the complexity can become overwhelming -- the
site becomes slower, more brittle, and less robust with each change. Something has
to be done to organize and simplify the process.
Suppose a web site is selling clothing. A user sees a nice sweater and
clicks on the button to add that sweater to his cart. The webserver gets the
request. How should it handle it?
One way would be to write a function that identifies the user sending the request, adds
the sweater to that user's shopping cart, and then writes out a page showing the current
contents of the cart. This type of monolithic
function approach works, but is not very flexible. Perhaps a change
needs to be made that requires an inventory check first, and then displays a different
message if the item is not in inventory. That would make the programmer have to
rewrite the monolithic function. Maybe the cart contents need to be displayed from
a link on every store page. Since the cart displayer code is in the handler function
we just described, the code must be duplicated or pulled out to be a separate routine.
In the monolithic function approach, the logic to choose what to display next
is intermixed with the code to manipulate the store database and to display store items.
This makes it very hard to figure out all the links through the web site.
A better way to design a web application is to separate out these three types of code:
Using the MVC paradigm when the user asks to add a sweater to his cart a Model function is called. The Model function manipulates the store's databases to add the sweater to the user's cart. The View function then displays the cart.
The paradigm described above, can easily be executed using Allegro WebActions and AllegroServe. Although there exist a number of web application frameworks, Webactions' simplicity, transparency and power distinguish it from other tools. Allegro WebActions uses Lisp as the web extension language. Lisp is not a simple scripting language (like Perl or PHP) or a byte interpreted strongly-typed language (like Java). Lisp is a flexible runtime-typed language that compiles down to machine code. As a result, code written in the Lisp extension language runs at machine speed with no interpreter overhead.
Webactions borrows ideas from the Struts
web application framework for Java. Struts has good ideas for mapping web
applications into the Model-View-Controller paradigm (more on this below).
Due to the more static design of the Java language, Struts is not as easy to use as
Webactions.
Unlike many other tools, which were cobbled together as web site requirements increased, Allegro WebActions was designed specifically to support complex web applications. It can easily support the key challenges outlined in this paper:
Webactions supports url rewriting in addition to Cookies. In url rewriting the links to other pages in the site are modified to include a session identifier. Webactions automatically does the check for disabled cookies and then switches to url rewriting, if necessary.
Webactions is also careful to create session identifiers that are unique and virtually impossible to guess. This prevents a malicious user from guessing a valid session identifier and hijacking another user's session.
Webactions provides a clear distinction between web design and programming, allowing web designers to work independently with their choice of tools. Webactions introduces a syntax of special tags that display dynamic content in static pages. This syntax was designed specifically so that existing "What you see is what you get" (WYSIWYG) html editors such as FrontPage and Mozilla will accept them. Webactions does not permit programming constructs to appear in html pages, so web designers won't see code they don't understand. This is not to say that Webactions forbids other scripting languages (such as javascript) from appearing in web pages. Webactions simply does not add its own set of programming constructs to html (as Java Server Pages do for example).
Webactions automatically notices when static pages are updated on the disk and begins serving them immediately. One can load in new definitions of dynamic functions and have them in use right away (this is a benefit of all Lisp programs).
Webactions adds a layer of abstraction which greatly simplifies the code. Web pages are denoted by symbolic names. Links from one web page to another are made using the symbolic name, as well. There is a single project description that lists all symbolic page names and describes how each is rendered. This project description allows a programmer to see a complete overview of the web site. Also, symbolic page names can be changed without having to edit any file that refers to it.
In MVC terms, the project description is input to the Controller. Items 2 and 3
are View components. The Model functions are item 4. Allegro Webactions itself
implements the Controller.
Let's examine a very simple Webactions web site. The project description is:
(webaction-project "simpleproject" :destination "site/" :index "home" :map '(("home" "pageone.clp") ("second" "pagetwo.clp")))
Let's skip right to the :map argument. Its value is a list of the pages that
make up this project. Each page is described by its symbolic page name followed by
an object which describes how the page is to be rendered. In this
example we have two pages, one named "home" and the other "second".
The render value for each of these pages is a string which in Webactions
means that each is rendered by serving a file on disk with the given name.
There are two files that accompany this project description:
site/pageone.clp:
<html> <body> This is page one. <br> Go to <a href="second">page two<a> </body> </html>
site/pagetwo.clp:
<html> <body> This is page two. <br> Go to <a href="home">page one<a> </body> </html>
You'll note that the clp files look just like html files. This is intentional. A clp file
is just an html file that is processed by Webactions before being sent to the browser. Two
types of processing are done by Webactions. First all symbolic page references
are replaced by page references that work in the current context. Symbolic page
references are found in two places: href="xxx" inside an <a> element and action="xxx"
inside a <form> element. Second if
there are references in the clp file to special clp Lisp functions, then those functions are called at the
appropriate time when the page is being sent to the browser.
In our example above each clp page has one symbolic page reference. Suppose we load
Webactions and this project definition into Lisp and start the server on port 8000.
We go to a web browser and ask for http://localhost:8000/.
What happens is the url in the web browser changes to http://localhost:8000/home
and we see:
This is page one.
Go to page two.
The reason the url changed to "/home" is that in the project definition we
specified "home" as the index page for this project. Thus accessing the
project without specifying a particular page resulted in the request being redirected to
the index page.
We click on the "page two" link and the browser now shows a url like:
http://localhost:8000/~159c546f07540c5a9ee4155d~/pagetwo.clp
and we see in the main frame of the web browser:
This is page two.
Go to page one.
Why does the url look so strange? The reason is that when the first page (the page named "home") was processed by Webactions and sent back to the browser, Webactions didn't know if the browser would accept a cookie. What Webactions did was send a cookie back with the page and at the same time it put the session identifier in the url for all symbolic page references. The number 159c546f07540c5a9ee4155d is the session id chosen by Webactions. When you clicked on page two this caused the web browser to send a request for that page to Webactions. That request either arrived at the web server with cookie value or it did not. If the request came with the cookie value then Webactions notes that for this session Webactions can depend on the cookie value being sent with each request and thus Webactions will not put the session id in any more urls. If the cookie value wasn't sent then Webactions notes that it will have to alter urls for this session and Webactions no longer tries to send cookies for this session.
If you run this simple example twice, once with your browser set to accept cookies and once with it set to not accept cookies you can see how Webactions adapts to your browser setting and still maintains session identity.
This simple two web page example doesn't do anything that couldn't have been done with
two static html pages. Even though this example doesn't make use of it,
Webactions is maintaining session information while the pages of this example are
accessed. We'll extend the example to make use of session tracking by counting the
number of times pages in this session were accessed.
We'll change the two clp files to add a line showing the hit count:
site/pageone.clp
<html> <body> This is page one. <br> Go to <a href="second">page two<a> <br> session hits: <sample_hitcount/> </body> </html>
site/pagetwo.clp
<html> <body> This is page two. <br> Go to <a href="home">page one<a> <br> session hits: <sample_hitcount/> </body> </html>
and then we define a clp function in this way and load it into the Lisp running
the web server:
(def-clp-function sample_hitcount (req ent args body)
(let ((session (websession-from-req req)))
(net.html.generator:html
(:princ
(setf (websession-variable session "hitcount")
(1+ (or (websession-variable session "hitcount") 0)))))))
Now we again go to a browser and view the pages. Now we see pages like this:
This is page one.
Go to page two.
session hits: 9
What is happening now is that when Webactions returns pageone.clp it sends all of the
page up to but not including <sample_hitcount/> to the browser, then it runs the sample_hitcount clp function, and then it sends the
contents of pageone.clp after <sample_hitcount/> to the browser.
The sample_hitcount function retrieves the
session object associated with this request (using websession-from-req)
and uses this session object to increment the session variable named "hitcount".
You're free to define as many session variables as you wish. If you access a
session variable that hasn't been set, the value nil
is returned. sample_hitcount increments the
"hitcount" variable for this session and then prints it to the html stream.
You may have noticed in the clp files that we used an xhtml syntax when we wrote html
code to invoke sample_hitcount:
<sample_hitcount/>
We could have written the equivalent
<sample_hitcount></sample_hitcount>
instead but the former is easier to type. While some html elements don't have a body (e.g. <img>) all clp elements we add to html have a body and the end of the body must be denoted in one of the two above ways.
The previous examples show the use of static clp pages and the introduction of dynamic
content via clp functions. There is one important part of Webactions left to
describe and that is Actions. When a web page is referenced symbolically it
can invoke a lisp function to perform some action. Usually that action affects the
data object behind the web site or the current session object. After the action is
performed an invisible redirect is done to another page on the web site which is then
handled either by another action or by displaying a web page.
Actions should never send anything to the web browser. The sole function of an
action is to affect the state of the Model behind the web site for this particular
session.
Our example for the use of actions is something found in most dynamic web sites these
days: the login page. In our sample web site we want to know the name of the
person visiting our site so we can personalize the page. The user enters the
web site at the main entry point and we check to see if he has logged on yet.
If so we go right to the home page of the site. If he hasn't then we ask him
to identify himself and once that's done we go to the home page of the site.
We begin with the project description and the two actions referenced:
(webaction-project "simpleproject" :destination "site/" :index "home" :map '(("home" action-check-login) ("login" "login.clp") ("gotlogin" action-got-login) ("realhome" "home.clp"))) (defun action-check-login (req ent) (let ((session (websession-from-req req))) (let ((user (websession-variable session "username"))) (if* user then ; already logged in ; just go to the real home "realhome" else ; must login "login")))) (defun action-got-login (req ent) (let ((username (request-query-value "username" req))) (if* (and username (> (length username) 0)) then (setf (websession-variable (websession-from-req req) "user") username) "realhome" else "login")))
We have four symbolic pages named in this project. Two of them refer to clp files
we'll show below. Two others refer to lisp functions that we call action functions. Users coming to the site
are redirected to the page with symbolic name "home". That causes action-check-login to be called. The action-check-login function checks to see if this
session has a non-nil value for the session variable "user". If so the
user has already logged in. Action functions never send anything back to the
browser. They simply return a string which is the symbolic name of the page in the
project that should be processed next. This action function returns either
"realhome" or "login". Both of those symbolic page names
refer to clp files in the project description.
site/login.clp
<html> <body> What is your name: <form action="gotlogin"> <input type="text" name="username"> <input type="submit"> </form> </body> </html>
site/home.clp
<html> <body> Welcome to my page, <clp_value name="user" session/>. </body> </html>
The login.clp file puts up a form that asks the visitor to enter his name. When
the submit button is pressed control goes to symbolic page name "gotlogin".
In our project, symbolic page name "gotlogin" is handled by action
function action-got-login shown above.
This action function reads the user name from the form and if it's non-empty
it stores the user name in the session variable named "user".
action-got-login returns either symbolic
page name "realhome" (if a valid user name was given) or "login" if a
name wasn't given and if the visitor must try again to identify himself.
The symbolic page "realhome" is connected to the file home.clp. This is a
very simple home page that simply welcomes the user by name. The clp_value function
is part of the built-in Webactions library. It retrieves and prints the value of a
variable. Here the variable name is "user" and the context is
"session".
The Allegro Webactions document is a reference manual for clp pages and
Webactions.
Allegro WebActions is a dynamic framework for building a web application.
Webactions does the work necessary to track sessions whether or not the browser
accepts cookies and allows the visual part of the website to be designed by html
programmers using tools they are accustomed to using. The dynamic part of the web
site is clearly partitioned from the static part so that the programming behind the web
site will not interfere with the visual part. Allegro WebActions makes it easier to
structure and update any web application in a cleaner, simpler way than with other current
web application building tools.