Author

Requirements

Documentation

Spiffy is a web-server library for the Chicken Scheme system. It's quite easy to set up and use (whether as a library or a standalone server application) and it can be customized in numerous ways.

Starting the server

[procedure](start-server [port: port-number])

Starts the server, to listen on the given port. Other configuration can be tweaked through SRFI-39 parameters. These are listed below. Once the server is started, server behaviour can be controlled through these parameters as well. By default, Spiffy will only serve static files. On directories, it will give a "403 forbidden", unless there is an index-file. If there is, that file's contents will be shown.

Configuration parameters

The following parameters can be used to control spiffy's behaviour. Besides these parameters, you can also influence spiffy's behaviour by tweaking the intarweb parameters.

[parameter](server-software [product])

The server software product description. This should be a valid product value as used in the server and user-agent headers by intarweb; this is a list of lists. The inner lists contain the product name, the product version and a comment, all either a string or #f. Default: (("Spiffy" "a.b" "Running on Chicken x.y")), with a.b being the Spiffy major/minor version and x.y being Chicken's.

[parameter](root-path [path])

The path to the document root, for the current vhost. Defaults to "./web".

[parameter](server-port [port-number])

The port number on which to listen. Defaults to 8080.

[parameter](max-connections [number])

The maximum number of simultaneously active connections. Defaults to 1024.

Any new connection that comes in when this number is reached must wait until one of the active connections is closed.

[parameter](spiffy-user [name-or-uid])

The name or UID of a user to switch to just after binding the port. This only works if you start Spiffy as root, so it can bind port 80 and then drop privileges. If #f, no switch will occur. Defaults to #f.

[parameter](spiffy-group [name-or-gid])

The name or GID of a group to switch to just after binding the port. This only works if you start Spiffy as root, so it can bind port 80 and then drop privileges. If #f, it will be set to the primary group of spiffy-user if the user was selected. Otherwise, no change will occur. Defaults to #f.

[parameter](index-files [file-list])

A list of filenames which are to be used as index files to serve when the requested URL identifies a directory. Defaults to '("index.html" "index.xhtml")

[parameter](mime-type-map [extension->mimetype-list])

An alist of extensions (strings) to mime-types (symbols), to use for the content-type header when serving up a static file. Defaults to

The mime-type (a symbol) to use if none was found in the mime-type-map. Defaults to 'application/octet-stream

[parameter](default-host [hostname])

The host name to use when no virtual host could be determined from the request. See the section on virtual hosts below.

[parameter](vhost-map [host-regex->vhost-handler])

A mapping of virtual hosts (regex) to handlers (procedures of one argument; a continuation thunk). See the section on virtual hosts below. Defaults to `((".*" . ,(lambda (continue) (continue))))

[parameter](file-extension-handlers [extension->handler-list])

An alist mapping file extensions (strings) to handler procedures (lambdas of one argument; the file name relative to the webroot). Defaults to '(). If no handler was found, defaults to just sending a static file.

The name of an access file, or #f if not applicable. This file is read when the directory is entered by the directory traversal system, and allows you to write dynamic handlers that can assign new values for parameters only for resources below that directory, very much like adding parameters in code before calling a procedure. See the section "Access files" for more information.

Handlers

Besides "static" configuration, Spiffy also has several handlers for when something is to be served.

[parameter](handle-directory [proc])

The handler for directory entries. If the requested URL points to a directory which has no index file, this handler is invoked. It is a procedure of one argument, the path (a string) relative to the webroot. Defaults to a procedure which returns a "403 forbidden".

[parameter](handle-file [proc])

The handler for files. If the requested URL points to a file, this handler is invoked to serve the file. It is a procedure of one argument, the path (a string) relative to the webroot. Defaults to a procedure which sets the content-type and determines a handler based on the file-extension-handlers, or send-static-file if none was found.

[parameter](handle-not-found [proc])

The handler for nonexisting files. If the requested URL does not point to an existing file or directory, this procedure is called. It is a procedure of one argument, the path (a string) that was requested. This path should be interpreted as being relative to the webroot (even though it points to no existing file). Defaults to a procedure which returns a "404 Not found".

[parameter](handle-exception [proc])

The handler for when an exception occurs. This defaults to a procedure that logs the error to the error log. While debugging or developing, it may be more convenient to use a procedure that sends the error back to the client:

Runtime information

During the handling of a request, Spiffy adds more information to the environment by parameterizing the following parameters whenever the information becomes available:

[parameter](current-request [request])

An intarweb request-object that defines the current request. Available from the moment the request comes in and is parsed. Contains, among other things, the query parameters and the request-headers, in fully parsed form (as intarweb returns them).

[parameter](current-response [response])

An intarweb response-object that defines the current response. Available from the same time current-request is available. This keeps getting updated along the way, while the response data is being refined (like when headers are being added).

[parameter](current-file [path])

The path to the requested file (a string). Available from the moment Spiffy determined the requested URL points to a file (just before the handle-file procedure is called). This file is relative to the root-path.

[parameter](current-pathinfo [path])

The trailing path fragments (a list of strings) that were passed in the URL after the requested filename. Available from the moment Spiffy determined the requested URL points to a file (just before the handle-file procedure is called).

[parameter](remote-address [address])

The IP address (a string) of the user-agent performing the current request.

[parameter](local-address [address])

The IP address (a string) on which the current request came in.

Virtual hosts

Spiffy has support for virtual hosting, using the HTTP/1.1 Host header. This allows you to use one Spiffy instance running on one IP address/port number to serve multiple webpages, as determined by the hostname that was requested.

The virtual host is defined by a procedure, which can set arbitrary parameters on-the-fly. It is passed a continuation thunk, which it should explicitly call if it wants the processing to continue. The most used parameter in virtual host setups is the root-path parameter, so that another docroot can be selected based on the requested hostname, showing different websites for different hosts:

In this example, if a client accesses foo.bar.com/mumble/blah.html, the file /var/www/domains/foo.bar.com/mumble/blah.html will be served. Any files ending in .ssp or .ws will be served by the corresponding file type handler. If there's any PHP file, its source will simply be displayed. In case of my.domain.com/something/bar.html, the file /var/www/domains/domain.com/something/bar.html will be served. If there's a .ssp or .ws file there, it will not be interpreted. Its source will be displayed instead. A .php file, on the other hand, will be passed via CGI to the program /usr/pkg/bin/php.

Domain names are mapped to a lambda that sets up any parameters it wants to override from the defaults. The host names are matched using string-match. If the host name is not yet a regexp, it will be converted to a case-insensitive regexp.

Access files

Fine-grained access-control can be implemented by using so-called access files. When a request for a specific file is made and a file with the name given in the access-file parameter exists in any directory between the root-dir of that vhost and the directory in which the file resides, then the access file is loaded as an s-expression containing a function and is evaluated with a single argument, the function that should be called to continue processing the request.

This works just like vhosting. The function that gets called can call parameterize to set additional constraints on the code that handles deeper directories.

For example, if we evaluate (access-file ".access") before starting the server, and we put the following code in a file named .access into the root-directory, then all accesses from localhost to any file in the root-directory or any subdirectory will be denied:

Of course, access files can be used for much more than just access checks. One can put anything in them that could be put in vhost configuration or in top-level configuration.

They are very useful for making deployable web applications, so you can just drop a directory on your server which has its own configuration embedded in an access file in the root directory of the application, without having to edit the server's main configuration files.

Procedures and macros

The following procedures and macros can be used in dynamic web programs, or dynamic server configuration:

[procedure](with-headers new-headers thunk)

Call thunk with the header list new-headers. This parameterizes the current response to contain the new headers. The existing headers are extended with new-headers through intarweb's headers procedure.

[procedure](write-logged-response)

This procedure simply writes current-response after calling handle-access-logging. Responses should always go through this procedure instead of directly using write-response from intarweb.

[procedure](log-to log format . rest)

Write a printf-style format string to the specified log (one of access-log, error-log or debug-log). format is a printf-style format string, and rest arguments should match the arguments one would pass to printf. A newline is appended to the end of the log message automatically.

[procedure](send-status code reason [message])

Easy way to send a page and a status code to the client. The optional message is a string containing HTML to add in the body of the response. Example:

Send a file to the client. This sets the content-length header and tries to send the file as quickly as possible to the client. The filename is interpreted relative to root-path.

[procedure](restart-request request)

Restart the entire request-handling starting at the point where the request was just parsed. The argument is the new request to use. Be careful, this makes it very easy to introduce unwanted endless loops!

[procedure](htmlize string) => string

Encode "special" html symbols like tag and attribute characters so they will not be interpreted by the browser.

[procedure](build-error-message exn chain [raw-output])

Build an error message for the exception exn, with call chain chain. Defaults to HTML output, unless raw-output is given and nonfalse.

[procedure](server-root-uri)

An absolute URI (uri-common object) which can be used as a base for relative references (with absolute paths). It includes the scheme, current vhost, port and a root path of "/".

Modules

This section will describe what the various modules that come with Spiffy are and how they work.

ssp-handler

SSP, or Scheme Server Pages, are a way to embed Scheme in HTML pages. Files with an extension of .ssp are handled specifically, by replacing occurrences of certain reserved tags with Scheme code. There are two possible forms, either the long version, where all output is redirected to the HTTP response port, or the short, where the result of the embedded expression is displayed in the response. The tags default to <?scheme and <?, see Configuration for how to change them.

When a .ssp file is loaded the first time, or when it has been modified, then a translation takes place that generates a loadable Scheme source file (with the extension .sspx, in the same directory as the original file) from the original data, so in the above example something like this would be generated:

Runtime information

For the duration of evaluating a SSP page, the following parameters will have a value assigned to them:

[parameter](current-workdir [path])

During execution, the current working directory of the SSP handler. Any of the "include" procedures (ssp-include, ssp-stringize) will interpret their file arguments to be relative to this directory.

[parameter](ssp-exit-handler [handler])

During execution of an ssp page, ssp-exit-handler is bound to a procedure that will finish the current page, ignoring any further content or code.

Procedures

The ssp-handler module adds the following procedures to the environment:

[procedure](ssp-handler filename)

The handler itself, which should be used in the file-extension-handlers parameter list.

[procedure](ssp-include filename)

Translates the file filename into Scheme by replacing <?scheme ... ?> and <? ... ?> sequences (if needed) and writes the translated contents to the current output-port.

[procedure](ssp-stringize FILENAME)

Similar to ssp-include, but instead of writing the translated text, the text is returned as a string.

web-scheme-handler

Another way of executing Scheme code to produce content are .ws files: these should contain a Scheme expression that is expected to evaluate to a string which will be directly written as the response to the current request. This facility is intended for Scheme code that uses the web-scheme extension.

You can use the web-scheme-handler for any Scheme file which returns HTML as a string or which has a side-effect of outputting the HTML. If it's the latter, make sure the final statement in your file does not return a string or it will be appended to the output (just like in the csi REPL).

Tip This handler type is perfect not only for web-scheme but also for when you're using SRV:send-reply with SXML or for example a wiki-to-string translator.

Note: each request runs in a separate thread, so code in .ws pages should take care when using global variables.

Note: web-scheme-handler is a separate extension and must be imported as such.

cgi-handler

All request headers will be passed as environment variables to the CGI program, prefixed with "HTTP_", and converted to uppercase, with hyphens ("-") replaced by an underscore ("_"). The CGI program will receive the request body in unparsed form from stdin and should write a complete HTTP response to stdout. Any headers that are missing but required for HTTP will be added by Spiffy. For more info on how a CGI script is called, consult the spec.

The AUTH_TYPE and REMOTE_USER environment variables are currently not set during invocation of CGI subprocesses. The REMOTE_IDENT environment variable is not and never will be supported.

Configuration

CGI handler can be configured with the following parameters:

[procedure](cgi-default-environment [env-alist])

The environment variables that should be in the default environnment of every CGI program. Variables like SCRIPT_NAME will be added dynamically to the end of this alist.

Default:

(("GATEWAY_INTERFACE" . "CGI/1.1"))

Procedures

CGI-handler adds two procedures to the environment:

[procedure](cgi-handler filename [interpreter])

The cgi handler simply calls CGI scripts. It is assumed the requested file is executable if no interpreter is given. (If used as a regular handler, it will only receive the filename).

[procedure](cgi-handler* [interpreter])

The cgi-handler* procedure is usually more useful. It allows you to define an interpreter to use for files and returns a new handler. See the example above for file-extension-handlers.

simple-directory-handler

In order to get directory listings, you can use simple-directory-handler. Just assign the simple-directory-handler to handle-directory and you're set.

Configuration

The simple directory handler has a few configuration options:

[procedure](simple-directory-dotfiles? [dotfiles?])

Determines if dotfiles should show up in the directory listings. Default: #f

[procedure](simple-directory-display-file [displayer])

A lambda that accepts three arguments: the remote filename, the local filename and a boolean that says if the file is a directory. This lambda should output a table row with the desired information. Defaults to a lambda that prints the name, size and date when the file was last modified.

Procedures

The simple-directory handler adds only one procedure to the environment:

[procedure](simple-directory-handler pathname)

The handler itself, which should be used in the handle-directory parameter.

Changelog

License

Copyright (c) 2005-2008, Felix L. Winkelmann and Peter Bex
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
Neither the name of the author nor the names of its contributors may
be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.