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 (except on LispWorks where it doesn't need KMRCL but uses Hunchentoot instead). As sockets aren't part of the ANSI Standard TBNL isn't portable to all Common Lisp implementations but it should work with LispWorks and all Lisps KMRCL has been ported to - including at least CMUCL, SBCL (but see below), and Allegro Common Lisp. 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, Cleartrip, PK Bass & Tackle, ERGO, and the Cork Multitext Project.
Note: TBNL is deprecated and has been replaced by Hunchentoot.
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
*file-upload-hook*
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
*use-apache-log*
log-file
log-message
log-message*
*default-log-level*
*log-lisp-errors-p*
*lisp-errors-log-level*
*log-lisp-warnings-p*
*lisp-warnings-log-level*
*log-lisp-backtraces-p*
*log-prefix*
*show-lisp-errors-p*
*show-lisp-backtraces-p*
*show-access-log-messages*
*http-error-handler*
get-backtrace
*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.11.4. You can (assuming you have ASDF available) install TBNL with
(asdf:oos 'asdf:load-op #+:lispworks :hunchentoot #:-lispworks :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.
Note: The TBNL/Araneida combination should be considered experimental. I'm not aware of anyone who uses it in earnest, and I don't test it when I make new releases.
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. And there's another TBNL tutorial from Frank Buss.
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.
[Macro]
define-easy-handler description lambda-list [[declaration* | documentation]] form*
Defines a handler as if byDEFUN
and optionally registers it with a URI so that it will be found byDISPATCH-EASY-HANDLERS
.
description
is either a symbolname
or a list matching the destructuring lambda list(name &key uri default-parameter-type default-request-type).lambda-list
is a list the elements of which are either a symbolvar
or a list matching the destructuring lambda list(var &key real-name parameter-type init-form request-type).The resulting handler will be a Lisp function with the namename
and keyword parameters named by thevar
symbols. Eachvar
will be bound to the value of the GET or POST parameter calledreal-name
(a string) before the body of the function is executed. Ifreal-name
is not provided, it will be computed by downcasing the symbol name ofname
.If
uri
(which is evaluated) is provided, then it must be a string or a function designator for a unary function. In this case, the handler will be returned byDISPATCH-EASY-HANDLERS
, ifuri
is a string and the script name of the current request isuri
, or ifuri
designates a function and applying this function to the currentREQUEST
object returns a true value.Whether the GET or POST parameter (or both) will be taken into consideration, depends on
request-type
which can be:GET
,:POST
,:BOTH
, orNIL
. In the last case, the value ofdefault-request-type
(the default of which is:BOTH
) will be used.The value of
var
will usually be a string (unless it resulted from a file upload in which case it won't be converted at all), but ifparameter-type
(which is evaluated) is provided, the string will be converted to another Lisp type by the following rules:If the corresponding GET or POST parameter wasn't provided by the client,
var
's value will beNIL
. Ifparameter-type
is'STRING
,var
's value remains as is. Ifparameter-type
is'INTEGER
and the parameter string consists solely of decimal digits,var
's value will be the corresponding integer, otherwiseNIL
. Ifparameter-type
is'KEYWORD
,var
's value will be the keyword obtained by interning the parameter string into the keyword package. Ifparameter-type
is'CHARACTER
and the parameter string is of length one,var
's value will be the single character of this string, otherwiseNIL
. Ifparameter-type
is'BOOLEAN
,var
's value will always beT
(unless it isNIL
by the first rule above, of course). Ifparameter-type
is any other atom, it is supposed to be a function designator for a unary function which will be called to convert the string to something else.Those were the rules for simple parameter types, but
parameter-type
can also be a list starting with one of the symbolsLIST
,ARRAY
, orHASH-TABLE
. The second value of the list must always be a simple parameter type as in the last paragraph - we'll call it the inner type below.In the case of
'LIST
, all GET/POST parameters calledreal-name
will be collected, converted to the inner type as by the rules above, and assembled into a list which will be the value ofvar
.In the case of
'ARRAY
, all GET/POST parameters which have a name like the result of(format nil "~A[~A]" real-name n)wheren
is a non-negative integer, will be assembled into an array where then
th element will be set accordingly, after conversion to the inner type. The array, which will become the value ofvar
, will be big enough to hold all matching parameters, but not bigger. Array elements not set as described above will beNIL
. Note thatVAR
will always be bound to an array, which may be empty, so it will never beNIL
, even if no appropriate GET/POST parameters are found.The full form of a
'HASH-TABLE
parameter type is(hash-table inner-type key-type test-function),butkey-type
andtest-function
can be left out in which case they default to'STRING
and'EQUAL
, respectively. For this parameter type, all GET/POST parameters which have a name like the result of(format nil "~A{~A}" real-name key)(wherekey
is a string that doesn't contain curly brackets) will become the values (after conversion toinner-type
) of a hash table with test functiontest-function
wherekey
(after conversion tokey-type
) will be the corresponding key. Note thatvar
will always be bound to a hash table, which may be empty, so it will never beNIL
, even if no appropriate GET/POST parameters are found.To make matters even more complicated, the three compound parameter types also have an abbreviated form - just one of the symbols
LIST
,ARRAY
, orHASH-TABLE
. In this case, the inner type will default to'STRING
.If
parameter-type
is not provided orNIL
,default-parameter-type
(the default of which is'STRING
) will be used instead.If the result of the computations above would be that
var
would be bound toNIL
, theninit-form
(if provided) will be evaluated instead, andvar
will be bound to the result of this evaluation.Handlers built with this macro are constructed in such a way that the resulting Lisp function is useful even outside of TBNL. Specifically, all the parameter computations above will only happen if
*REQUEST*
is bound, i.e. if we're within a TBNL request. Otherwise,var
will always be bound to the result of evaluatinginit-form
unless a corresponding keyword argument is provided.The test code that comes with TBNL contains an example which demonstrates some of the features of
DEFINE-EASY-HANDLER
.
[Function]
dispatch-easy-handlers request => handler
This is a dispatcher which returns the appropriate handler defined withDEFINE-EASY-HANDLER
, if there is one. The newest handlers are checked first.DEFINE-EASY-HANDLER
makes sure that there's always only one handler per name and one per URI. URIs are compared byEQUAL
, so anonymous functions won't be recognized as being identical.
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
.
[Special variable]
*file-upload-hook*
If this is notNIL
, it should be a designator for a unary function which will be called with a pathname for each file which is uploaded to TBNL. The pathname denotes the temporary file to which the uploaded file is written. The hook is called directly before the file is created. At this point,*REQUEST*
is already bound to the currentREQUEST
object, but obviously you can't access the post parameters yet.
[Function]
raw-post-data &optional request => string
If the request is a POST request (which is not of typemultipart/form-data
), returns the raw body of the request, otherwise returnsT
.
[Function]
parameter name &optional request => string
Returns the value of the GET or POST parameter named by the stringname
as a string (orNIL
if there ain't no parameter with this name). If both a GET and a POST parameter with the namename
exist, the GET parameter will be returned. See alsoGET-PARAMETER
andPOST-PARAMETER
.
[Function]
header-in name &optional request => string
Returns the incoming header named by the stringname
as a string (orNIL
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 alsoHEADERS-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 alsoHEADER-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 incomingAuthorization
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 variableREMOTE_ADDR
in CGI scripts.) See alsoREAL-REMOTE-ADDR
. This function doesn't necessarily 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. Ifas-number
is true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variableREMOTE_PORT
in CGI scripts.) This function doesn't necessarily return a meaningful value unless you use the mod_lisp front-end.
[Function]
real-remote-addr &optional request => string
Returns the value of the incomingX-Forwarded-For
http header if it exists. Otherwise returns the value ofREMOTE-ADDR
. This function doesn't necessarily 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 variableSERVER_ADDR
in CGI scripts.) This function doesn't necessarily 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. Ifas-number
is true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variableSERVER_PORT
in CGI scripts.) This function doesn't necessarily 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"
. Ifas-keyword
is true, which is the default, the result will be returned as a keyword, otherwise as a string. (This corresponds to the environment variableSERVER_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 variableSSL_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 incomingUser-Agent
http header. (This corresponds to the environment variableHTTP_USER_AGENT
in CGI scripts.)
[Function]
referer &optional request => string
Returns the value of the incomingReferer
(sic!) http header. (This corresponds to the environment variableHTTP_REFERER
in CGI scripts.)
[Function]
cookie-in name &optional request => string
Returns the value of the incoming cookie named by the stringname
(orNIL
if there ain't no cookie with this name). See alsoCOOKIES-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 alsoCOOKIE-IN
.
This accessor can be used to associate arbitrary data with the the symbolsymbol
in theREQUEST
objectrequest
.present-p
is true if such data was found, otherwiseNIL
.
[Function]
delete-aux-request-value symbol &optional request => |
Completely removes any data associated with the symbolsymbol
from theREQUEST
objectrequest
. Note that this is different from usingAUX-REQUEST-VALUE
to set the data toNIL
.
[Function]
recompute-request-parameters &key request external-format => |
Recomputes the GET and POST parameters for theREQUEST
objectrequest
. This only makes sense if you've changed the external format and with POST parameters it will only work if the request body was sent with aContent-Length
header and theapplication/x-www-form-urlencoded
content type.The default value for
external-format
is*TBNL-DEFAULT-EXTERNAL-FORMAT*
. Note that external formats are usually implementation-dependent but you can use the predefined values+LATIN-1+
and+UTF-8+
. Seetest/test.lisp
for an example.
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.
In all of the functions below the default value
for the optional argument reply
is the value of *REPLY*,
i.e. handlers will usually not need to provide this argument when
calling the function.
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 *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 currentREPLY
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 stringname
if there is one, otherwiseNIL
.SETF
ofHEADER-OUT
changes the current value of the header namedname
. If no header namedname
exists it is created. The association between a header and its name is case-insensitive.Note that the headers
Set-Cookie
,Content-Length
, andContent-Type
cannot be queried byHEADER-OUT
and must not be set bySETF
ofHEADER-OUT
.See also
HEADERS-OUT
,CONTENT-TYPE
,CONTENT-LENGTH
,COOKIES-OUT
, andCOOKIE-OUT
.
[Function]
headers-out &optional request => alist
Returns an alist of all outgoing http parameters (except forSet-Cookie
,Content-Length
, andContent-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, useSETF
ofHEADER-OUT
instead.
[Function]
cookie-out name &optional reply => cookie
Returns the outgoing cookie named by the stringname
(orNIL
if there ain't no cookie with this name). See alsoCOOKIES-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 alsoCOOKIE-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
ofRETURN-CODE
changes it. The return code of eachREPLY
object is initially set to+HTTP-OK+
.
[Accessor]
content-type &optional reply => string
(setf (content-type &optional reply) new-value)
CONTENT-TYPE
returns the outgoingContent-Type
http header.SETF
ofCONTENT-TYPE
changes the current value of this header. The content type of eachREPLY
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 outgoingContent-Length
http header.SETF
ofCONTENT-LENGTH
changes the current value of this header. The content length of eachREPLY
object is initially set toNIL
. If you leave it like that TBNL will automatically try to compute the correct value usingLENGTH
. If you set the value yourself you must make sure that it's the correct length of the body in octets (not in characters).Note that setting this value explicitely doesn't mix well with URL rewriting.
[Function]
send-headers => stream
Sends the initial status line and all headers as determined by theREPLY
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 likeREDIRECT
or throwing toTBNL-HANDLER-DONE
won't have the desired effect once the headers are sent.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
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. SeeRETURN-CODE
.
[Special variable]
*default-content-type*
The value of this variable is used to initialize the content type of eachREPLY
object. Its initial value is"text/html; charset=iso-8859-1"
. SeeCONTENT-TYPE
.
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
.
[Function]
start-session => session
Returns*SESSION*
if it isn'tNIL
, otherwise creates a newSESSION
object and returns it.
This accessor can be used to associate arbitrary data with the the symbolsymbol
in theSESSION
objectsession
.present-p
is true if such data was found, otherwiseNIL
. The default value forsession
is*SESSION*
.If
SETF
ofSESSION-VALUE
is called withsession
beingNIL
then a session is automatically instantiated withSTART-SESSION
.
[Function]
delete-session-value symbol &optional session => |
Completely removes any data associated with the symbolsymbol
from theSESSION
objectsession
. Note that this is different from usingSESSION-VALUE
to set the data toNIL
. The default value forsession
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 theSESSION
objectsession
. 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) theSESSION
objectsession
has been used.
[Accessor]
session-max-time session => seconds
(setf (session-max-time session) seconds)
This gets or sets the maximum time (in seconds) theSESSION
objectsession
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 thanseconds
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 (seeREAL-REMOTE-ADDR
) of the client for which theSESSION
objectsession
was initiated.
[Function]
session-user-agent session => address
Returns the 'User-Agent' http header (seeUSER-AGENT
) of the client for which theSESSION
objectsession
was initiated.
[Special variable]
*use-remote-addr-for-sessions*
If this value is true (the default isNIL
) then the 'real' remote address (seeREAL-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.Note that this is not secure because it's obviously not very hard to fake an
X_FORWARDED_FOR
header. On the other hand, relying on the remote address (seeREMOTE-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 sendX_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 (seeUSER-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.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.
[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 (seeCONTENT-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, aSESSION
object. This function is called directly before the session is destroyed, either byRESET-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 - seeSESSION-MAX-TIME
. This value is initially set to 1800.
[Macro]
do-sessions (var &optional result-form) statement* => result
Executes the statements withvar
bound to each existingSESSION
object consecutively. An implicit block namedNIL
surrounds the body of this macro. Returns the values returned byresult-form
unlessRETURN
is executed. The scope of the binding ofvar
does not includeresult-form
.
Furthermore, all errors happening within a handler which are not caught by the handler itself are handled by TBNL - see details below.
[Special variable]
*use-apache-log*
If this variable is true log messages are forwarded to Apache/mod_lisp. If it isNIL
log messages are written to the log file. The default value of this variable isNIL
if and only if:ARANEIDA
is an element of*FEATURES*
when building TBNL.
[Accessor]
log-file => pathname
(setf (log-file) pathspec)
The functionLOG-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 beNIL
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'sNIL
.format
andargs
are used as withFORMAT
.
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 => |
LikeLOG-MESSAGE
but withlog-level
set to*DEFAULT-LOG-LEVEL*
.
[Special variable]
*default-log-level*
The log level used byLOG-MESSAGE*
. The initial value isNIL
.
[Special variable]
*log-lisp-errors-p*
Whether unhandled errors in handlers should be logged. See also*LISP-ERRORS-LOG-LEVEL*
. The default value isT
.
[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 isT
.
[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 isNIL
.
[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 isNIL
, however, no such prefix will be written. If the value isT
(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 isNIL
(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 isNIL
.
[Special variable]
*show-access-log-messages*
If this variable is true and if*USE-APACHE-LOG*
isNIL
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 isT
.
[Special variable]
*http-error-handler*
This variable holdsNIL
(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 orNIL
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.)
[Function]
get-backtrace condition => backtrace
This is the function that is used internally by TBNL to show or log backtraces. It accepts a condition objectcondition
and returns a string with the corresponding backtrace.
[Function]
start-tbnl => listener
This will start TBNL by creating a '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 'listener' (seeSTART-TBNL
) 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 is3000
.
[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 (orNIL
) to this catch to immediately abort handling the request. See the source code ofREDIRECT
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 timetime
then the header+HTTP-NOT-MODIFIED+
with no content is immediately returned to the client.Note that for this function to be useful you should usually send 'Last-Modified' headers back to the client. See the code of
CREATE-STATIC-FILE-DISPATCHER-AND-HANDLER
for an example.
[Function]
rfc-1123-date &optional time => string
This function accepts a universal timetime
(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 - seeHANDLE-IF-MODIFIED-SINCE
.
[Function]
redirect script-name &key host protocol add-session-id permanently => |
Sends back appropriate headers to redirect the client to the resourcescript-name
on the hosthost
. Ifpermanently
is true (the default isNIL
) a 301 status code will be sent, otherwise a 302 status code. Ifhost
is not provided the current host (seeHOST
) will be used. Ifprotocol
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 bothhost
andprotocol
aren't provided then the value ofprotocol
will match the current request.
[Function]
require-authorization &optional realm => |
Sends back appropriate headers to require basic http authentication (see RFC 2617) for the realmrealm
. The default value vorrealm
is"TBNL"
.
[Function]
escape-for-html string => escaped-string
Escapes all occurrences of the characters#\<
,#\>
,#\'
,#"
, and#\&
withinstring
for HTML output.
[Function]
url-encode string &optional external-format => url-encoded-string
URL-encodes a string using the external formatexternal-format
. The default forexternal-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 formatexternal-format
, i.e. this is the inverse ofURL-ENCODE
. It is assumed that you'll rarely need this function, if ever. But just in case here it is. The default forexternal-format
is the value of*TBNL-DEFAULT-EXTERNAL-FORMAT*
.
[Function]
read-from-string* string => object, position
This is likeREAD-FROM-STRING
but*READ-EVAL*
is bound toNIL
and*PACKAGE*
is bound to the package namedTBNL-DUMMY
which exists solely for this purpose.
[Function]
http-token-p object => generalized-boolean
This function tests whetherobject
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]
*tbnl-default-external-format*
The external format used when computing theREQUEST
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.)
If you want to debug your TBNL/mod_lisp applications it is also
recommended 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.
Another thing 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... :)
[Special variable]
*catch-errors-p*
If the value of this variable isNIL
(the default isT
), then errors which happen while a request is handled aren't caught as usual, but instead your Lisp's debugger is invoked. This variable should obviously always be set to a true value in a production environment.
[Special variable]
*debug-mode*
If the value of this variable is set to a true value (the default isNIL
), 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.
(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.
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.
$Header: /usr/local/cvsrep/tbnl/doc/index.html,v 1.146 2006/10/09 23:46:41 edi Exp $