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 aREQUESTobject and, depending on this object, should either return a handler to handle the request orNILwhich 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 returnshandlerwhenever 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 returnshandlerwhenever 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 designatorpathwith content typecontent-typeto the client. Sets the necessary handlers. In particular the function employsHANDLE-IF-MODIFIED-SINCE.If
content-typeisNILthe 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-HEADERSinternally, 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 designatorpathwith content typecontent-typeif theSCRIPT-NAMEof the request matches the stringuri. UsesHANDLE-STATIC-FILEinternally.If
content-typeisNILthe 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-paththat is denoted by the URI of the request relative touri-prefix.uri-prefixmust be a string ending with a slash,base-pathmust be a pathname designator for an existing directory. The content types of the files are determined via their suffix, falling back todefault-content-typeif the suffix is unknown.Uses
HANDLE-STATIC-FILEinternally.
[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 byDEFUNand optionally registers it with a URI so that it will be found byDISPATCH-EASY-HANDLERS.
descriptionis either a symbolnameor a list matching the destructuring lambda list(name &key uri default-parameter-type default-request-type).lambda-listis a list the elements of which are either a symbolvaror 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 namenameand keyword parameters named by thevarsymbols. Eachvarwill be bound to the value of the GET or POST parameter calledreal-name(a string) before the body of the function is executed. Ifreal-nameis 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, ifuriis a string and the script name of the current request isuri, or ifuridesignates a function and applying this function to the currentREQUESTobject returns a true value.Whether the GET or POST parameter (or both) will be taken into consideration, depends on
request-typewhich 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
varwill 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-typeis'STRING,var's value remains as is. Ifparameter-typeis'INTEGERand the parameter string consists solely of decimal digits,var's value will be the corresponding integer, otherwiseNIL. Ifparameter-typeis'KEYWORD,var's value will be the keyword obtained by interning the parameter string into the keyword package. Ifparameter-typeis'CHARACTERand the parameter string is of length one,var's value will be the single character of this string, otherwiseNIL. Ifparameter-typeis'BOOLEAN,var's value will always beT(unless it isNILby the first rule above, of course). Ifparameter-typeis 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-typecan 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-namewill 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)wherenis a non-negative integer, will be assembled into an array where thenth 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 thatVARwill 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-TABLEparameter type is(hash-table inner-type key-type test-function),butkey-typeandtest-functioncan be left out in which case they default to'STRINGand'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)(wherekeyis a string that doesn't contain curly brackets) will become the values (after conversion toinner-type) of a hash table with test functiontest-functionwherekey(after conversion tokey-type) will be the corresponding key. Note thatvarwill 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-typeis 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
varwould be bound toNIL, theninit-form(if provided) will be evaluated instead, andvarwill 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,varwill always be bound to the result of evaluatinginit-formunless 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-HANDLERmakes 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 currentREQUESTobject.
[Function]
host &optional request => string
Returns the value of the incomingHosthttp header. (This corresponds to the environment variableHTTP_HOSTin CGI scripts.)
[Function]
request-method &key request as-keyword => keyword/string
Returns the request method. Ifas-keywordis true (which is the default), then the result is returned as a keyword, otherwise as a string. (This corresponds to the environment variableREQUEST_METHODin 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=barinto his browser, this function will return"/xxx/frob.html?foo=bar". (This corresponds to the environment variableREQUEST_URIin 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-URIin front of the first question mark (if any). (This corresponds to the environment variableSCRIPT_NAMEin 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-URIbehind the first question mark (if any). (This corresponds to the environment variableQUERY_STRINGin CGI scripts.) See alsoGET-PARAMETERandGET-PARAMETERS.
[Function]
get-parameter name &optional request => string
Returns the value of the GET parameter (as provided via the request URI) named by the stringnameas a string (orNILif 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 (orNILif 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)wherepathis 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 bypathwill 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-PARAMETERSand*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 currentREQUESTobject, 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 stringnameas a string (orNILif there ain't no parameter with this name). If both a GET and a POST parameter with the namenameexist, the GET parameter will be returned. See alsoGET-PARAMETERandPOST-PARAMETER.
[Function]
header-in name &optional request => string
Returns the incoming header named by the stringnameas a string (orNILif 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.nameis 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-INand the remark about incoming headers there.
[Function]
authorization &optional request => user, password
Returns as two values the user and password (if any) from the incomingAuthorizationhttp 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_ADDRin 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-numberis true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variableREMOTE_PORTin 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-Forhttp 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_ADDRin 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-numberis true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variableSERVER_PORTin 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-keywordis true, which is the default, the result will be returned as a keyword, otherwise as a string. (This corresponds to the environment variableSERVER_PROTOCOLin 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_IDin 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-Agenthttp header. (This corresponds to the environment variableHTTP_USER_AGENTin CGI scripts.)
[Function]
referer &optional request => string
Returns the value of the incomingReferer(sic!) http header. (This corresponds to the environment variableHTTP_REFERERin CGI scripts.)
[Function]
cookie-in name &optional request => string
Returns the value of the incoming cookie named by the stringname(orNILif 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 symbolsymbolin theREQUESTobjectrequest.present-pis true if such data was found, otherwiseNIL.
[Function]
delete-aux-request-value symbol &optional request => |
Completely removes any data associated with the symbolsymbolfrom theREQUESTobjectrequest. Note that this is different from usingAUX-REQUEST-VALUEto set the data toNIL.
[Function]
recompute-request-parameters &key request external-format => |
Recomputes the GET and POST parameters for theREQUESTobjectrequest. 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-Lengthheader and theapplication/x-www-form-urlencodedcontent type.The default value for
external-formatis*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.lispfor 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 currentREPLYobject.
[Accessor]
header-out name &optional reply => string
(setf (header-out name &optional reply) new-value)
HEADER-OUTreturns the outgoing http header named by the stringnameif there is one, otherwiseNIL.SETFofHEADER-OUTchanges the current value of the header namedname. If no header namednameexists it is created. The association between a header and its name is case-insensitive.Note that the headers
Set-Cookie,Content-Length, andContent-Typecannot be queried byHEADER-OUTand must not be set bySETFofHEADER-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, useSETFofHEADER-OUTinstead.
[Function]
cookie-out name &optional reply => cookie
Returns the outgoing cookie named by the stringname(orNILif there ain't no cookie with this name). See alsoCOOKIES-OUTand 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-OUTand the section about cookies.
[Accessor]
return-code &optional reply => string
(setf (return-code &optional reply) new-value)
RETURN-CODEreturns the http return code of the reply,SETFofRETURN-CODEchanges it. The return code of eachREPLYobject is initially set to+HTTP-OK+.
[Accessor]
content-type &optional reply => string
(setf (content-type &optional reply) new-value)
CONTENT-TYPEreturns the outgoingContent-Typehttp header.SETFofCONTENT-TYPEchanges the current value of this header. The content type of eachREPLYobject is initially set to the value of*DEFAULT-CONTENT-TYPE*.
[Accessor]
content-length &optional reply => length
(setf (content-length &optional reply) new-value)
CONTENT-LENGTHreturns the outgoingContent-Lengthhttp header.SETFofCONTENT-LENGTHchanges the current value of this header. The content length of eachREPLYobject 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 theREPLYobject*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 likeREDIRECTor throwing toTBNL-HANDLER-DONEwon'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 eachREPLYobject. 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 aCOOKIEobject from the parameters provided to this function and adds it to the outgoing cookies of theREPLYobjectreply. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default forreplyis*REPLY*. The default forvalueis the empty string.
[Function]
set-cookie* cookie &optional reply => cookie
Adds theCOOKIEobjectcookieto the outgoing cookies of theREPLYobjectreply. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default forreplyis*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 currentSESSIONobject (if any) orNIL.
[Function]
start-session => session
Returns*SESSION*if it isn'tNIL, otherwise creates a newSESSIONobject and returns it.
This accessor can be used to associate arbitrary data with the the symbolsymbolin theSESSIONobjectsession.present-pis true if such data was found, otherwiseNIL. The default value forsessionis*SESSION*.If
SETFofSESSION-VALUEis called withsessionbeingNILthen a session is automatically instantiated withSTART-SESSION.
[Function]
delete-session-value symbol &optional session => |
Completely removes any data associated with the symbolsymbolfrom theSESSIONobjectsession. Note that this is different from usingSESSION-VALUEto set the data toNIL. The default value forsessionis*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 theSESSIONobjectsession. 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) theSESSIONobjectsessionhas been used.
[Accessor]
session-max-time session => seconds
(setf (session-max-time session) seconds)
This gets or sets the maximum time (in seconds) theSESSIONobjectsessionshould 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 thansecondsseconds 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 theSESSIONobjectsessionwas initiated.
[Function]
session-user-agent session => address
Returns the 'User-Agent' http header (seeUSER-AGENT) of the client for which theSESSIONobjectsessionwas 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_FORheader. 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_FORheaders 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, aSESSIONobject. This function is called directly before the session is destroyed, either byRESET-SESSIONSor 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 withvarbound to each existingSESSIONobject consecutively. An implicit block namedNILsurrounds the body of this macro. Returns the values returned byresult-formunlessRETURNis executed. The scope of the binding ofvardoes 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 isNILlog messages are written to the log file. The default value of this variable isNILif and only if:ARANEIDAis an element of*FEATURES*when building TBNL.
[Accessor]
log-file => pathname
(setf (log-file) pathspec)
The functionLOG-FILEreturns 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-levelshould be one of the keywords:EMERG,:ALERT,:CRIT,:ERROR,:WARNING,:NOTICE,:INFO, or:DEBUGwhich correspond to the various Apache log levels.log-levelcan also beNILin 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.formatandargsare used as withFORMAT.
LOG-MESSAGEis a generic function so you can specialize it or bypass it completely with an around method.
[Function]
log-message* format &rest args => |
LikeLOG-MESSAGEbut withlog-levelset 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*isNILthen 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 orNILif 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 objectconditionand 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 ofREDIRECTfor 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 timetimethen 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-HANDLERfor 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-nameon the hosthost. Ifpermanentlyis true (the default isNIL) a 301 status code will be sent, otherwise a 302 status code. Ifhostis not provided the current host (seeHOST) will be used. Ifprotocolis the keyword:HTTPSthe client will be redirected to a https URL, if it's:HTTPit'll be sent to a http URL. If bothhostandprotocolaren't provided then the value ofprotocolwill 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 vorrealmis"TBNL".
[Function]
escape-for-html string => escaped-string
Escapes all occurrences of the characters#\<,#\>,#\',#", and#\&withinstringfor HTML output.
[Function]
url-encode string &optional external-format => url-encoded-string
URL-encodes a string using the external formatexternal-format. The default forexternal-formatis 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-formatis the value of*TBNL-DEFAULT-EXTERNAL-FORMAT*.
[Function]
read-from-string* string => object, position
This is likeREAD-FROM-STRINGbut*READ-EVAL*is bound toNILand*PACKAGE*is bound to the package namedTBNL-DUMMYwhich exists solely for this purpose.
[Function]
http-token-p object => generalized-boolean
This function tests whetherobjectis 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 theREQUESTobject. 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 $