TBNL - A toolkit for dynamic Lisp websites


 

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.


 

Contents

  1. General overview
  2. Requirements, download, installation, configuration
    1. Installing the Apache/mod_lisp front-end
    2. Installing the Araneida front-end
    3. Installing TBNL itself
    4. Activating the Apache/mod_lisp front-end
    5. Activating the Araneida front-end
    6. Using TBNL without a front-end
    7. Miscellaneous
  3. Support and mailing lists
  4. The TBNL dictionary
    1. Handlers
      1. *dispatch-table*
      2. default-dispatcher
      3. *default-handler*
      4. create-prefix-dispatcher
      5. create-regex-dispatcher
      6. handle-static-file
      7. create-static-file-dispatcher-and-handler
      8. create-folder-dispatcher-and-handler
      9. dispatch-request
      10. define-easy-handler
      11. dispatch-easy-handlers
    2. Requests
      1. *request*
      2. host
      3. request-method
      4. request-uri
      5. script-name
      6. query-string
      7. get-parameter
      8. get-parameters
      9. post-parameter
      10. post-parameters
      11. *file-upload-hook*
      12. raw-post-data
      13. parameter
      14. header-in
      15. headers-in
      16. authorization
      17. remote-addr
      18. remote-port
      19. real-remote-addr
      20. server-addr
      21. server-port
      22. server-protocol
      23. mod-lisp-id
      24. ssl-session-id
      25. user-agent
      26. referer
      27. cookie-in
      28. cookies-in
      29. aux-request-value
      30. delete-aux-request-value
      31. recompute-request-parameters
    3. Replies
      1. *reply*
      2. header-out
      3. headers-out
      4. cookie-out
      5. cookies-out
      6. return-code
      7. content-type
      8. content-length
      9. send-headers
      10. +http-continue+
      11. +http-switching-protocols+
      12. +http-ok+
      13. +http-created+
      14. +http-accepted+
      15. +http-non-authoritative-information+
      16. +http-no-content+
      17. +http-reset-content+
      18. +http-partial-content+
      19. +http-multiple-choices+
      20. +http-moved-permanently+
      21. +http-moved-temporarily+
      22. +http-see-other+
      23. +http-not-modified+
      24. +http-use-proxy+
      25. +http-temporary-redirect+
      26. +http-bad-request+
      27. +http-authorization-required+
      28. +http-payment-required+
      29. +http-forbidden+
      30. +http-not-found+
      31. +http-method-not-allowed+
      32. +http-not-acceptable+
      33. +http-proxy-authentication-required+
      34. +http-request-time-out+
      35. +http-conflict+
      36. +http-gone+
      37. +http-length-required+
      38. +http-precondition-failed+
      39. +http-request-entity-too-large+
      40. +http-request-uri-too-large+
      41. +http-unsupported-media-type+
      42. +http-requested-range-not-satisfiable+
      43. +http-expectation-failed+
      44. +http-internal-server-error+
      45. +http-not-implemented+
      46. +http-bad-gateway+
      47. +http-service-unavailable+
      48. +http-gateway-time-out+
      49. +http-version-not-supported+
      50. *default-content-type*
    4. Cookies
      1. cookie-name
      2. cookie-value
      3. cookie-expires
      4. cookie-path
      5. cookie-domain
      6. cookie-secure
      7. set-cookie
      8. set-cookie*
    5. Sessions
      1. *session*
      2. start-session
      3. session-value
      4. delete-session-value
      5. reset-sessions
      6. session-string
      7. session-counter
      8. session-max-time
      9. session-remote-addr
      10. session-user-agent
      11. *use-remote-addr-for-sessions*
      12. *use-user-agent-for-sessions*
      13. *rewrite-for-session-urls*
      14. *content-types-for-url-rewrite*
      15. *session-cookie-name*
      16. *session-removal-hook*
      17. *session-max-time*
      18. do-sessions
    6. Logging and error handling
      1. *use-apache-log*
      2. log-file
      3. log-message
      4. log-message*
      5. *default-log-level*
      6. *log-lisp-errors-p*
      7. *lisp-errors-log-level*
      8. *log-lisp-warnings-p*
      9. *lisp-warnings-log-level*
      10. *log-lisp-backtraces-p*
      11. *log-prefix*
      12. *show-lisp-errors-p*
      13. *show-lisp-backtraces-p*
      14. *show-access-log-messages*
      15. *http-error-handler*
      16. get-backtrace
    7. Miscellaneous
      1. start-tbnl
      2. stop-tbnl
      3. *listener*
      4. *tbnl-port*
      5. tbnl-handler-done
      6. no-cache
      7. handle-if-modified-since
      8. rfc-1123-date
      9. redirect
      10. require-authorization
      11. escape-for-html
      12. url-encode
      13. url-decode
      14. read-from-string*
      15. http-token-p
      16. *tmp-directory*
      17. *tbnl-default-external-format*
      18. +latin-1+
      19. +utf-8+
    8. Debugging TBNL applications
      1. *catch-errors-p*
      2. *debug-mode*
      3. *backtrace*
      4. *body*
      5. *command*
      6. *error*
  5. Known quirks
  6. History
  7. Acknowledgements

 

General overview

Once it's started TBNL will listen on a TCP/IP port (see *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.
 

Requirements, download, installation, and configuration

Installing the Apache/mod_lisp front-end

For this front-end you need two things: Then you will have to configure Apache and mod_lisp to make them aware of TBNL. First, in your Apache configuration file (usually called 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.)

Installing the Araneida front-end

For this front-end you'll have to install, compile and load Araneida before you build TBNL - if you had already compiled TBNL you must delete the FASL files first. In other words: You need to build a version of TBNL that knows about Araneida and therefore Araneida must be already loaded when you compile TBNL.

It is beyond the scope of this document to explain how to install Araneida - see Araneida's documentation.

Installing TBNL itself

TBNL depends on a couple of other Lisp libraries which you'll need to install first: Note: TBNL has been notorious to not work as expected with SBCL. I don't use SBCL myself, so I don't usually test against it. If you think TBNL fails on SBCL because of an error in TBNL, please report it, preferably with a patch to fix the problem. Note, however, that many of the recent problems with the SBCL/TBNL combo were due to problems in third-party libraries and/or due to the fast development of SBCL itself. Check if you use the latest versions of all dependencies listed above and search the TBNL mailing list - see for example here, here, and here.

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:

Now in your Lisp evaluate
(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.

Activating the Apache/mod_lisp front-end

Point your browser at 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.)

Activating the Araneida front-end

Start Araneida with
(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.

Using TBNL without a front-end

Just point your browser at http://localhost:3000/tbnl/test.

Miscellaneous

Look into the 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.


 

Support and mailing lists

For questions, bug reports, feature requests, improvements, or patches please use the tbnl-devel mailing list. If you want to be notified about future releases subscribe to the tbnl-announce mailing list. These mailing lists were made available thanks to the services of common-lisp.net.
 

The TBNL dictionary

Handlers

TBNL handles each incoming request dynamically depending on the contents of a global dispatch table. The details can be found below. (See the file 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 a REQUEST object and, depending on this object, should either return a handler to handle the request or NIL 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 symbol DEFAULT-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 by DEFAULT-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 returns handler whenever the path part of the request URI starts with the string prefix.


[Function]
create-regex-dispatcher regex handler => dispatch-fn


A convenience function which will return a dispatcher that returns handler whenever the path part of the request URI matches the CL-PPCRE regular expression regex (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 designator path with content type content-type to the client. Sets the necessary handlers. In particular the function employs HANDLE-IF-MODIFIED-SINCE.

If content-type is NIL 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 designator path with content type content-type if the SCRIPT-NAME of the request matches the string uri. Uses HANDLE-STATIC-FILE internally.

If content-type is NIL the function tries to determine the correct content type from the file's suffix or falls back to the value of *DEFAULT-CONTENT-TYPE*.


[Function]
create-folder-dispatcher-and-handler uri-prefix base-path &optional default-content-type => dispatch-fn


Creates and returns a dispatch function which will dispatch to a handler function which emits the file relative to base-path that is denoted by the URI of the request relative to uri-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 to default-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 by DEFUN and optionally registers it with a URI so that it will be found by DISPATCH-EASY-HANDLERS.

description is either a symbol name 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 symbol var 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 name name and keyword parameters named by the var symbols. Each var will be bound to the value of the GET or POST parameter called real-name (a string) before the body of the function is executed. If real-name is not provided, it will be computed by downcasing the symbol name of name.

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 by DISPATCH-EASY-HANDLERS, if uri is a string and the script name of the current request is uri, or if uri designates a function and applying this function to the current REQUEST 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, or NIL. In the last case, the value of default-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 if parameter-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 be NIL. If parameter-type is 'STRING, var's value remains as is. If parameter-type is 'INTEGER and the parameter string consists solely of decimal digits, var's value will be the corresponding integer, otherwise NIL. If parameter-type is 'KEYWORD, var's value will be the keyword obtained by interning the parameter string into the keyword package. If parameter-type is 'CHARACTER and the parameter string is of length one, var's value will be the single character of this string, otherwise NIL. If parameter-type is 'BOOLEAN, var's value will always be T (unless it is NIL by the first rule above, of course). If parameter-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 symbols LIST, ARRAY, or HASH-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 called real-name will be collected, converted to the inner type as by the rules above, and assembled into a list which will be the value of var.

In the case of 'ARRAY, all GET/POST parameters which have a name like the result of

  (format nil "~A[~A]" real-name n)
where n is a non-negative integer, will be assembled into an array where the nth element will be set accordingly, after conversion to the inner type. The array, which will become the value of var, will be big enough to hold all matching parameters, but not bigger. Array elements not set as described above will be NIL. Note that VAR will always be bound to an array, which may be empty, so it will never be NIL, 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),
but key-type and test-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)
(where key is a string that doesn't contain curly brackets) will become the values (after conversion to inner-type) of a hash table with test function test-function where key (after conversion to key-type) will be the corresponding key. Note that var will always be bound to a hash table, which may be empty, so it will never be NIL, 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, or HASH-TABLE. In this case, the inner type will default to 'STRING.

If parameter-type is not provided or NIL, 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 to NIL, then init-form (if provided) will be evaluated instead, and var 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 evaluating init-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 with DEFINE-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 by EQUAL, so anonymous functions won't be recognized as being identical.

Requests

When a request comes in, TBNL creates a 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 current REQUEST object.


[Function]
host &optional request => string


Returns the value of the incoming Host http header. (This corresponds to the environment variable HTTP_HOST in CGI scripts.)


[Function]
request-method &key request as-keyword => keyword/string


Returns the request method. If as-keyword is true (which is the default), then the result is returned as a keyword, otherwise as a string. (This corresponds to the environment variable REQUEST_METHOD in CGI scripts.)


[Function]
request-uri &optional request => string


Returns the URI for request. Note that this not the full URI but only the part behind the scheme and authority components, so that if the user has typed http://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 variable REQUEST_URI in CGI scripts.)


[Function]
script-name &optional request => string


Returns the file name (or path) component of the URI for request, i.e. the part of the string returned by REQUEST-URI in front of the first question mark (if any). (This corresponds to the environment variable SCRIPT_NAME in CGI scripts.)


[Function]
query-string &optional request => string


Returns the query component of the URI for request, i.e. the part of the string returned by REQUEST-URI behind the first question mark (if any). (This corresponds to the environment variable QUERY_STRING in CGI scripts.) See also GET-PARAMETER and GET-PARAMETERS.


[Function]
get-parameter name &optional request => string


Returns the value of the GET parameter (as provided via the request URI) named by the string name as a string (or NIL 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 name name. See also GET-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 also GET-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 string name. Note that only the first value will be returned if the client provided more than one POST parameter with the name name. This value will usually be a string (or NIL 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)
where path is a pathname denoting the place were the uploaded file was stored, file-name (a string) is the file name sent by the browser, and content-type (also a string) is the content type sent by the browser. The file denoted by path 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 also POST-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 also POST-PARAMETER.


[Special variable]
*file-upload-hook*


If this is not NIL, 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 current REQUEST 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 type multipart/form-data), returns the raw body of the request, otherwise returns T.


[Function]
parameter name &optional request => string


Returns the value of the GET or POST parameter named by the string name as a string (or NIL if there ain't no parameter with this name). If both a GET and a POST parameter with the name name exist, the GET parameter will be returned. See also GET-PARAMETER and POST-PARAMETER.


[Function]
header-in name &optional request => string


Returns the incoming header named by the string name as a string (or NIL if there ain't no header with this name). Note that this queries the headers sent to TBNL by the front-end or by the client. This may not only include the incoming http headers but also some headers sent by mod_lisp or Araneida. name is matched case-insensitively. See also HEADERS-IN.


[Function]
headers-in &optional request => alist


Returns an alist of all incoming headers. The car of each element of this list is the headers's name while the cdr is its value (as a string). There's no guarantee about the order of this list. See also HEADER-IN and the remark about incoming headers there.


[Function]
authorization &optional request => user, password


Returns as two values the user and password (if any) from the incoming Authorization http header.


[Function]
remote-addr &optional request => string


Returns the IP address (as a string) of the client which sent the request. (This corresponds to the environment variable REMOTE_ADDR in CGI scripts.) See also REAL-REMOTE-ADDR. This function doesn't 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. If as-number is true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variable REMOTE_PORT in CGI scripts.) This function doesn't 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 incoming X-Forwarded-For http header if it exists. Otherwise returns the value of REMOTE-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 variable SERVER_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. If as-number is true, which is the default, the result will be returned as a number, otherwise as a string. (This corresponds to the environment variable SERVER_PORT in CGI scripts.) This function doesn't 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". If as-keyword is true, which is the default, the result will be returned as a keyword, otherwise as a string. (This corresponds to the environment variable SERVER_PROTOCOL in CGI scripts.)


[Function]
mod-lisp-id &optional request => string


Returns the 'Server ID' sent by mod_lisp. This corresponds to the third parameter in the "LispServer" directive in Apache's configuration file and can be interesting if you deploy several different Apaches or TBNL instances at once. This function doesn't return a meaningful value unless you use the mod_lisp front-end.


[Function]
ssl-session-id &optional request => string


Returns the SSL session ID if it exists. Note that SSL sessions aren't related to TBNL sessions. (This corresponds to the environment variable SSL_SESSION_ID in CGI scripts.) This function doesn't return a meaningful value unless you use the mod_lisp front-end.


[Function]
user-agent &optional request => string


Returns the value of the incoming User-Agent http header. (This corresponds to the environment variable HTTP_USER_AGENT in CGI scripts.)


[Function]
referer &optional request => string


Returns the value of the incoming Referer (sic!) http header. (This corresponds to the environment variable HTTP_REFERER in CGI scripts.)


[Function]
cookie-in name &optional request => string


Returns the value of the incoming cookie named by the string name (or NIL if there ain't no cookie with this name). See also COOKIES-IN.


[Function]
cookies-in &optional request => alist


Returns an alist of all incoming cookies. The car of each element of this list is the cookie's name while the cdr is the cookie's value. See also COOKIE-IN.


[Accessor]
aux-request-value symbol &optional request => value, present-p
(setf (aux-request-value symbol &optional request) new-value)


This accessor can be used to associate arbitrary data with the the symbol symbol in the REQUEST object request. present-p is true if such data was found, otherwise NIL.


[Function]
delete-aux-request-value symbol &optional request => |


Completely removes any data associated with the symbol symbol from the REQUEST object request. Note that this is different from using AUX-REQUEST-VALUE to set the data to NIL.


[Function]
recompute-request-parameters &key request external-format => |


Recomputes the GET and POST parameters for the REQUEST object request. 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 a Content-Length header and the application/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+. See test/test.lisp for an example.

Replies

It is the responsibility of a handler function to prepare the reply for the client. This is done by For each request there's one REPLY object which 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 current REPLY object.


[Accessor]
header-out name &optional reply => string
(setf (header-out name &optional reply) new-value)


HEADER-OUT returns the outgoing http header named by the string name if there is one, otherwise NIL. SETF of HEADER-OUT changes the current value of the header named name. If no header named name exists it is created. The association between a header and its name is case-insensitive.

Note that the headers Set-Cookie, Content-Length, and Content-Type cannot be queried by HEADER-OUT and must not be set by SETF of HEADER-OUT.

See also HEADERS-OUT, CONTENT-TYPE, CONTENT-LENGTH, COOKIES-OUT, and COOKIE-OUT.


[Function]
headers-out &optional request => alist


Returns an alist of all outgoing http parameters (except for Set-Cookie, Content-Length, and Content-Type). The car of each element of this list is the headers's name while the cdr is its value. This alist should not be manipulated directly, use SETF of HEADER-OUT instead.


[Function]
cookie-out name &optional reply => cookie


Returns the outgoing cookie named by the string name (or NIL if there ain't no cookie with this name). See also COOKIES-OUT and the section about cookies.


[Function]
cookies-out &optional reply => alist


Returns an alist of all outgoing cookies. The car of each element of this list is the cookie's name while the cdr is the cookie itself. See also COOKIE-OUT and the section about cookies.


[Accessor]
return-code &optional reply => string
(setf (return-code &optional reply) new-value)


RETURN-CODE returns the http return code of the reply, SETF of RETURN-CODE changes it. The return code of each REPLY object is initially set to +HTTP-OK+.


[Accessor]
content-type &optional reply => string
(setf (content-type &optional reply) new-value)


CONTENT-TYPE returns the outgoing Content-Type http header. SETF of CONTENT-TYPE changes the current value of this header. The content type of each REPLY object is initially set to the value of *DEFAULT-CONTENT-TYPE*.


[Accessor]
content-length &optional reply => length
(setf (content-length &optional reply) new-value)


CONTENT-LENGTH returns the outgoing Content-Length http header. SETF of CONTENT-LENGTH changes the current value of this header. The content length of each REPLY object is initially set to NIL. If you leave it like that TBNL will automatically try to compute the correct value using LENGTH. If you set the value yourself you must make sure that it's the correct length of the body in octets (not in characters).

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 the REPLY object *REPLY*. Returns a stream to which the body of the reply can be written. Once this function has been called further changes to *REPLY* don't have any effect. Also, automatic handling of errors (i.e. sending the corresponding status code to the browser, etc.) is turned off for this request. Likewise, functions like REDIRECT or throwing to TBNL-HANDLER-DONE won't have the desired effect once the headers are sent.

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. See RETURN-CODE.


[Special variable]
*default-content-type*


The value of this variable is used to initialize the content type of each REPLY object. Its initial value is "text/html; charset=iso-8859-1". See CONTENT-TYPE.

Cookies

Outgoing cookies are stored in the request's REPLY object (see COOKIE-OUT and COOKIES-OUT). They are CLOS objects defined like this:
(defclass cookie ()
  ((name :initarg :name
         :reader cookie-name
         :type string
         :documentation "The name of the cookie - a string.")
   (value :initarg :value
          :accessor cookie-value
          :initform ""
          :documentation "The value of the cookie. Will be URL-encoded when sent to the browser.")
   (expires :initarg :expires
            :initform nil
            :accessor cookie-expires
            :documentation "The time (a universal time) when the cookie expires (or NIL).")
   (path :initarg :path
         :initform nil
         :accessor cookie-path
         :documentation "The path this cookie is valid for (or NIL).")
   (domain :initarg :domain
           :initform nil
           :accessor cookie-domain
           :documentation "The domain this cookie is valid for (or NIL).")
   (secure :initarg :secure
           :initform nil
           :accessor cookie-secure
           :documentation "A generalized boolean denoting whether this cookie is a secure cookie.")))
The reader COOKIE-NAME and the accessors COOKIE-VALUE, COOKIE-EXPIRES, COOKIE-PATH, COOKIE-DOMAIN, and COOKIE-SECURE are all exported from the TBNL package.


[Function]
set-cookie name &key value expires path domain secure reply => cookie


Creates a COOKIE object from the parameters provided to this function and adds it to the outgoing cookies of the REPLY object reply. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default for reply is *REPLY*. The default for value is the empty string.


[Function]
set-cookie* cookie &optional reply => cookie


Adds the COOKIE object cookie to the outgoing cookies of the REPLY object reply. If a cookie with the same name (case-sensitive) already exists, it is replaced. The default for reply is *REPLY*.

Sessions

TNBL supports sessions: Once a TBNL page has called 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 current SESSION object (if any) or NIL.


[Function]
start-session => session


Returns *SESSION* if it isn't NIL, otherwise creates a new SESSION object and returns it.


[Accessor]
session-value symbol &optional session => value, present-p
(setf (session-value symbol &optional session) new-value)


This accessor can be used to associate arbitrary data with the the symbol symbol in the SESSION object session. present-p is true if such data was found, otherwise NIL. The default value for session is *SESSION*.

If SETF of SESSION-VALUE is called with session being NIL then a session is automatically instantiated with START-SESSION.


[Function]
delete-session-value symbol &optional session => |


Completely removes any data associated with the symbol symbol from the SESSION object session. Note that this is different from using SESSION-VALUE to set the data to NIL. The default value for session is *SESSION*.


[Function]
reset-sessions => |


This function unconditionally invalidates and destroys all sessions immediately.


[Function]
session-string session => string


Returns a unique string that's associated with the SESSION object session. This string is sent to the browser as a cookie value or as a GET parameter,


[Function]
session-counter session => count


Returns the number of times (requests) the SESSION object session has been used.


[Accessor]
session-max-time session => seconds
(setf (session-max-time session) seconds)


This gets or sets the maximum time (in seconds) the SESSION object session should be valid before it's invalidated: If a request associated with this session comes in and the last request for the same session was more than seconds seconds ago than the session is deleted and a new one is started for this client. The default value is determined by *SESSION-MAX-TIME*.


[Function]
session-remote-addr session => address


Returns the 'real' remote address (see REAL-REMOTE-ADDR) of the client for which the SESSION object session was initiated.


[Function]
session-user-agent session => address


Returns the 'User-Agent' http header (see USER-AGENT) of the client for which the SESSION object session was initiated.


[Special variable]
*use-remote-addr-for-sessions*


If this value is true (the default is NIL) then the 'real' remote address (see REAL-REMOTE-ADDR) of the client will be encoded into the session identifier, i.e. if this value changes on the client side the session will automatically be invalidated.

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 (see REMOTE-ADDR) of the client isn't an ideal solution either because some of your users may connect through http proxies and the proxy they use may change during the session. But then again, some proxies don't send X_FORWARDED_FOR headers anyway. Sigh...


[Special variable]
*use-user-agent-for-sessions*


If this value is true (which is the default) then the 'User-Agent' http header (see USER-AGENT) of the client will be encoded into the session identifier, i.e. if this value changes on the client side the session will automatically be invalidated.

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 (see CONTENT-TYPE) starts with one of the strings in *CONTENT-TYPES-FOR-URL-REWRITE* and unless the client has already sent a cookie named *SESSION-COOKIE-NAME*.


[Special variable]
*content-types-for-url-rewrite*


This is a list of strings (the initial value is ("text/html" "application/xhtml+xml")) the content-type of an outgoing body is compared with if *REWRITE-FOR-SESSION-URLS* is true. If the content-type starts with one of these strings then url-rewriting will happen, otherwise it won't.


[Special variable]
*session-cookie-name*


This is the name that is used for the session-related cookie or GET parameter sent to the client. Its default value is "tbnl-session". Note that changing this name while TBNL is running will invalidate existing sessions.


[Special variable]
*session-removal-hook*


The value of this variable should be a function of one argument, a SESSION object. This function is called directly before the session is destroyed, either by RESET-SESSIONS or when it's invalidated because it's too old.


[Special variable]
*session-max-time*


The default time (in seconds) after which a session times out - see SESSION-MAX-TIME. This value is initially set to 1800.


[Macro]
do-sessions (var &optional result-form) statement* => result


Executes the statements with var bound to each existing SESSION object consecutively. An implicit block named NIL surrounds the body of this macro. Returns the values returned by result-form unless RETURN is executed. The scope of the binding of var does not include result-form.

Logging and error handling

TBNL provides facilities for writing to Apache's error log file (when using the mod_lisp front-end) or for logging to an arbitrary file in the file system. Note that due to the nature of mod_lisp Apache log mesages don't appear immediately but only after all data has been sent from TBNL to Apache/mod_lisp.

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 is NIL log messages are written to the log file. The default value of this variable is NIL if and only if :ARANEIDA is an element of *FEATURES* when building TBNL.


[Accessor]
log-file => pathname
(setf (log-file) pathspec)


The function LOG-FILE returns a pathname designating the log file which is currently used (unless log messages are forwarded to Apache). This destination for log messages can be changed with (SETF LOG-FILE). The initial location of the log file is implementation-dependent.


[Generic function]
log-message log-level format &rest args => |


Schedules a message for the Apache log file or writes it directly to the current log file depending on the value of *USE-APACHE-LOG*. log-level should be one of the keywords :EMERG, :ALERT, :CRIT, :ERROR, :WARNING, :NOTICE, :INFO, or :DEBUG which correspond to the various Apache log levels. log-level can also be NIL in which case mod_lisp's default log level is used. If Apache isn't used the log level is just written to the log file unless it's NIL. format and args are used as with FORMAT.

LOG-MESSAGE is a generic function so you can specialize it or bypass it completely with an around method.


[Function]
log-message* format &rest args => |


Like LOG-MESSAGE but with log-level set to *DEFAULT-LOG-LEVEL*.


[Special variable]
*default-log-level*


The log level used by LOG-MESSAGE*. The initial value is NIL.


[Special variable]
*log-lisp-errors-p*


Whether unhandled errors in handlers should be logged. See also *LISP-ERRORS-LOG-LEVEL*. The default value is T.


[Special variable]
*lisp-errors-log-level*


The log level used to log Lisp errors. See also *LOG-LISP-ERRORS-P*. The default value is :ERROR.


[Special variable]
*log-lisp-warnings-p*


Whether unhandled warnings in handlers should be logged. See also *LISP-WARNINGS-LOG-LEVEL*. The default value is T.


[Special variable]
*lisp-warnings-log-level*


The log level used to log Lisp warnings. See also *LOG-LISP-WARNINGS-P*. The default value is :WARNING.


[Special variable]
*log-lisp-backtraces-p*


Whether backtraces should also be logged in addition to error messages and warnings. This value will only have effect if *LOG-LISP-ERRORS-P* or *LOG-LISP-WARNINGS-P* is true. Note that logging of backtraces is currently only supported for CMUCL, SBCL, AllegroCL, and LispWorks. The default value is NIL.


[Special variable]
*log-prefix*


All messages written to the Apache error log by TBNL are prepended by a string which is the value of this variable enclosed in square brackets. If the value is NIL, however, no such prefix will be written. If the value is T (which is the default) the prefix will be "[TBNL]".


[Special variable]
*show-lisp-errors-p*


Whether unhandled Lisp errors should be shown to the user. If this value is NIL (which is the default) only the message An error has occurred will be shown.


[Special variable]
*show-lisp-backtraces-p*


Whether backtraces should also be shown to the user. This value will only have effect if *SHOW-LISP-ERRORS-P* is true. Note that the display of backtraces is currently only supported for CMUCL, SBCL, AllegroCL, and LispWorks. The default value is NIL.


[Special variable]
*show-access-log-messages*


If this variable is true and if *USE-APACHE-LOG* is NIL then for each request a line somewhat similar to what can be found in Apache's access log will be written to the log file. The default value of this variable is T.


[Special variable]
*http-error-handler*


This variable holds NIL (the default) or a function designator for a function of one argument. The function gets called if the responsible handler has set a return code other than +HTTP-OK+ or +HTTP-NOT-MODIFIED+ and receives this return code as its argument. It can return the contents of an error page or NIL if it refuses to handle the error, i.e. if TBNL's default error page should be shown. (Note that the function can access the request and reply data.)


[Function]
get-backtrace condition => backtrace


This is the function that is used internally by TBNL to show or log backtraces. It accepts a condition object condition and returns a string with the corresponding backtrace.

Miscellaneous

Various functions and variables which didn't fit into one of the other categories.


[Function]
start-tbnl => listener


This will start TBNL by creating a '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' (see START-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 is 3000.


[Symbol]
tbnl-handler-done


This is a catch tag which names a catch which is active during the lifetime of a handler. The handler can at any time throw the outgoing content body (or NIL) to this catch to immediately abort handling the request. See the source code of REDIRECT for an example.


[Function]
no-cache => |


This function will set appropriate outgoing headers to completely prevent caching on virtually all browsers.


[Function]
handle-if-modified-since time => |


This function is designed to be used inside a handler. If the client has sent an 'If-Modified-Since' header (see RFC 2616, section 14.25) and the time specified matches the universal time time then the header +HTTP-NOT-MODIFIED+ with no content is immediately returned to the client.

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 time time (default is the current time) and returns a string which encodes this time according to RFC 1123. This can be used to send a 'Last-Modified' header - see HANDLE-IF-MODIFIED-SINCE.


[Function]
redirect script-name &key host protocol add-session-id permanently => |


Sends back appropriate headers to redirect the client to the resource script-name on the host host. If permanently is true (the default is NIL) a 301 status code will be sent, otherwise a 302 status code. If host is not provided the current host (see HOST) will be used. If protocol is the keyword :HTTPS the client will be redirected to a https URL, if it's :HTTP it'll be sent to a http URL. If both host and protocol aren't provided then the value of protocol will match the current request.


[Function]
require-authorization &optional realm => |


Sends back appropriate headers to require basic http authentication (see RFC 2617) for the realm realm. The default value vor realm is "TBNL".


[Function]
escape-for-html string => escaped-string


Escapes all occurrences of the characters #\<, #\>, #\', #", and #\& within string for HTML output.


[Function]
url-encode string &optional external-format => url-encoded-string


URL-encodes a string using the external format external-format. The default for external-format is the value of *TBNL-DEFAULT-EXTERNAL-FORMAT*.


[Function]
url-decode string &optional external-format => url-encoded-string


URL-decodes a string using the external format external-format, i.e. this is the inverse of URL-ENCODE. It is assumed that you'll rarely need this function, if ever. But just in case here it is. The default for external-format is the value of *TBNL-DEFAULT-EXTERNAL-FORMAT*.


[Function]
read-from-string* string => object, position


This is like READ-FROM-STRING but *READ-EVAL* is bound to NIL and *PACKAGE* is bound to the package named TBNL-DUMMY which exists solely for this purpose.


[Function]
http-token-p object => generalized-boolean


This function tests whether object is a non-empty string which is a token according to RFC 2068 (i.e. whether it may be used for, say, cookie names).


[Special variable]
*tmp-directory*


This should be a pathname denoting a directory where temporary files can be stored. It is used for file uploads.


[Special variable]
*tbnl-default-external-format*


The external format used when computing the REQUEST object. The default value is +LATIN-1+.


[Constants]
+latin-1+
+utf-8+


Constants representing the corresponding external formats in some supported implementations. (As of version 0.8.0 these are LispWorks, AllegroCL, and SBCL with Unicode support.)

Debugging TBNL applications

The best option to debug a TBNL application is probably to use the debugger.

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 is NIL (the default is T), 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 is NIL), then on each request the global values of the special variables *REQUEST*, *REPLY*, *BODY*, *COMMAND*, *BACKTRACE*, and *ERROR* will be set accordingly, i.e. you'll have access to them after the request has been handled.


[Special variable]
*backtrace*


If *DEBUG-MODE* is true this variable holds the backtrace (as a string) of the last error or warning that was handled by TBNL. Note that capturing of backtraces is currently only supported for CMUCL, SBCL, and LispWorks.


[Special variable]
*body*


If *DEBUG-MODE* is true this variable holds the last content body that was sent by TBNL.


[Special variable]
*error*


If *DEBUG-MODE* is true this variable holds the last condition that was handled by TBNL.


[Special variable]
*command*


If *DEBUG-MODE* is true this variable holds the last input received from the front-end or the client. This is an alist of keys and values the details of which depend on the front-end.

 

Known quirks

Here are a couple of things to watch out for:
 

History

TBNL (which is short for "To Be Named Later") grew over the years as a toolkit that I used for various commercial and private projects. In August 2003 Daniel Barlow started a review of web APIs on the lispweb mailing list and I described the API of my hitherto-unreleased bunch of code (and christened it "TBNL").

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.
 

Acknowledgements

Thanks to Jeff Caldwell - TBNL would not have been released without his efforts. Thanks to Marc Battyani for mod_lisp and to Chris Hanson for mod_lisp2. Thanks to Stefan Scholl for various additions and fixes, to Michael Weber for initial file upload code, and to Janis Dzerins for his RFC 2388 code. Thanks to Bob Hutchison for his code for multiple front-ends and the UTF-8 example.

$Header: /usr/local/cvsrep/tbnl/doc/index.html,v 1.146 2006/10/09 23:46:41 edi Exp $

BACK TO MY HOMEPAGE