Abstract
TBNL is a toolkit for building dynamic websites with Common Lisp. It can sit behind front-ends like Hunchentoot, Araneida (two web servers written in Common Lisp), or the popular Apache combined with Marc Battyani's mod_lisp. It can also pretend to be a HTTP server itself.TBNL provides facilities like automatic session handling (with and without cookies), logging (to Apache's log files or to a file in the file system), customizable error handling, and easy access to GET and POST parameters sent by the client. It does not include functionality to programmatically generate HTML output. For this task you can use any library you like, e.g. (shameless self-plug) CL-WHO or HTML-TEMPLATE.
TBNL talks with its front-end or with the client over a socket and uses Kevin Rosenberg's KMRCL library for this task. As sockets aren't part of the ANSI Standard TBNL isn't portable to all Common Lisp implementations but it should work with all Lisps KMRCL has been ported to (including at least CMUCL, SBCL, Allegro Common Lisp, and LispWorks). TBNL also needs an implementation that offers multi-processing facilities.
It comes with a BSD-style license so you can basically do with it whatever you want.
TBNL is used by CafeSpot, PK Bass & Tackle, ERGO, and Heike Stephan.
Download shortcut: http://weitz.de/files/tbnl.tar.gz.
*request*
host
request-method
request-uri
script-name
query-string
get-parameter
get-parameters
post-parameter
post-parameters
raw-post-data
parameter
header-in
headers-in
authorization
remote-addr
remote-port
real-remote-addr
server-addr
server-port
server-protocol
mod-lisp-id
ssl-session-id
user-agent
referer
cookie-in
cookies-in
aux-request-value
delete-aux-request-value
recompute-request-parameters
*reply*
header-out
headers-out
cookie-out
cookies-out
return-code
content-type
content-length
send-headers
+http-continue+
+http-switching-protocols+
+http-ok+
+http-created+
+http-accepted+
+http-non-authoritative-information+
+http-no-content+
+http-reset-content+
+http-partial-content+
+http-multiple-choices+
+http-moved-permanently+
+http-moved-temporarily+
+http-see-other+
+http-not-modified+
+http-use-proxy+
+http-temporary-redirect+
+http-bad-request+
+http-authorization-required+
+http-payment-required+
+http-forbidden+
+http-not-found+
+http-method-not-allowed+
+http-not-acceptable+
+http-proxy-authentication-required+
+http-request-time-out+
+http-conflict+
+http-gone+
+http-length-required+
+http-precondition-failed+
+http-request-entity-too-large+
+http-request-uri-too-large+
+http-unsupported-media-type+
+http-requested-range-not-satisfiable+
+http-expectation-failed+
+http-internal-server-error+
+http-not-implemented+
+http-bad-gateway+
+http-service-unavailable+
+http-gateway-time-out+
+http-version-not-supported+
*default-content-type*
*session*
start-session
session-value
delete-session-value
reset-sessions
session-string
session-counter
session-max-time
session-remote-addr
session-user-agent
*use-remote-addr-for-sessions*
*use-user-agent-for-sessions*
*rewrite-for-session-urls*
*content-types-for-url-rewrite*
*session-cookie-name*
*session-removal-hook*
*session-max-time*
do-sessions
*TBNL-PORT*
) waiting for
requests to come in. The usual setup is to use a "real" HTTP server
like Apache (combined with mod_lisp) or Araneida as TBNL's "front-end"
and let it handle the requests coming in from the clients (i.e. from
browsers). The front-end will hand over these requests to TBNL and
TBNL will then send back its reply to the front-end which in turn
forwards it to the client, so the actual HTTP communication is handled
by the front-end. It is possible to leave out the front-end and let
clients connect directly to TBNL but this is not recommend for
production sites, only for demos or small in-house applications.
TBNL is not a full-blown HTTP server. (This implies that you should usually protect the TBNL port from the outside world by a firewall!)
If Apache is your front-end, Apache and your Lisp (with TBNL) will actually act as two separate servers which are mostly independent. (They can in fact reside on different physical machines and one TBNL instance can serve multiple Apaches - think load balancing - or one Apache can talk to multiple Lisp images.) This also means that Apache can be restarted while TBNL keeps running or vice versa.
TBNL will wrap all relevant information about the incoming request in
a REQUEST
object and then, depending on its dispatch table, call a function which is
responsible to serve the request. This will be a function with no
arguments which can access the request object via the special
variable *REQUEST*
. This
function will generally return the body of the reply as a string but
it can also manipulate the http headers which will be sent to the
client
Note that due to this control flow there's an upper bound (depending
on your Lisp implementation - see ARRAY-TOTAL-SIZE-LIMIT
)
for the size of the replies which can be served by TBNL. This is a
deliberate design decision to facilitate error handling and other
things. See SEND-HEADERS
if you want to serve very large static files.
apxs -c -i -a mod_lisp.cas root (and afterwards restart Apache).
The newest version of mod_lisp is available from http://www.fractalconcept.com:8000/public/open-source/mod_lisp/. For Apache 1.3.x you should use mod_lisp.c (version 2.42 or later), for Apache 2.x you should use mod_lisp2.c, which is a reimplementation of Marc's mod_lisp by Chris Hanson, (version 1.1 or later).
You can get pre-compiled modules for the Win32 version of Apache 2 from http://www.fractalconcept.com:8000/public/open-source/mod_lisp/windows/. Put the file into Apache's modules
folder and add the line
LoadModule lisp_module modules/mod_lisp2.soto your
httpd.conf
file.
httpd.conf
) add these lines
LispServer 127.0.0.1 3000 "tbnl" <Location /tbnl> SetHandler lisp-handler </Location>and afterwards restart Apache. This informs mod_lisp that there's a Lisp listening on port 3000 (see
*TBNL-PORT*
) and named
"tbnl" - you can of course use any other name or port or
even put TBNL on another physical machine. (In the latter case you'll
have to replace 127.0.0.1
with the FQDN or IP address of
this machine.)
The Location/SetHandler
part means that every URL which
starts with /tbnl
will be handled by mod_lisp (and thus
TBNL) on this server. (Again, you can of course use other locations. See the
Apache documentation for things like virtual hosts or
directives like LocationMatch
.)
It is beyond the scope of this document to explain how to install Araneida - see Araneida's documentation.
TBNL together with this documentation can be downloaded from http://weitz.de/files/tbnl.tar.gz. The current version is 0.9.10. You can (assuming you have ASDF available) install TBNL with
(asdf:oos 'asdf:load-op :tbnl)
There are some shortcuts available to make the above a bit less painful:
(asdf:oos 'asdf:load-op :tbnl-test) (tbnl:start-tbnl)This will start TBNL with a couple of test pages defined in the file
test/test.lisp
within the TBNL source directory.
http://localhost/tbnl/test
and you should see
something. (This last step assumes that, for the purpose of this test,
you have not changed the Location
line above and that Apache is listening on port 80.)
(tbnl-test:start-araneida-listener)and point your browser at
http://localhost:8000/tbnl/test
. See the file test/araneida-test.lisp
for other options like putting Araneida behind a HTTP proxy.
http://localhost:3000/tbnl/test
.
contrib
directory that comes with TBNL for
additions to TBNL and example code that uses TBNL. Also,
the test
directory is slightly misnamed and should
probably be called examples
, i.e. there you'll find some
"Hello World" code to get you started.
A tutorial for TBNL can be found at http://www.jalat.com/blogs/lisp?id=3 thanks to Asbjørn Bjørnstad.
For Win32, Bill Clementson explains how to set up TBNL with Apache/mod_lisp in his blog entry at http://bc.tech.coop/blog/041105.html.
There is, by the way, no public CVS repository for TBNL - the repository at common-lisp.net is out of date and not in sync with the (current) version distributed from weitz.de.
test/test.lisp
for an example.)
[Special variable]
*dispatch-table*
This is a list of function designators for dispatch functions each of which should be a function of one argument which accepts aREQUEST
object and, depending on this object, should either return a handler to handle the request orNIL
which means that the next dispatcher will be queried. A handler is a designator for a function with no arguments which usually returns a string to be sent to the client as the body of the http reply. (Note that if you use symbols as function designators you can redefine your handler functions without the need to change the dispatch functions.) See the section about replies for more about what handlers can do.The dispatchers in
*DISPATCH-TABLE*
are tried in turn until one of them returns a handler. If this doesn't happen, TBNL will return a 404 status code (Not Found) to the client.The default value of
*DISPATCH-TABLE*
is a list which just contains the symbolDEFAULT-DISPATCHER
.
[Function]
default-dispatcher request => handler
This is a function which will always unconditionally returns the value of*DEFAULT-HANDLER*
. It is intended to be the last element of*DISPATCH-TABLE*
[Special variable]
*default-handler*
This variable holds the handler which is always returned byDEFAULT-DISPATCHER
. The default value is a function which unconditonally shows a short TBNL info page.
[Function]
create-prefix-dispatcher prefix handler => dispatch-fn
A convenience function which will return a dispatcher that returnshandler
whenever the path part of the request URI starts with the stringprefix
.
[Function]
create-regex-dispatcher regex handler => dispatch-fn
A convenience function which will return a dispatcher that returnshandler
whenever the path part of the request URI matches the CL-PPCRE regular expressionregex
(which can be a string, an s-expression, or a scanner).
[Function]
handle-static-file path &optional content-type => nil
Sends the file denote by the pathname designatorpath
with content typecontent-type
to the client. Sets the necessary handlers. In particular the function employsHANDLE-IF-MODIFIED-SINCE
.If
content-type
isNIL
the function tries to determine the correct content type from the file's suffix or falls back to the value of*DEFAULT-CONTENT-TYPE*
.Note that this function calls
SEND-HEADERS
internally, so after you've called it the headers are sent and the return value of your handler is ignored.
[Function]
create-static-file-dispatcher-and-handler uri path &optional content-type => dispatch-fn
A convenience function which will return a dispatcher that dispatches to a handler which emits the file denoted by the pathname designatorpath
with content typecontent-type
if theSCRIPT-NAME
of the request matches the stringuri
. UsesHANDLE-STATIC-FILE
internally.If
content-type
isNIL
the function tries to determine the correct content type from the file's suffix or falls back to the value of*DEFAULT-CONTENT-TYPE*
.
Creates and returns a dispatch function which will dispatch to a handler function which emits the file relative tobase-path
that is denoted by the URI of the request relative touri-prefix
.uri-prefix
must be a string ending with a slash,base-path
must be a pathname designator for an existing directory. The content types of the files are determined via their suffix, falling back todefault-content-type
if the suffix is unknown.Uses
HANDLE-STATIC-FILE
internally.
[Generic function]
dispatch-request dispatch-table => result
This is a generic function so users can customize its behaviour. Look at the source code for details.
REQUEST
object
which is available to the handler via the
special variable *REQUEST*
. This object holds
all the information available about the request and can be queried
with the functions described in this chapter. Note that the internal
structure of REQUEST
objects should be considered opaque and may chance
in future releases of TBNL.
In all of the functions below the default value
for request
(which is either an optional or a
keyword argument) is the value of *REQUEST*
,
i.e. handlers will usually not need to provide this argument when
calling the function.
[Special variable]
*request*
Holds the currentREQUEST
object.
[Function]
host &optional request => string
Returns the value of the incomingHost
http header. (This corresponds to the environment variableHTTP_HOST
in CGI scripts.)
[Function]
request-method &key request as-keyword => keyword/string
Returns the request method. Ifas-keyword
is true (which is the default), then the result is returned as a keyword, otherwise as a string. (This corresponds to the environment variableREQUEST_METHOD
in CGI scripts.)
[Function]
request-uri &optional request => string
Returns the URI forrequest
. Note that this not the full URI but only the part behind the scheme and authority components, so that if the user has typedhttp://user:password@www.domain.com/xxx/frob.html?foo=bar
into his browser, this function will return"/xxx/frob.html?foo=bar"
. (This corresponds to the environment variableREQUEST_URI
in CGI scripts.)
[Function]
script-name &optional request => string
Returns the file name (or path) component of the URI forrequest
, i.e. the part of the string returned byREQUEST-URI
in front of the first question mark (if any). (This corresponds to the environment variableSCRIPT_NAME
in CGI scripts.)
[Function]
query-string &optional request => string
Returns the query component of the URI forrequest
, i.e. the part of the string returned byREQUEST-URI
behind the first question mark (if any). (This corresponds to the environment variableQUERY_STRING
in CGI scripts.) See alsoGET-PARAMETER
andGET-PARAMETERS
.
[Function]
get-parameter name &optional request => string
Returns the value of the GET parameter (as provided via the request URI) named by the stringname
as a string (orNIL
if there ain't no GET parameter with this name). Note that only the first value will be returned if the client provided more than one GET parameter with the namename
. See alsoGET-PARAMETERS
.
[Function]
get-parameters &optional request => alist
Returns an alist of all GET parameters (as provided via the request URI). The car of each element of this list is the parameter's name while the cdr is its value (as a string). The elements of this list are in the same order as they were within the request URI. See alsoGET-PARAMETER
.
[Function]
post-parameter name &optional request => string
Returns the value of the POST parameter (as provided in the request's body) named by the stringname
. Note that only the first value will be returned if the client provided more than one POST parameter with the namename
. This value will usually be a string (orNIL
if there ain't no POST parameter with this name). If, however, the browser sent a file through a multipart/form-data form, the value of this function is a three-element list(path file-name content-type)wherepath
is a pathname denoting the place were the uploaded file was stored,file-name
(a string) is the file name sent by the browser, andcontent-type
(also a string) is the content type sent by the browser. The file denoted bypath
will be deleted after the request has been handled - you have to move or copy it somewhere else if you want to keep it. See alsoPOST-PARAMETERS
and*TMP-DIRECTORY*
.File uploads don't seem to work with Araneida at the moment - use the Apache/mod_lisp front-end if you need them.
[Function]
post-parameters &optional request => alist
Returns an alist of all POST parameters (as provided via the request's body). The car of each element of this list is the parameter's name while the cdr is its value. The elements of this list are in the same order as they were within the request's body. See alsoPOST-PARAMETER
.
Note that external formats are usually implementation-dependent but you can use the predefined values
In all of the functions below the default value
for the optional argument
While TBNL's preferred way of sending data to the client is the one
described above (i.e. the handler returns the whole payload as a
string or an array of octets) you can, if you
really need to, get a stream you can write to directly. This is
achieved by first setting
up
Note that the
headers
See also
Note that setting this value explicitely doesn't mix well with URL rewriting.
If your handlers return the full body as a string or as an array of
octets you should not call this function. If a handler
calls
[Function]
raw-post-data &optional request => string
If the request is a POST request (which is not of type multipart/form-data
), returns the raw body of
the request, otherwise returns T
.
[Function]
parameter name &optional request => string
Returns the value of the GET or POST parameter named by the string name
as a string (or NIL
if there ain't no parameter with this name). If both a GET and a POST parameter with the name name
exist, the GET parameter will be returned. See also GET-PARAMETER
and POST-PARAMETER
.
[Function]
header-in name &optional request => string
Returns the incoming header named by the string name
as a string (or NIL
if there ain't no header with this name). Note that this queries the headers sent to TBNL by the front-end or by the client. This may not only include the incoming http headers but also some headers sent by mod_lisp or Araneida. name
is matched case-insensitively. See also HEADERS-IN
.
[Function]
headers-in &optional request => alist
Returns an alist of all incoming headers. The car of each element of this list is the headers's name while the cdr is its value (as a string). There's no guarantee about the order of this list. See also HEADER-IN
and the remark about incoming headers there.
[Function]
authorization &optional request => user, password
Returns as two values the user and password (if any) from the incoming Authorization
http header.
[Function]
remote-addr &optional request => string
Returns the IP address (as a string) of the client which sent the
request. (This corresponds to the environment
variable REMOTE_ADDR
in CGI scripts.) See also REAL-REMOTE-ADDR
. This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
remote-port &key request as-number => number/string
Returns the IP port of the client which sent the
request. If as-number
is true, which is the default, the result will be returned as a number, otherwise as a string.
(This corresponds to the environment
variable REMOTE_PORT
in CGI scripts.) This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
real-remote-addr &optional request => string
Returns the value of the incoming X_FORWARDED_FOR
http header if it
exists. Otherwise returns the value of REMOTE-ADDR
. This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
server-addr &optional request => string
Returns the IP address (as a string) where the request came in. Note
that this is not necessarily the IP address of the machine TBNL is
running on. (This corresponds to the environment
variable SERVER_ADDR
in CGI scripts.) This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
server-port &key request as-number => number/string
Returns the IP port (usually 80 for http and 443 for https) where the request came in. If as-number
is true, which is the default, the result will be returned as a number, otherwise as a string.
(This corresponds to the environment
variable SERVER_PORT
in CGI scripts.) This function doesn't return a meaningful value if you use TBNL without a front-end.
[Function]
server-protocol &key request as-keyword => keyword/string
Returns the version of the http protocol which is used by the client - this is usually either "HTTP/1.0"
or "HTTP/1.1"
. If as-keyword
is true, which is the default, the result will be returned as a keyword, otherwise as a string.
(This corresponds to the environment
variable SERVER_PROTOCOL
in CGI scripts.)
[Function]
mod-lisp-id &optional request => string
Returns the 'Server ID' sent by mod_lisp. This corresponds to the
third parameter in the "LispServer" directive in Apache's
configuration file and can be interesting if you deploy several different
Apaches or TBNL instances at once. This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
ssl-session-id &optional request => string
Returns the SSL session ID if it exists. Note that SSL sessions aren't related to TBNL sessions.
(This corresponds to
the environment variable SSL_SESSION_ID
in CGI
scripts.) This function doesn't return a meaningful value unless you use the mod_lisp front-end.
[Function]
user-agent &optional request => string
Returns the value of the incoming User-Agent
http header.
(This corresponds to
the environment variable HTTP_USER_AGENT
in CGI
scripts.)
[Function]
referer &optional request => string
Returns the value of the incoming Referer
(sic!) http header.
(This corresponds to
the environment variable HTTP_REFERER
in CGI
scripts.)
[Function]
cookie-in name &optional request => string
Returns the value of the incoming cookie named by the string name
(or NIL
if there ain't no cookie with this name). See also COOKIES-IN
.
[Function]
cookies-in &optional request => alist
Returns an alist of all incoming cookies. The car of each element of this list is the cookie's name while the cdr is the cookie's value. See also COOKIE-IN
.
This accessor can be used to associate arbitrary data with the the symbol symbol
in the REQUEST
object request
.
present-p
is true if such data was found,
otherwise NIL
.
[Function]
delete-aux-request-value symbol &optional request => |
Completely removes any data associated with the symbol symbol
from the REQUEST
object request
. Note that this is different from
using AUX-REQUEST-VALUE
to set the data to NIL
.
[Function]
recompute-request-parameters &key request external-format => |
Recomputes the GET and POST parameters and the
incoming cookies for the REQUEST
object
request
. This only makes sense if you've changed
the external format. The default value for
external-format
is *TBNL-DEFAULT-EXTERNAL-FORMAT*
.
+LATIN-1+
and +UTF-8+
.
See test/test.lisp
for an example.
Replies
It is the responsibility of a handler function to prepare the reply for the client. This is done by
For each request there's one REPLY
object which will be described in this section.
REPLY
object which is accessible
to the handler via the
special variable *REPLY*. This object holds
all the information available about the reply and can be accessed
with the functions described in this chapter. Note that the internal
structure of REPLY
objects should be considered opaque and may chance
in future releases of TBNL.
reply
is the value of *REPLY*,
i.e. handlers will usually not need to provide this argument when
calling the function.
*REPLY*
and then
calling SEND-HEADERS
. Note
that in this case the usual error handling is
disabled. See the file test/test.lisp
for an example.
[Special variable]
*reply*
Holds the current REPLY
object.
[Accessor]
header-out name &optional reply => string
(setf (header-out name &optional reply) new-value)
HEADER-OUT
returns the outgoing http header named by the
string name
if there is one,
otherwise NIL
. SETF
of HEADER-OUT
changes the current value of the header
named name
. If no header
named name
exists it is created. The association
between a header and its name is case-insensitive.
Set-Cookie
, Content-Length
,
and Content-Type
cannot be queried by HEADER-OUT
and must not be set by SETF
of HEADER-OUT
.
HEADERS-OUT
, CONTENT-TYPE
, CONTENT-LENGTH
, COOKIES-OUT
, and COOKIE-OUT
.
[Function]
headers-out &optional request => alist
Returns an alist of all outgoing http parameters (except for Set-Cookie
, Content-Length
,
and Content-Type
). The car of each element of this list is the headers's name while the cdr is its value. This alist should not be manipulated directly, use SETF
of HEADER-OUT
instead.
[Function]
cookie-out name &optional reply => cookie
Returns the outgoing cookie named by the string name
(or NIL
if there ain't no cookie with this name). See also COOKIES-OUT
and the section about cookies.
[Function]
cookies-out &optional reply => alist
Returns an alist of all outgoing cookies. The car of each element of this list is the cookie's name while the cdr is the cookie itself. See also COOKIE-OUT
and the section about cookies.
[Accessor]
return-code &optional reply => string
(setf (return-code &optional reply) new-value)
RETURN-CODE
returns the http return code of the
reply, SETF
of RETURN-CODE
changes it. The
return code of each REPLY
object is initially set to +HTTP-OK+
.
[Accessor]
content-type &optional reply => string
(setf (content-type &optional reply) new-value)
CONTENT-TYPE
returns the
outgoing Content-Type
http header. SETF
of CONTENT-TYPE
changes the current value of this header. The content type of each REPLY
object is initially set to the value of *DEFAULT-CONTENT-TYPE*
.
[Accessor]
content-length &optional reply => length
(setf (content-length &optional reply) new-value)
CONTENT-LENGTH
returns the outgoing
Content-Length
http header. SETF
of
CONTENT-LENGTH
changes the current value of this
header. The content length of each REPLY
object is
initially set to NIL
. If you leave it like that TBNL
will automatically try to compute the correct value using LENGTH
.
If you set the value yourself you must make sure that it's
the correct length of the body in octets (not in characters).
[Function]
send-headers => stream
Sends the initial status line and all headers as determined by
the REPLY
object *REPLY*
. Returns a stream to which the body of
the reply can be written. Once this function has been called
further changes to *REPLY*
don't have any effect. Also,
automatic handling of errors (i.e. sending the
corresponding status code to the browser, etc.) is turned off for this
request. Likewise, functions
like REDIRECT
or throwing
to TBNL-HANDLER-DONE
won't have the desired effect once the headers are sent.
SEND-HEADERS
its return
value is ignored.
[Constants]
+http-continue+
+http-switching-protocols+
+http-ok+
+http-created+
+http-accepted+
+http-non-authoritative-information+
+http-no-content+
+http-reset-content+
+http-partial-content+
+http-multiple-choices+
+http-moved-permanently+
+http-moved-temporarily+
+http-see-other+
+http-not-modified+
+http-use-proxy+
+http-temporary-redirect+
+http-bad-request+
+http-authorization-required+
+http-payment-required+
+http-forbidden+
+http-not-found+
+http-method-not-allowed+
+http-not-acceptable+
+http-proxy-authentication-required+
+http-request-time-out+
+http-conflict+
+http-gone+
+http-length-required+
+http-precondition-failed+
+http-request-entity-too-large+
+http-request-uri-too-large+
+http-unsupported-media-type+
+http-requested-range-not-satisfiable+
+http-expectation-failed+
+http-internal-server-error+
+http-not-implemented+
+http-bad-gateway+
+http-service-unavailable+
+http-gateway-time-out+
+http-version-not-supported+
The values of these constants are 100, 101, 200, 201, 202, 203, 204, 205, 206, 300, 301, 302, 303, 304, 305, 307, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 500, 501, 502, 503, 504, and 505. See RETURN-CODE
.
[Special variable]
*default-content-type*
The value of this variable is used to initialize the content type of each REPLY
object. Its initial value is "text/html; charset=iso-8859-1"
. See CONTENT-TYPE
.
Cookies
Outgoing cookies are stored in the request's REPLY
object (see COOKIE-OUT
and COOKIES-OUT
). They are CLOS objects defined like this:
(defclass cookie ()
((name :initarg :name
:reader cookie-name
:type string
:documentation "The name of the cookie - a string.")
(value :initarg :value
:accessor cookie-value
:initform ""
:documentation "The value of the cookie. Will be URL-encoded when sent to the browser.")
(expires :initarg :expires
:initform nil
:accessor cookie-expires
:documentation "The time (a universal time) when the cookie expires (or NIL).")
(path :initarg :path
:initform nil
:accessor cookie-path
:documentation "The path this cookie is valid for (or NIL).")
(domain :initarg :domain
:initform nil
:accessor cookie-domain
:documentation "The domain this cookie is valid for (or NIL).")
(secure :initarg :secure
:initform nil
:accessor cookie-secure
:documentation "A generalized boolean denoting whether this cookie is a secure cookie.")))
The reader COOKIE-NAME
and
the accessors
COOKIE-VALUE
, COOKIE-EXPIRES
, COOKIE-PATH
, COOKIE-DOMAIN
, and COOKIE-SECURE
are all exported
from the TBNL package.
[Function]
set-cookie name &key value expires path domain secure reply => cookie
Creates aCOOKIE
object from the parameters provided to this function and adds it to the outgoing cookies of theREPLY
objectreply
. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default forreply
is*REPLY*
. The default forvalue
is the empty string.
[Function]
set-cookie* cookie &optional reply => cookie
Adds theCOOKIE
objectcookie
to the outgoing cookies of theREPLY
objectreply
. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default forreply
is*REPLY*
.
START-SESSION
TBNL uses either
cookies or (if the client doesn't send the cookies back) rewrites URLs
to keep track of this client, i.e. to provide a kind of 'state' for
the stateless http protocol. The session associated with the client is
an opaque CLOS object which can be used to store arbitrary data
between requests.
TBNL makes some reasonable effort to prevent eavesdroppers from hijacking sessions (see below) but this should not be considered really secure. Don't store sensitive data into sessions and rely solely on the session mechanism as a safeguard against malicious users who want to get at this data!
For each request there's one SESSION
object which is accessible
to the handler via the
special variable *SESSION*
. This object holds
all the information available about the session and can be accessed
with the functions described in this chapter. Note that the internal
structure of SESSION
objects should be considered opaque and may chance
in future releases of TBNL.
Sessions are automatically verified for validity and age when
the REQUEST
object is instantiated, i.e. if *SESSION*
is not NIL
then this
session is valid (as far as TBNL is concerned) and not too old.
[Special variable]
*session*
Holds the currentSESSION
object (if any) orNIL
.
If
Note that this is not secure because it's obviously not very hard to
fake an
While this is intended to make the life of malicious users harder it
might affect legitimate users as well: I've seen the this http
header change with certain browsers when the Java plug-in was used.
Furthermore, all errors happening within a handler which are not
caught by the handler itself are handled by TBNL - see details below.
Note that for this function to be useful you should usually send
'Last-Modified' headers back to the client. See the code of
Another option you should try is to use an external log
file (as opposed to Apache's log) because it can reveal error
messages that might get lost if something's broken in the
communication between TBNL and mod_lisp.
Good luck... :)
It turned out that Jeff
Caldwell had worked on something similar so he emailed me and
proposed to join our efforts. As I had no immediate plans to release
my code (which was poorly organized, undocumented, and mostly
CMUCL-specific) I gave it to Jeff and he worked towards a release. He
added docstrings, refactored, added some stuff, and based it on KMRCL
to make it portable across several Lisp implementations.
Unfortunately, Jeff is at least as busy as I am so he didn't find the
time to finish a full release. But now (in spring 2004) I needed
a documented version of the code for a client of mine who also thought
it would be good if the toolkit were publicly available under an open
source license. So I took Jeff's code, refactored again (to sync with
the changes I had done in the meantime), and added documentation. This
resulted in TBNL as it is now. Jeff's code (which includes a lot more
stuff that I didn't use) is still available from his own website tbnl.org.
I have added and modified some stuff while preparing this release and
I probably introduced some bugs. So don't think TBNL will work for you
just because it has worked for me in the last years. Let me know if you have problems.
[Function]
start-session => session
Returns *SESSION*
if it
isn't NIL
, otherwise creates a new SESSION
object and returns it.
This accessor can be used to associate arbitrary data with the the symbol symbol
in the SESSION
object session
.
present-p
is true if such data was found,
otherwise NIL
. The default value
for session
is *SESSION*
.
SETF
of SESSION-VALUE
is called with session
being NIL
then a session is automatically instantiated with START-SESSION
.
[Function]
delete-session-value symbol &optional session => |
Completely removes any data associated with the symbol symbol
from the SESSION
object session
. Note that this is different from
using SESSION-VALUE
to set the data to NIL
.
The default value
for session
is *SESSION*
.
[Function]
reset-sessions => |
This function unconditionally invalidates and destroys all sessions immediately.
[Function]
session-string session => string
Returns a unique string that's associated with the SESSION
object session
. This string is sent to the browser as a
cookie value or as a GET parameter,
[Function]
session-counter session => count
Returns the number of times (requests) the SESSION
object session
has been used.
[Accessor]
session-max-time session => seconds
(setf (session-max-time session) seconds)
This gets or sets the maximum time (in seconds)
the SESSION
object session
should be
valid before it's invalidated: If a request associated with this
session comes in and the last request for the same session was more
than seconds
seconds ago
than the session is deleted and a new one is started for this client. The default value is determined by *SESSION-MAX-TIME*
.
[Function]
session-remote-addr session => address
Returns the 'real' remote address (see REAL-REMOTE-ADDR
) of the
client for which the SESSION
object session
was initiated.
[Function]
session-user-agent session => address
Returns the 'User-Agent' http header (see USER-AGENT
) of the
client for which the SESSION
object session
was initiated.
[Special variable]
*use-remote-addr-for-sessions*
If this value is true (the default is NIL
) then
the 'real' remote address (see REAL-REMOTE-ADDR
) of the
client will be encoded into the session identifier, i.e. if this value
changes on the client side the session will automatically be
invalidated.
X_FORWARDED_FOR
header. On the other hand,
relying on the remote address (see REMOTE-ADDR
) of the client isn't
an ideal solution either because some of your users may connect
through http proxies and the proxy they use may change during the
session. But then again, some proxies don't
send X_FORWARDED_FOR
headers anyway. Sigh...
[Special variable]
*use-user-agent-for-sessions*
If this value is true (which is the default)
then the 'User-Agent' http header (see USER-AGENT
) of the client will be
encoded into the session identifier, i.e. if this value changes on the
client side the session will automatically be invalidated.
[Special variable]
*rewrite-for-session-urls*
If this value is true (which is the default)
then content bodies sent by TBNL will be rewritten
(using URL-REWRITE) such
that GET parameters for session handling are appended to all relevant
URLs. This only happens, though, if the body's content type (see CONTENT-TYPE
) starts
with one of the strings in *CONTENT-TYPES-FOR-URL-REWRITE*
and unless the client has already sent a cookie named *SESSION-COOKIE-NAME*
.
[Special variable]
*content-types-for-url-rewrite*
This is a list of strings (the initial value is
("text/html" "application/xhtml+xml")
) the
content-type of an outgoing body is compared with if *REWRITE-FOR-SESSION-URLS*
is true. If the content-type starts with one of these strings then
url-rewriting will happen, otherwise it won't.
[Special variable]
*session-cookie-name*
This is the name that is used for the session-related cookie or GET
parameter sent to the client. Its default value
is "tbnl-session"
. Note that changing this name while
TBNL is running will invalidate existing sessions.
[Special variable]
*session-removal-hook*
The value of this variable should be a function of one argument, a SESSION
object. This function is called directly before the session is destroyed, either by RESET-SESSIONS
or when it's invalidated because it's too old.
[Special variable]
*session-max-time*
The default time (in seconds) after which a session times out - see SESSION-MAX-TIME
. This value is initially set to 1800.
[Macro]
do-sessions (var &optional result-form) statement* => result
Executes the statements with var
bound to each
existing SESSION
object consecutively. An implicit block
named NIL
surrounds the body of this macro. Returns the
values returned by result-form
unless
RETURN
is executed. The scope of the binding of
var
does not include
result-form
.
Logging and error handling
TBNL provides facilities for writing to Apache's error log
file (when using the mod_lisp front-end) or for logging to an arbitrary file in the file system. Note that due to the nature of mod_lisp Apache log mesages don't appear immediately but only after all data has been sent from TBNL to Apache/mod_lisp.
[Special variable]
*use-apache-log*
If this variable is true log messages are forwarded to Apache/mod_lisp. If it is NIL
log messages are written to the log file. The default value of this variable is NIL
if and only if :ARANEIDA
is an element of *FEATURES*
when building TBNL.
[Accessor]
log-file => pathname
(setf (log-file) pathspec)
The function LOG-FILE
returns a pathname designating the log file which is currently used (unless log messages are forwarded to Apache). This destination for log messages can be changed with (SETF LOG-FILE)
. The initial location of the log file is implementation-dependent.
[Generic function]
log-message log-level format &rest args => |
Schedules a message for the Apache log
file or writes it directly to the current log file depending on the value of *USE-APACHE-LOG*
. log-level
should be one of the
keywords :EMERG
, :ALERT
, :CRIT
, :ERROR
, :WARNING
, :NOTICE
, :INFO
,
or :DEBUG
which correspond to the various Apache log
levels. log-level
can also be NIL
in which case mod_lisp's default log level is used.
If Apache isn't used the log level is just written to the log file unless it's NIL
.
format
and args
are used as with
FORMAT
.
LOG-MESSAGE
is a generic function so you can specialize it or bypass it completely with an around method.
[Function]
log-message* format &rest args => |
Like LOG-MESSAGE
but with log-level
set to *DEFAULT-LOG-LEVEL*
.
[Special variable]
*default-log-level*
The log level used by LOG-MESSAGE*
. The initial value is NIL
.
[Special variable]
*log-lisp-errors-p*
Whether unhandled errors in handlers should be logged. See also *LISP-ERRORS-LOG-LEVEL*
. The default value is T
.
[Special variable]
*lisp-errors-log-level*
The log level used to log Lisp errors. See also *LOG-LISP-ERRORS-P*
. The default value is :ERROR
.
[Special variable]
*log-lisp-warnings-p*
Whether unhandled warnings in handlers should be logged. See also *LISP-WARNINGS-LOG-LEVEL*
. The default value is T
.
[Special variable]
*lisp-warnings-log-level*
The log level used to log Lisp warnings. See also *LOG-LISP-WARNINGS-P*
. The default value is :WARNING
.
[Special variable]
*log-lisp-backtraces-p*
Whether backtraces should also be logged in addition to error messages and warnings. This value will only have effect if *LOG-LISP-ERRORS-P*
or *LOG-LISP-WARNINGS-P*
is true. Note that logging of backtraces is currently only supported for CMUCL, SBCL, AllegroCL, and LispWorks. The default value is NIL
.
[Special variable]
*log-prefix*
All messages written to the Apache error log by TBNL are prepended by a string which is the value of this variable enclosed in square brackets. If the value is NIL
, however, no such prefix will be written. If the value is T
(which is the default) the prefix will be "[TBNL]"
.
[Special variable]
*show-lisp-errors-p*
Whether unhandled Lisp errors should be shown to the user. If this value is NIL
(which is the default) only the message An error has occurred will be shown.
[Special variable]
*show-lisp-backtraces-p*
Whether backtraces should also be shown to the user. This value will only have effect if *SHOW-LISP-ERRORS-P*
is true. Note that the display of backtraces is currently only supported for CMUCL, SBCL, AllegroCL, and LispWorks. The default value is NIL
.
[Special variable]
*show-access-log-messages*
If this variable is true and if *USE-APACHE-LOG*
is NIL
then for each request a line somewhat similar to what can be found in Apache's access log will be written to the log file. The default value of this variable is T
.
Miscellaneous
Various functions and variables which didn't fit into one of the other categories.
[Function]
start-tbnl => listener
This will start TBNL by creating a KMRCL listener which listens
on *TBNL-PORT*
. This
listener is bound to *LISTENER*
and returned. If *LISTENER*
is
already bound to a true value this value is simply returned
and a warning is issued.
[Function]
stop-tbnl => |
Stops the listener the variable *LISTENER*
is bound to. If this variable isn't bound to a true value a warning is issued.
[Special variable]
*listener*
This variable is bound to the KMRCL listener which is
currently used by TBNL (if TBNL active).
[Special variable]
*tbnl-port*
The port TBNL listens on. You have to configure
your front-end appropriately. The default value is 3000
.
[Symbol]
tbnl-handler-done
This is a catch
tag which names a catch
which is active during the lifetime of a handler. The handler can at any time throw
the outgoing content body (or NIL
) to this catch to immediately abort handling the request. See the source code of REDIRECT
for an example.
[Function]
no-cache => |
This function will set appropriate outgoing headers to completely prevent caching on virtually all browsers.
[Function]
handle-if-modified-since time => |
This function is designed to be used inside a handler. If the client has sent an
'If-Modified-Since' header (see RFC 2616,
section 14.25) and the time specified matches the universal time
time
then the header +HTTP-NOT-MODIFIED+
with
no content is immediately returned to the client.
CREATE-STATIC-FILE-DISPATCHER-AND-HANDLER
for an example.
[Function]
rfc-1123-date &optional time => string
This function accepts a universal time time
(default is the current time) and returns a string which encodes this time according to RFC 1123. This can be used to send a 'Last-Modified' header - see HANDLE-IF-MODIFIED-SINCE
.
[Function]
redirect script-name &key host protocol add-session-id permanently => |
Sends back appropriate headers to redirect the client
to the resource script-name
on the
host host
. If permanently
is true (the default is NIL
) a 301 status
code will be sent, otherwise a 302 status code. If host
is not provided the current host (see HOST
) will be
used. If protocol
is the
keyword :HTTPS
the client will be redirected to a https
URL, if it's :HTTP
it'll be sent to a http URL. If
both host
and protocol
aren't
provided then the value of protocol
will
match the current request.
[Function]
require-authorization &optional realm => |
Sends back appropriate headers to require basic http authentication (see RFC 2617) for the realm realm
. The default value vor realm
is "TBNL"
.
[Function]
escape-for-html string => escaped-string
Escapes all occurrences of the characters #\<
, #\>
, #\'
, #"
, and #\&
within string
for HTML output.
[Function]
url-encode string &optional external-format => url-encoded-string
URL-encodes a string using the external format external-format
. The default for external-format
is the value of *TBNL-DEFAULT-EXTERNAL-FORMAT*
.
[Function]
url-decode string &optional external-format => url-encoded-string
URL-decodes a string using the external format external-format
, i.e. this is the inverse of URL-ENCODE
.
It is assumed that you'll rarely need this function, if ever. But just in case here it is.
The default for external-format
is the value of *TBNL-DEFAULT-EXTERNAL-FORMAT*
.
[Function]
read-from-string* string => object, position
This is like READ-FROM-STRING
but *READ-EVAL*
is bound to NIL
and *PACKAGE*
is bound to the package named TBNL-DUMMY
which exists solely for this purpose.
[Function]
http-token-p object => generalized-boolean
This function tests
whether object
is a non-empty string which is
a token according to RFC 2068 (i.e. whether it may be used
for, say, cookie names).
[Special variable]
*tmp-directory*
This should be a pathname denoting a directory where temporary files can be stored. It is used for file uploads.
[Special variable]
*http-error-handler*
This variable holds NIL
(the default) or a function designator for a function of one argument. The function gets called if the responsible handler has set a return code other than +HTTP-OK+
or +HTTP-NOT-MODIFIED+
and receives this return code as its argument. It can return the contents of an
error page or NIL
if it refuses to handle the error, i.e. if TBNL's default error page should be shown.
(Note that the function can access the request and reply data.)
[Special variable]
*tbnl-default-external-format*
The external format used when computing the REQUEST
object. The default value is +LATIN-1+
.
Constants representing the corresponding external formats in some supported implementations. (As of version 0.8.0 these are LispWorks, AllegroCL, and SBCL with Unicode support.)
Debugging TBNL applications
If you want to debug your TBNL/mod_lisp applications it is recommend that you
start Apache (i.e. the httpd
binary) with the
-X
command-line option. Then set *DEBUG-MODE*
to a true
value and poke around in the listener.
[Special variable]
*debug-mode*
If the value of this variable is set to a true value (the default is NIL
) then on each request the global values of the special variables *REQUEST*
, *REPLY*
, *BODY*
, *COMMAND*
, *BACKTRACE*
, and *ERROR*
will be set accordingly, i.e. you'll have access to them after the request has been handled.
[Special variable]
*backtrace*
If *DEBUG-MODE*
is true
this variable holds the backtrace (as a string) of the last error or
warning that was handled by TBNL. Note that capturing of backtraces is
currently only supported for CMUCL, SBCL, and LispWorks.
[Special variable]
*body*
If *DEBUG-MODE*
is true
this variable holds the last content body that was sent by TBNL.
[Special variable]
*error*
If *DEBUG-MODE*
is true
this variable holds the last condition that was handled by TBNL.
[Special variable]
*command*
If *DEBUG-MODE*
is true
this variable holds the last input received from the front-end or the client. This is an alist of keys and values the details of which depend on the front-end.
Known quirks
Here are a couple of things to watch out for:
(UNSIGNED-BYTE 8)
. This is currently
implemented for AllegroCL 6.0 and higher and for
LispWorks 4.3 and higher. See the code for *TEST-IMAGE*
in test/test.lisp
for an example.
History
TBNL (which is short for "To Be Named Later") grew over the
years as a toolkit that I used for various commercial and private
projects. In August 2003 Daniel Barlow started a review of web
APIs on the lispweb
mailing list and I described the
API of my hitherto-unreleased bunch of code (and christened it "TBNL").
Acknowledgements
Thanks to Jeff Caldwell - TBNL would not have been released without
his efforts. Thanks to Marc
Battyani for mod_lisp and to Chris Hanson for
mod_lisp2. Thanks to Stefan Scholl for various additions and fixes, to Michael Weber for initial file upload code, and to Janis Dzerins for his RFC 2388 code. Thanks to Bob Hutchison for his code for multiple front-ends and the UTF-8 example.
$Header: /usr/local/cvsrep/tbnl/doc/index.html,v 1.123 2006/05/24 18:32:44 edi Exp $