You are looking at historical revision 4155 of this page. It may differ significantly from its current revision.
Spiffy
Description
A small web-server written in Chicken.
Author
Felix L. Winkelmann. Currently maintained by Peter Bex.
Requirements
Requires the http and regex-case extensions.
Download
Documentation
Spiffy is a simple web-server for the Chicken Scheme system. It's quite easy to set up and use and it can be customized in numerous ways.
Note that only very basic functionality is provided, there is no support for encoding styles.
(start-server #!key root port accept name location user debug init)
Starts the web server. The arguments provided default to configuration variables explained below, following this pattern:
(start-server #!key
(root spiffy-root-path)
(port spiffy-tcp-port)
(accept spiffy-accept-incoming-host)
(name spiffy-server-name)
(location (get-host-name))
(user spiffy-user-name)
(debug spiffy-debug-mode)
(init noop) )
After startup the current directory is changed to the path given in the ROOT argument. If USER is specified, then (after creating a network connection to the specified port) the current user is changed to the one given, with groups and the $HOME directory set accordingly. The INIT argument should be a procedure of zero arguments. This procedure will be called after setting up the connection and changing the user ID.
Note that there is currently no way of terminating the server manually, but terminating the whole process.
The following variables determine configuration of Spiffy:
- spiffy-tcp-port (integer)
- The port (as in the --port option). Default: 8080
- spiffy-debug-mode (boolean)
- Whether debugging information should be printed. Default: #f
- spiffy-log-file (string)
- File to append log output to. Default: #f (disabled)
- spiffy-error-port (port)
- Port to which to write error messages from evaluated code to. Default: (current-error-port)
- spiffy-eval-environment (environment)
- Environment in which to evaluate dynamically loaded Scheme code. Default: (interaction-environment)
- spiffy-server-name (string)
- The name of the server as given in HTTP responses. Default: (Silly)
- spiffy-accept-incoming-host (string)
- If given, restricts incoming requests to this hostname. Default: #f
- spiffy-user-name (string)
- If given, changes to given user after setting up listening port. Default: #f
- spiffy-root-path (string)
- The path containing the served files. Default: ./web
- spiffy-deny-access (regexp)
- Any file matching this regular expression results in a "Forbidden" reply. Default: ^.|/.|.sspx$
- spiffy-deny-paths (list of regexps)
- Any path containing any of this regular expressions will also be denied. Default: '(".." "~")
- spiffy-vhost-map (alist of string<->string pairs)
- Maps hosts to basepaths of virtual servers. Default: #f
- spiffy-index-pages (list of strings)
- List of filenames that should be tried in case a request references a directory. Default: '("index.html" "index.ssp" "index.sxml" "index.ws")
- spiffy-file-type-map (alist of string <-> symbol pairs)
- Maps file-extensions to MIME types. Default:
'(("txt" . text/plain)
("xml" . text/xml)
("xul" . application/vnd.mozilla.xul+xml)
("htm" . text/html)
("html" . text/html)
("pdf" . application/pdf)
("css" . text/css)
("bmp" . image/bmp)
("ico" . image/x-icon)
("gif" . image/gif)
("jpg" . image/jpeg)
("jpeg" . image/jpeg)
("png" . image/png))
- spiffy-read-block-size (integer)
- The size of chunks in which files should be loaded and transferred to the client. Default: 100000
- spiffy-cache-limit (integer)
- The maximum size of the cache for static file-based resources. Default: 0
- spiffy-access-file (filename)
- The name of directory-local access-permission files. Default: #f
- spiffy-config-file (filename)
- The name of the configuration file to load at startup or SIGHUP. Default: #f
Modules
Starting at version 3.0, Spiffy's functionality has become large enough to split out into modules. Currently it has modules for:
- Web-scheme
- Web-scheme, a functional web programming language on top of Scheme.
- CGI
- An implementation of the Common Gateway Interface, version 1.1. Use this to run legacy PHP scripts, if you have them.
- SSP (Scheme Server Pages)
- This is a language inspired by PHP, Embedded Ruby or ASP. You just type your HTML with bits of code sprinkled through it delimited by special "code tags".
You can run a module simply by adding a <scheme>use</scheme> statement to your program and then hooking a file extension handler for that module's handler. Here's an example on how to do this:
<scheme> (add-file-ext-handlers! `(("ssp" . ,ssp-handler)
("ws" . ,web-scheme-handler) ("php" . ,(cgi-handler* "/usr/pkg/libexec/cgi-bin/php"))))
</scheme>
Dynamic reconfiguration
Sending a SIGHUP to a Spiffy instance causes it to re-read its configuration file.
Generating web-pages dynamically
- Add Resources by invoking the procedure http:add-resource. Procedures can be registered that are invoked when a request refers to a given path. See the documentation for the HTTP extension library Chicken.
(http:add-resource
"/index.html"
(lambda (request args)
(http:write-response-header)
(let ([name (car (user-information (current-user-id)))])
(let ([s (sprintf "<h1>Hello, ~A</h1>" name)])
(printf "Content-length: ~A\r\nContent-type: text/html\r\n\r\n~A"
(string-length s) s) ) ) ) )
- 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.
<html><body> <ol><?scheme (for-each (lambda (i) (printf "<li>~S~%" i)) (iota 5))?></ol> <br /> <b><?(call-with-values (lambda () (user-information (current-user-id))) (lambda (name . _) name))?><b> </body></html>
would generate for example something like this:
1. 0 2. 1 3. 2 4. 3 5. 4 (felix x 500 100 /home/felix /bin/bash)
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:
(let ()
(display "<html><body>\n<ol>")
(for-each (lambda (i) (printf "<li>~S~%" i)) (iota 5))
(display "</ol>\n<br />\n<b>")
(display (call-with-values (lambda () (user-information (current-user-id))) (lambda (name . _) name)))
(display "<b>\n</body></html>\n") )
Note that the body is evaluated in a (let () ...) form.
Yet 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 .ws 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). This is needed because .ssp files always generate a .sspx file. If your complete .ssp file is contained within spiffy-open-tag and spiffy-close-tag, you don't need or want this. In those cases, use .ws extensions.
- During execution of .ws or .ssp files, the following parameters are available, which hold some useful data for the currently handled request:
- (current-request)
- Holds an object representing the current HTTP request. See HTTP for more information.
- (current-urlencoded-arguments)
- Holds an a-list containing pairs of strings, which contains URL-encoded arguments. So a request for /foo.html?bar=baz would result in this parameter containing (("bar" . "baz")).
- (current-response-code)
- A pair containing the status code and the status message sent in the reply. Defaults to (200 . "OK").
- (current-response-headers)
- An a-list holding the HTTP headers which are sent before the body of the current response. By default this parameter contains (("content-type" . "text/html")). You can modify the existing value, or assign a different one by calling the parameter with a new a-list.
Note: each request runs in a separate thread, so code in .ssp pages should take care when using global variables.
Spiffy supports a subset of the CGI/1.1 spec. All request headers will be passed as environment variables to the CGI program (which must be executable and match spiffy-cgi-pattern), prefixed with HTTP_, and converted to uppercase, with hyphens ("-") replaced by an underscore ("_"). The CGI program will receiver the request body in unparsed form from stdin and should write a complete HTTP response to stdout.
The following environment variables are currently not set during invocation of CGI subprocesses:
AUTH_TYPE PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER
Content transporters
The content of a served file is read and transmitted using a content-transporter procedure. The default content-transporter reads in the data and forwards it to the client with appropriate Content-length and Content-type headers.
User-defined content-transporters allow customized retrieval and processing of resources, depending on file-type.
- (content-transporter TYPE [PROC])
- If called with a single argument, this procedure returns the content-transporter procedure for the given TYPE, which should be a symbol like text/html. If the optional argument PROC is given, then the content-transporter for the given type is set to PROC. PROC should accept four arguments: the type TYPE, the filename of the requested resource, the current request object and the list of URL-encoded arguments. Any output generated by PROC is transmitted to the client.
Access to the cache
- (cache-read PATH)
- Reads a string from the cache, stored under the string PATH and returns it if found, or #f otherwise.
- (cache-write PATH STRING)
- Writes a string into the cache, stored under PATH. If the cache-limit is not sufficient (see spiffy-cache-limit the data is not stored.
Note that the cache is by default not enabled (spiffy-cache-limit is 0).
- (invalidate-cache [PATH])
- If the optional argument PATH is given, remove the associated item from the cache. If no argument is given, clear the whole cache.
Logging
If logging is enabled by setting the parameter spiffy-log-file to the pathname of a log-file, then each request will be logged by appending a line of the format
CLIENT-IP DATE-AND-TIME REQUEST-LINE USER-AGENT
Virtual hosts
You can set up simple virtual hosting by defining a vhost-map like so:
(vhost-map `((,(regexp "foo\\.bar\\.com" #t) "foobar")
(,(regexp (glob->regexp "*.domain.com") #t) "example")))
In this example, if a client accesses foo.bar.com/mumble/blah.html, the file foobar/mumble/blah.html will be served. In case of my.domain.com/something/bar.html, the file example/something/bar.html will be served. These domain names are mapped to a directory that serves as rootdir for the virtual host. The host names are matched using string-match, which is case-sensitive by default; hence the call to regexp.
This is a HTTP/1.1 feature, so this will only work with HTTP/1.1 clients It also works with HTTP/1.0 clients which insert an additional Host: line in the request headers.
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 spiffy-access-file parameter exists in the same directory, then the access file is loaded as an s-expression containing a function and is evaluated with a single argument, the http:request object for the current request. The procedure should return #f if the access is to be denied.
For example, if we evaluate (spiffy-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 (but not sub-directories!) will be denied:
(lambda (r) (not (string=? (http:request-ip r) "127.0.0.1")))
Some useful procedures
- (write-fragment-response REQUEST FRAGMENTS [CODE MESSAGE [ALIST [PORT]]])
- Generates a HTTP response and Writes the elements of the list FRAGMENTS as the contents of the body. A fragment may be a #f or the empty list (which are ignored), a string, character, number, symbol or a list of other fragments.
- (redirect PATH)
- Changes the current value of (current-response-code) and (current-response-headers) to return a redirection response. This is basically the same as redirect-page but is usable in .ssp pages.
- (redirect-page REQUEST PATH)
- Generates a 303 See Other HTTP response that redirects the client to PATH.
- (spiffy-debug FORMATSTRING ARG ...)
- When debugging output has been turned on (via the debug argument to start-server, or by (spiffy-debug-mode #t)), then this is equivalent to (fprintf (current-error-port) FORMATSTRING ARG ...).
- (write-response-header REQUEST [CODE MESSAGE [ALIST [PORT [PROTOCOL]]]])
- Writes a HTTP response header. This largely equivalent to http:write-response-header, but handles persistent connections properly by checking the HTTP version any Connection request headers.
- (current-workdir)
- A parameter that holds the current include path. File loading operations like load-once, load-ssp or include-ssp set this parameter, so that nested load/include operations always take place relative the the containing source file.
- (include-ssp FILENAME)
- Translates the file FILENAME into Scheme by replacing <?scheme ... ?> and <? ... ?> sequences (if needed) and writes the translated contents to the current output-port.
- (ssp-stringize FILENAME)
- Similar to include-ssp, but instead of writing the translated text, the text is returned as a string.
- (htmlize STRING)
- Makes STRING suitable for embedding in HTML by replacing <, >, & and " with <, >, & and ".
- (htmlize-with-spaces STRING)
- Makes STRING suitable for embedding in HTML similar to htmlize, but also replaces spaces with entities and newlines with <br />.
- (strip-tags STRING)
- Returns STRING with HTML tags removed.
- (escape-chars STRING)
- Transforms non-alphanumeric characters in STRING into %XX characters sequences.
- (load-once FILENAME)
- If no file with this name has been loaded, or if the file has been modified since the last time it has been loaded, then it is loaded and evaluated and #t is returned. Otherwise nothing is done and load-once returns #f.
Note that this works only reliable for source files, not for compiled code.
- (get-header STRING [DEFAULT])
- Retrieves the value of a HTTP header in the current request. STRING should name the header and will be compared (case-insensitive) with any headers passed in the current request object (as given in (current-request)). If no header with this name can be found DEFAULT will be returned (or #f if DEFAULT is not specified).
- (set-header! STRING)
- Sets a HTTP header in the current response by changing the value of the current-response-headers parameter. STRING should be a valid HTTP header of the form <header-name>: <header-value>. If the header was already set it will be overwritten except in the case of Set-Cookie. In this case, it will just output an additional Set-Cookie header.
- (exit)
- During execution of a .ssp page, the current exit-handler is bound to a procedure that will finish the current page, ignoring any further content or code.
SXML operations
- (write-sxml SXML [PORT])
- Transforms the s-expression SXML into HTML, writing the translation into PORT. PORT defaults to the value of (current-output-port).
- (write-sxml-response REQUEST SXML [CODE MESSAGE [ALIST [PORT]]])
- Generates a HTTP response with the SXML as content (via write-sxml).
Convenience macros
- (syntax) (define-http-resource (URL ARGUMENT ...) BODY ...)
- Defines a resource-handler (as with http:add-resource) for URL, which should be a string or symbol. the ARGUMENTs should be symbols, which will be bound to URL-encoded or POSTed arguments or they should be of the form (IDENTIFIER DEFAULT) to bind them to a default value, if not provided. The result of BODY ... will be returned as the response, it should be a string containing HTML or an SXML expression (which will be converted to (X)HTML).
To exit prematurly from the body, or to customize the returned response, the procedure respond will be lexically visible inside the body, and can be called like this:
(respond RESULT #!key (code 200) (description "OK") (headers '()) (type "text/html"))
Calling (respond RESULT) is identical to returning RESULT in the body.
Changelog
- 2.43 Program name was wrongly passed as argument to CGI subprocess.
- 2.42 Fix small typo (thanks to Mario Domenech Goulart)
- 2.41 Fixed bug in webscheme where any output would be injected in the HTTP response. .ws files can now be used for any pure scheme code to be evaled, as an alternative to .ssp. Fixed generate-directory-listing as per ticket #15.
- 2.40 write-sxml generates improved output [Thanks to Peter Wright]
- 2.39 Renamed sxml->html to write-sxml (kept the old name for backwards compatibility), also changed newline behaviour subtly [thanks to Peter Wright and Zbigniew Szadkowski]
- 2.38 CGI "Status" header is recognized now
- 2.37 Added handling for .htm extension [Thanks to Mario Goulart]
- 2.36 Added spiffy-eval-environment and more error output
- 2.35 Added spiffy-error-port [suggested by Daishi Kato]
- 2.34 Bugfix in load-web-scheme by Mario Domenech Goulart
- 2.33 More complete CGI support (needs http 1.47)
- 2.32 Preliminary CGI support
- 2.31 Disabled keep-alive connections on MSVC until its problems have been investigated [Suggested by Graham Fawcett]
- 2.3 Disabled signal-handler setup on MSVC [Thanks to Graham Fawcett]
- 2.2 htmlize shouldn't have replaced newlines with br tag
- 2.1 Didn't cd to root dir on startup
- 2.0 Support dynamic reconfiguration [by Peter Bex]
- 1.53 Supports default "ws" page and small bugfix [Thanks to Mario Domenech Goulart]
- 1.52 Add virtual hosting support [by Peter Bex]
- 1.51 set-header! uses different regexp for non-pregexp platforms
- 1.50 Direct loading of Scheme source code files with the ".ws" extension [by Mario Goulart]
- 1.49 Yet another bugfix by Hans Bulfone
- 1.48 Better error-output
- 1.47 Removed SIGPIPE handler (is done by tcp unit); added pregexp-compatibility hack [by Daishi Kato]
- 1.46 Adapted to SRFI-69 compatible hash-tables
- 1.45 The code in .ssp pages is evaluated in a lexically scoped body; added files beginning with a dot to files denied by default; added access-files
- 1.44 define-http-resource automatically prefixes url with / if needed
- 1.43 define-http-resource didn't pass headers and code back to responder [Thanks to Michele Simionato]
- 1.42 define-http-resource binds some important parameters [Thanks to Michele Simionato]
- 1.41 Link in directory listing to parent directory was broken [Thanks to Guy Banay]
- 1.40 Added define-http-resource and reorganized code a tiny bit
- 1.39 Serving files with no extension failed
- 1.38 File-extension mapping was broken (argh)
- 1.37 File-extension mapping is case-insensitive [Thanks to Peter Bex]
- 1.36 Peter Bex added htmlize-with-spaces.
- 1.35 Peter Bex added current-workdir and relative load/include paths
- 1.34 Fixed a bug in load-scheme (uncaught exceptions triggered an unbound variable error)
- 1.33 Directory-listing ignores broken links and displays footer; {{spiffy-program-pattern} is matched with full pathname
- 1.32 Some bugfixes and cosmectic changes
- 1.31 Removed spiffy-program-path and spiffy-log
- 1.30 Spiffy is now a library; removed all command-line processing and spiffy-monitor; added start-server and support for exit in .ssp pages; directory listings; programs (spiffy-program-path and spiffy-program-pattern); get-header
- 1.29 translate-file opened input-file twice; the default location is set to the result of get-host-name; set-header! used broken regular expression; errors during redirection are properly caught [Thanks to Peter Bex]
- 1.28 The fallback page-handler didn't work with relative root-paths [Thanks to Gian Paolo Ciceri]
- 1.27 Added optional argument to set-header!, removed load-ssp and added include-ssp and ssp-stringize [Thanks to Peter Bex]
- 1.26 Added current-response-code and redirect (and renamed the old one to redirect-page). set-header! handles spaces in the header value [Thanks to Peter Bex]
- 1.25 Changing to root path was done too early
- 1.24 Fixed documentation; added text/css to list of content-types; on startup current directory is changed to root path; changing to user sets supplementary groups [Thanks to Peter Bex]
- 1.22 Changed "root" directory to "web"
- 1.21 argument needs no default value; set-header! wasn't exported
- 1.20 Removed sill startup message, spiffy-monitor uses HEAD request, added several new helper procedures
- 1.19 preliminary HEAD support
- 1.18 spiffy-log was broken
- 1.17 changed .shp and .shp.2 extensions to .ssp and .sspx, respectively; several new API routines and improvements
- 1.16 Updated for http extension (version 1.11), added support for restriction of incoming requests and changing user ID.
- 1.15 fixed bug in startup code for daemonized spiffy
- 1.14 reply protocol uses the one given in request
- 1.13
License:
Copyright (c) 2005, Felix L. Winkelmann 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.