source: project/wiki/eggref/5/spiffy @ 36279

Last change on this file since 36279 was 36279, checked in by sjamaan, 11 months ago

Spiffy: Document version 6.1

File size: 36.6 KB
Line 
1[[tags: egg]]
2
3== Spiffy
4
5[[toc:]]
6
7=== Description
8
9A small web-server written in [[http://www.call-cc.org|Chicken]].
10
11=== Author
12
13[[/users/felix-winkelmann|Felix Winkelmann]].
14Currently maintained by [[/users/peter-bex|Peter Bex]].
15
16=== Requirements
17
18Requires the following extensions:
19
20* [[intarweb]]
21* [[uri-common]]
22* [[uri-generic]]
23* [[sendfile]]
24* [[srfi-1]]
25* [[srfi-13]]
26* [[srfi-14]]
27* [[srfi-18]]
28
29There are some optional dependencies too:
30
31* To support HTTPS, the [[openssl]] egg must be installed.
32* When you want to bind to a privileged port (like 80), Spiffy can drop privileges after binding, which requires you to install the [[posix-groups]] egg.
33
34=== Documentation
35
36Spiffy is a web-server library for the Chicken Scheme system. It's
37quite easy to set up and use (whether as a library or a standalone
38server application) and it can be customized in numerous ways.
39
40To test it out immediately, try the following command after installing:
41
42  $ csi -e "(import spiffy) (start-server)"
43
44This starts up a spiffy server which listens on port 8080, and serves
45documents from the default docroot, which is a directory called {{web}}
46underneath the current working directory.
47
48A typical httpd would be a more complete program which first
49configures a few things like the {{port-number}} and {{root-path}}
50parameters and set up some logging through {{access-log}} and
51{{error-log}}.  It could also configure some extensions (like
52{{simple-directory-handler}}) and register some user-defined path
53handlers by adding them to {{vhost-map}}.  Finally it could daemonize by
54forking and exiting.  The mentioned parameters are explained below.
55
56=== Starting the server
57
58<procedure>(start-server [port: port-number] [bind-address: address] [listen: listen-procedure] [accept: accept-procedure] [addresses: addresses-procedure])</procedure>
59
60This is the most convenient way to get a server up and running quickly.
61It starts the server to listen on the given port. Other configuration
62can be tweaked through SRFI-39 parameters. These are listed below.
63Once the server is started, server behaviour can be controlled through
64these parameters as well.  After the listener is started, when
65{{spiffy-user}} and/or {{spiffy-group}} are provided, this procedure
66will drop privileges before starting the accept loop.
67
68By default, Spiffy will only serve static files. On directories, it
69will give a "403 forbidden", unless there is an index-file. If there
70is, that file's contents will be shown.
71
72All arguments directly supplied to {{start-server}} override the
73configuration parameter values and will be parameterized to reflect
74this new setting.
75
76{{port-number}} defaults to the value of {{server-port}} (see below).
77{{bind-address}} defaults to the value of {{server-bind-address}} (see below).
78{{listen}} defaults to {{tcp-listen}} and should accept a port number, backlog and bind address.
79{{accept}} defaults to {{tcp-accept}}, and is passed on as-is to {{accept-loop}}.
80{{addresses-procedure}} defaults to a procedure which works like {{tcp-addresses}} but can also detect SSL ports and return the addresses of the underlying TCP connection.
81
82<procedure>(accept-loop listener accept [addresses])</procedure>
83
84This procedure starts the loop which accepts incoming connections and
85fires off threads to handle requests on those connections.  You can
86use it if you need more control over the startup process than
87{{start-server}} offers.
88
89The listener object should be an object which is accepted by the
90{{accept}} procedure, which should return two values; an input and
91an output port which represent an incoming connection from a client.
92The optional {{addresses}} procedure should accept the input port
93returned by the {{accept}} procedure and return two strings; the
94local and remote addresses of the server and client, respectively.
95
96For example, you can set up an SSL context and drop privileges,
97and possibly load extra code before starting the accept loop
98(Spiffy contains the required code to detect SSL ports,
99and will handle those more-or-less transparently):
100
101<enscript highlight="scheme">
102(import spiffy openssl)
103
104(server-port 443)
105(spiffy-user "www")
106(spiffy-group "www")
107
108;; Bind the port as root, before we drop privileges
109(define listener (ssl-listen (server-port)))
110
111;; Load the certificate files as root so we can secure their permissions
112(ssl-load-certificate-chain! listener "server.pem")
113(ssl-load-private-key! listener "server.key")
114
115;; Drop root privileges
116(switch-user/group (spiffy-user) (spiffy-group))
117 
118;; We don't want to load this extra code as root!
119(load "extra-code.scm")
120
121;; Done! Start listening for connections.
122(accept-loop listener ssl-accept)
123</enscript>
124
125<procedure>(switch-user/group user group)</procedure>
126
127This is a helper procedure which allows you to easily drop privileges
128before running the accept loop.  The user and group must be either
129strings or UID/GID numbers which indicate the username and groupname
130to which you want to switch.  Either is also allowed to be {{#f}}, if
131you don't want to switch that aspect of the process.
132
133For this to work you'll need to install the [[posix-groups]] egg,
134otherwise a runtime exception is raised.
135
136=== Configuration parameters
137
138The following parameters can be used to control spiffy's behaviour.
139Besides these parameters, you can also influence spiffy's behaviour by
140tweaking the [[intarweb]] parameters.
141
142<parameter>(server-software [product])</parameter>
143
144The server software product description. This should be a valid
145product value as used in the server and user-agent headers by intarweb;
146this is a list of lists. The inner lists contain the product name,
147the product version and a comment, all either a string or {{#f}}.
148Default: {{(("Spiffy" "a.b" "Running on Chicken x.y"))}}, with {{a.b}}
149being the Spiffy major/minor version and {{x.y}} being Chicken's.
150
151<parameter>(root-path [path])</parameter>
152
153The path to the document root, for the current vhost.
154Defaults to {{"./web"}}.
155
156<parameter>(server-port [port-number])</parameter>
157
158The port number on which to listen. Defaults to 8080.
159
160<parameter>(server-bind-address [address])</parameter>
161
162The IP address on which to listen, or all addresses if {{#f}}.
163Defaults to {{#f}}.
164
165<parameter>(max-connections [number])</parameter>
166
167The maximum number of simultaneously active connections. Defaults to 1024.
168
169Any new connection that comes in when this number is reached must wait
170until one of the active connections is closed.
171
172<parameter>(spiffy-user [name-or-uid])</parameter>
173
174The name or UID of a user to switch to just after binding the
175port. This only works if you start Spiffy as root, so it can bind port
17680 and then drop privileges. If {{#f}}, no switch will occur.
177Defaults to {{#f}}.
178
179If you set this to non-{{#f}}, you'll need to install the
180[[posix-groups]] egg, otherwise a runtime exception is raised.
181
182<parameter>(spiffy-group [name-or-gid])</parameter>
183
184The name or GID of a group to switch to just after binding the
185port. This only works if you start Spiffy as root, so it can bind port
18680 and then drop privileges. If {{#f}}, it will be set to the primary
187group of {{spiffy-user}} if the user was selected. Otherwise, no
188change will occur.  Defaults to {{#f}}.
189
190If you set this to non-{{#f}}, you'll need to install the
191[[posix-groups]] egg, otherwise a runtime exception is raised.
192
193<parameter>(index-files [file-list])</parameter>
194
195A list of filenames which are to be used as index files to serve when
196the requested URL identifies a directory.  Defaults to
197{{'("index.html" "index.xhtml")}}
198
199<parameter>(mime-type-map [extension->mimetype-list])</parameter>
200
201An alist of extensions (strings) to mime-types (symbols), to use
202for the content-type header when serving up a static file. Defaults to
203
204  '(("html" . text/html)
205    ("xhtml" . application/xhtml+xml)
206    ("js"  . application/javascript)
207    ("css" . text/css)
208    ("png" . image/png)
209    ("xml" . application/xml)
210    ("pdf" . application/pdf)
211    ("jpeg" . image/jpeg)
212    ("jpg" . image/jpeg)
213    ("gif" . image/gif)
214    ("ico" . image/vnd.microsoft.icon)
215    ("svg" . image/svg+xml)
216    ("txt" . text/plain))
217
218See also {{file-extension->mime-type}} for a procedure which
219can look up file extensions for you.
220
221<parameter>(default-mime-type [mime-type])</parameter>
222
223The mime-type (a symbol) to use if none was found in the
224{{mime-type-map}}. Defaults to {{'application/octet-stream}}
225
226<parameter>(default-host [hostname])</parameter>
227
228The host name to use when no virtual host could be determined from the
229request.  See the section on virtual hosts below.
230
231<parameter>(vhost-map [host-regex->vhost-handler])</parameter>
232
233A mapping of virtual hosts (regex) to handlers (procedures of one
234argument; a continuation thunk). See the section on virtual hosts
235below. Defaults to {{`((".*" . ,(lambda (continue) (continue))))}}
236
237<parameter>(file-extension-handlers [extension->handler-list])</parameter>
238
239An alist mapping file extensions (strings) to handler procedures
240(lambdas of one argument; the file name relative to the webroot).
241Defaults to {{'()}}. If no handler was found, defaults to just sending
242a static file.
243
244<parameter>(access-log [log-file-or-port])</parameter>
245
246Filename (string) or port to append access log output to.  Default:
247{{#f}} (disabled)
248
249<parameter>(error-log [log-file-or-port])</parameter>
250
251Filename (string) or port to which error messages from evaluated code
252should be output. Default: {{(current-error-port)}}
253
254<parameter>(debug-log [log-file-or-port])</parameter>
255
256Filename (string) or port to write debugging messages to.  Default:
257{{#f}} (disabled)
258
259<parameter>(access-file [string])</parameter>
260
261The name of an access file, or {{#f}} if not applicable.  This file is
262read when the directory is entered by the directory traversal system,
263and allows you to write dynamic handlers that can assign new values
264for parameters only for resources below that directory, very much like
265adding parameters in code before calling a procedure.  See the section
266"Access files" for more information.
267
268<parameter>(trusted-proxies [list-of-strings])</parameter>
269
270When an incoming request is first accepted, the {{remote-address}} is
271initialized to the IP address of the remote peer.  When this peer is a
272reverse proxy in an internal network, that value is not so useful
273because all requests would seem to come from there.
274
275If you want to have a more meaningful value, you can add the IP
276addresses of proxies to this list, and {{X-Forwarded-For}} entries
277from these proxies will be stripped, and the first entry just before
278the most-distant trusted proxy will be used.
279
280Be careful: all IP addresses in this list will be trusted on their
281word.
282
283Default: {{()}} (trust no one)
284
285
286=== Handlers
287
288Besides "static" configuration, Spiffy also has several handlers for
289when something is to be served.
290
291<parameter>(handle-directory [proc])</parameter>
292
293The handler for directory entries. If the requested URL points to a
294directory which has no index file, this handler is invoked. It is a
295procedure of one argument, the path (a string) relative to the
296webroot. Defaults to a procedure which returns a "403 forbidden".
297
298<parameter>(handle-file [proc])</parameter>
299
300The handler for files. If the requested URL points to a file, this
301handler is invoked to serve the file. It is a procedure of one
302argument, the path (a string) relative to the webroot. Defaults to a
303procedure which sets the content-type and determines a handler based
304on the {{file-extension-handlers}}, or {{send-static-file}} if none
305was found and the method was {{HEAD}} or {{GET}} (otherwise it replies
306with 405 "Method Not Allowed").
307
308<parameter>(handle-not-found [proc])</parameter>
309
310The handler for nonexistent files. If the requested URL does not point
311to an existing file or directory, this procedure is called. It is a
312procedure of one argument, the path (a string) to the first missing file
313in the request path.  Defaults to a procedure which returns a
314"404 Not found".
315
316The path path which is passed to the handler is handled in a particular
317way: It contains all the components that exist, and the final component
318is the file or directory which does not exist.  That means this path may
319differ from the concatenated URI path; this can be seen as "pathinfo" for
320the would-be file, if it had existed.
321
322The path should be interpreted as relative to {{root-path}}.
323
324<parameter>(handle-exception [proc])</parameter>
325
326The handler for when an exception occurs. This defaults to a procedure
327that logs the error to the error log. While debugging or developing, it
328may be more convenient to use a procedure that sends the error back to
329the client:
330
331<enscript highlight=scheme>
332(handle-exception
333  (lambda (exn chain)
334    (send-status 'internal-server-error (build-error-message exn chain))))
335</enscript>
336
337<parameter>(handle-access-logging [proc])</parameter>
338
339The handler for access logging. This is a procedure of zero arguments
340which should write a line to the access log. Defaults to a procedure which
341writes a line to {{access-log}} which looks like this:
342
343   127.0.0.1 [Sun Nov 16 15:16:01 2008] "GET http://localhost:8080/foo?bar HTTP/1.1" 200 "http://localhost:8080/referer" "Links (2.2; NetBSD 5.99.01 macppc; x)"
344
345=== Runtime information
346
347During the handling of a request, Spiffy adds more information to the
348environment by parameterizing the following parameters whenever the
349information becomes available:
350
351<parameter>(current-request [request])</parameter>
352
353An intarweb request-object that defines the current request. Available
354from the moment the request comes in and is parsed. Contains, among
355other things, the query parameters and the request-headers, in fully
356parsed form (as intarweb returns them).
357
358The URI is automatically augmented with the host, scheme and port if
359it is not an absolute URI.
360
361<parameter>(current-response [response])</parameter>
362
363An intarweb response-object that defines the current
364response. Available from the same time current-request is available.
365This keeps getting updated along the way, while the response data is
366being refined (like when headers are being added).
367
368<parameter>(current-file [path])</parameter>
369
370The path to the requested file (a string). Available from the moment
371Spiffy determined the requested URL points to a file (just before the
372{{handle-file}} procedure is called). This file is relative to the
373{{root-path}}.
374
375<parameter>(current-pathinfo [path])</parameter>
376
377The trailing path ''fragments'' (a list of strings) that were passed
378in the URL after the requested filename. Available from the moment
379Spiffy determined the requested URL points to a file (just before the
380{{handle-file}} procedure is called).
381
382<parameter>(remote-address [address])</parameter>
383
384The IP address (a string) of the user-agent performing the current
385request.  See also {{trusted-proxies}}.
386
387<parameter>(local-address [address])</parameter>
388
389The IP address (a string) on which the current request came in.
390
391<parameter>(secure-connection? [boolean])</parameter>
392
393{{#t}} when the current connection is a secure one (SSL), {{#f}} if it
394isn't (regular HTTP).  This pertains only to the direct connection
395itself, so if Spiffy is behind a proxy this will be {{#f}} even if the
396proxy itself is connected to the client over SSL.
397
398One way to get around this is to ''always'' add a custom header to
399your reverse proxy's configuration file.  Then you can read this out
400in Spiffy and set secure-connection? to {{#t}} or {{#f}}, as well as
401updating the request URI's scheme.  There is no standardized header
402for this, so the default Spiffy won't do this.
403
404An easier way around this is to set up two spiffies listening on
405different ports and configure one to have {{secure-connection?}} set
406to {{#t}}, which you redirect incoming HTTPS requests to.
407
408'''This parameter may disappear or change in the future''', when there
409are more smart people using Spiffy who know how to deal with this or
410the Spiffy maintainer has a moment of clarity and decides how to do
411this cleanly.
412
413=== Virtual hosts
414
415Spiffy has support for virtual hosting, using the HTTP/1.1 Host
416header.  This allows you to use one Spiffy instance running on one IP
417address/port number to serve multiple webpages, as determined by the
418hostname that was requested.
419
420The virtual host is defined by a procedure, which can set arbitrary
421parameters on-the-fly. It is passed a continuation thunk, which it
422should explicitly call if it wants the processing to continue.  The
423most used parameter in virtual host setups is the {{root-path}}
424parameter, so that another docroot can be selected based on the
425requested hostname, showing different websites for different hosts:
426
427<enscript highlight="scheme">
428(vhost-map `(("foo\\.bar\\.com" .
429               ,(lambda (continue)
430                  ;; Requires the spiffy-dynamic-handlers egg...
431                  (parameterize ((file-extension-handlers
432                                   `(("ssp" . ,ssp-handler) ("ws" . ,web-scheme-handler)))
433                                 (root-path "/var/www/domains/foo.bar.com"))
434                     (continue))))
435             (,(glob->regexp "*.domain.com") .
436                ,(lambda (continue)
437                   ;; Requires the spiffy-cgi-handlers egg...
438                   (parameterize ((file-extension-handlers
439                                    `(("php" . ,(cgi-handler* "/usr/pkg/libexec/cgi-bin/php"))))
440                                  ;; You can also change PHP's arg_separator.input
441                                  ;; to be ";&" instead of this parameter
442                                  (form-urlencoded-separator "&")
443                                  (root-path "/var/www/domains/domain.com"))
444                     (continue))))))
445</enscript>
446
447In this example, if a client accesses
448{{foo.bar.com/mumble/blah.html}}, the file
449{{/var/www/domains/foo.bar.com/mumble/blah.html}} will be served.  Any
450files ending in {{.ssp}} or {{.ws}} will be served by the
451corresponding file type handler.  If there's any PHP file, its source
452will simply be displayed.  In case of
453{{my.domain.com/something/bar.html}}, the file
454{{/var/www/domains/domain.com/something/bar.html}} will be served.  If
455there's a {{.ssp}} or {{.ws}} file there, it will not be interpreted.
456Its source will be displayed instead.  A {{.php}} file, on the other
457hand, will be passed via CGI to the program {{/usr/pkg/libexec/cgi-bin/php}}.
458
459Domain names are mapped to a lambda that can set up any parameters it
460wants to override from the defaults.  The host names are matched using
461{{irregex-match}}.  If the host name is not yet a regexp, it will be
462converted to a ''case-insensitive'' regexp.
463
464=== Access files
465
466Fine-grained access-control can be implemented by using so-called
467access files.  When a request for a specific file is made and a file
468with the name given in the {{access-file}} parameter exists in any
469directory between the {{root-path}} of that vhost and the directory in
470which the file resides, then the access file is loaded as an
471s-expression containing a function and is evaluated with a single
472argument, the function that should be called to continue processing
473the request.
474
475This works just like vhosting.  The function that gets called can call
476{{parameterize}} to set additional constraints on the code that
477handles deeper directories.
478
479For example, if we evaluate {{(access-file ".access")}} before
480starting the server, and we put the following code in a file named
481{{.access}} into the root-directory, then all accesses to any file
482in the root-directory or any subdirectory will be denied unless the
483request comes from localhost:
484
485<enscript highlight=scheme>
486 (lambda (continue)
487   (if (string=? (remote-address) "127.0.0.1")
488       (continue)
489       (send-status 'forbidden "Sorry, you're not allowed here")))
490</enscript>
491
492If we only want to deny access to files that start with an X, put this
493in the {{.access}} file:
494
495<enscript highlight=scheme>
496 (lambda (continue)
497   (let ((old-handler (handle-file)))
498     (parameterize ((handle-file
499                      (lambda (path)
500                        (if (not (string-prefix? "X" (pathname-file path)))
501                            (send-status 'forbidden "No X-files allowed!")
502                            (old-handler path)))))
503       (continue))))
504</enscript>
505
506Of course, access files can be used for much more than just access
507checks.  One can put anything in them that could be put in vhost
508configuration or in top-level configuration.
509
510They are very useful for making deployable web applications, so you
511can just drop a directory on your server which has its own
512configuration embedded in an access file in the root directory of the
513application, without having to edit the server's main configuration
514files.
515
516'''Warning:''' Be very careful when using this.  The full Scheme
517language is available from access files.  That means that a small
518mistake in your web application's file permissions might result in a
519complete breach of your server's security.  This feature will be
520removed in a future version, because it is much too dangerous.
521
522=== Procedures and macros
523
524The following procedures and macros can be used in dynamic web
525programs, or dynamic server configuration:
526
527<procedure>(with-headers new-headers thunk)</procedure>
528
529Call {{thunk}} with the header list {{new-headers}}. This
530parameterizes the current response to contain the new headers.  The
531existing headers are extended with {{new-headers}} through intarweb's
532{{headers}} procedure.
533
534<procedure>(write-logged-response)</procedure>
535
536This procedure simply writes {{current-response}} after calling
537{{handle-access-logging}}. Responses should always go through this
538procedure instead of directly using {{write-response}} from intarweb.
539
540If you have a response body to write, you still need to remember to
541call {{finish-response-body}} (from intarweb) after doing so.
542
543<procedure>(log-to log format . rest)</procedure>
544
545Write a printf-style format string to the specified log (one of
546{{access-log}}, {{error-log}} or {{debug-log}}). {{format}} is a
547{{printf}}-style format string, and rest arguments should match
548the arguments one would pass to printf. A newline is appended to
549the end of the log message automatically.
550
551<procedure>(send-response #!key status code reason body headers)</procedure>
552
553Easy way to send string data to the client, with additional headers.
554It will add appropriate headers and will automatically detect {{HEAD}}
555requests.  If BODY is {{#f}}, no body is sent and the {{content-length}}
556header is set to zero.
557
558The status is a symbol describing the response status, which is looked
559up in [[intarweb]]'s {{http-status-code}} parameter.  If {{code}} and/or
560{{reason}} are supplied, these take precedence.  If {{status}} and {{code}}
561are missing, the default status is {{ok}}.
562
563<procedure>(send-status code reason [message])</procedure><br>
564<procedure>(send-status status [message])</procedure>
565
566Easy way to send a page and a status code to the client.  The optional
567{{message}} is a string containing HTML to add in the body of the
568response. Some structure will be added around the message, so message
569should only be the actual message you want to send.
570
571This can be called either with a numeric code, string reason and
572optional message or with a symbolic status and optional message.
573
574Example:
575
576<enscript highight="scheme">
577(send-status 404 "Not found"
578 "Sorry, page not found! Please try <a href='/search.ws'>our search page</a>")
579
580;; Alternative way of doing this:
581(send-status 'not-found
582 "Sorry, page not found! Please try <a href='/search.ws'>our search page</a>")
583</enscript>
584
585<procedure>(send-static-file filename)</procedure>
586
587Send a file to the client. This sets the {{content-length}} header and
588tries to send the file as quickly as possible to the client. The
589filename is interpreted relative to {{root-path}}.
590
591<procedure>(file-extension->mime-type EXT)</procedure>
592
593Looks up the file extension {{EXT}} (without a leading dot)
594in {{mime-type-map}}, or uses {{default-mime-type}} when the
595extension can't be found.
596
597If {{EXT}} is {{#f}}, it'll look up the extension that is the
598empty string.
599
600This returns a symbol which indicates the mime-type which is matched
601to the extension (for example {{text/html}}).
602
603<procedure>(restart-request request)</procedure>
604
605Restart the entire request-handling starting at the point where the
606request was just parsed. The argument is the new request to use.
607Be careful, this makes it very easy to introduce unwanted endless loops!
608
609<procedure>(htmlize string) => string</procedure>
610
611Encode "special" html symbols like tag and attribute characters so
612they will not be interpreted by the browser.
613
614<procedure>(build-error-message exn chain [raw-output])</procedure>
615
616Build an error message for the exception {{exn}}, with call chain
617{{chain}}. Defaults to HTML output, unless {{raw-output}} is given and
618nonfalse.
619
620=== Modules
621
622This section will describe what the various modules that come with
623Spiffy are and how they work.
624
625==== ssp-handler
626
627This was moved to the [[spiffy-dynamic-handlers]] egg.
628
629==== web-scheme-handler
630
631This was moved to the [[spiffy-dynamic-handlers]] egg.
632
633==== cgi-handler
634
635This was moved to the [[spiffy-cgi-handlers]] egg.
636
637==== simple-directory-handler
638
639In order to get directory listings, you can use
640{{simple-directory-handler}}.  Just parameterize {{handle-directory}}'s
641value with the {{simple-directory-handler}} procedure and you're set.
642
643This directory handler truly is very simple and limited in what you
644can customize.  For a more flexible directory handler, see the
645[[spiffy-directory-listing]] egg.
646
647===== Configuration
648
649The simple directory handler has a few configuration options:
650
651<procedure>(simple-directory-dotfiles? [dotfiles?])</procedure>
652
653Determines if dotfiles should show up in the directory listings.
654Default: {{#f}}
655
656<procedure>(simple-directory-display-file [displayer])</procedure>
657
658A lambda that accepts three arguments: the remote filename, the local
659filename and a boolean that says if the file is a directory.  This
660lambda should output a table row with the desired information.
661Defaults to a lambda that prints the name, size and date when the file
662was last modified.
663
664===== Procedures
665
666The simple-directory handler adds only one procedure to the environment:
667
668<procedure>(simple-directory-handler pathname)</procedure>
669
670The handler itself, which should be used in the {{handle-directory}}
671parameter.
672
673=== Examples
674
675==== Quick config for serving up a docroot
676
677Spiffy is very easy to use for simple cases:
678
679<enscript highlight=scheme>
680(import spiffy)
681
682(server-port 80)
683(root-path "/var/www")
684;; When dropping privileges, switch to this user
685(spiffy-user "httpd")
686(spiffy-group "httpd")
687(start-server)
688</enscript>
689
690One could also use parameterize (according to taste):
691
692<enscript highlight=scheme>
693(import spiffy)
694
695(parameterize ((server-port 80)
696               (spiffy-user "httpd")
697               (spiffy-group "httpd")
698               (root-path "/var/www"))
699  (start-server))
700</enscript>
701
702If you put this in {{/usr/local/libexec/spiffy.scm}} you can use [[/spiffy-init-script|this example init.d script]] or [[/spiffy-systemd-scripts|these example systemd scripts]] as-is, to launch Spiffy.
703
704==== A simple dynamic web page example
705
706If you would like to dynamically serve content, you can use a custom
707handler in the vhost-map for the host on which you want to serve
708this content:
709
710<enscript highlight="scheme">
711(import spiffy intarweb uri-common)
712
713(define (handle-greeting continue)
714  (let* ((uri (request-uri (current-request))))
715    (if (equal? (uri-path uri) '(/ "greeting"))
716        (send-response status: 'ok body: "<h1>Hello!</h1>")
717        (continue))))
718
719(vhost-map `(("localhost" . ,handle-greeting)))
720
721(start-server)
722</enscript>
723
724It's not advised to use string concatenation for building HTML, as it
725is rather unsafe and easy to get wrong.  For a slightly larger
726example, see
727[[http://bugs.call-cc.org/browser/project/demonstrations/spiffy|this
728demonstration of Spiffy with SXML]].
729
730
731==== Network tweaks
732
733Spiffy does not activate Chicken's TCP buffering, which results in
734extra traffic: one packet sent per header line.  With a TCP buffer
735size greater than the total header length, all headers will be
736coalesced into a single write; generally the response body will be
737coalesced as well.  For example:
738
739 (tcp-buffer-size 2048)   ; from unit tcp
740 (start-server)
741
742==== Redirecting to another domain
743
744When you have a domain you want to canonicalize so that it will always
745have {{www}} in front of it, you can set up a conditional redirect in
746your {{vhosts}} section:
747
748<enscript highlight=scheme>
749(import spiffy intarweb uri-common)
750
751(vhost-map `(("example.com"
752               . ,(lambda (continue)
753                    (let* ((old-u (request-uri (current-request)))
754                           (new-u (update-uri old-u host: "www.example.com")))
755                      (with-headers `((location ,new-u))
756                        (lambda () (send-status 'moved-permanently))))))
757             ("www.example.com" . ,(lambda (continue) (continue)))))
758
759(start-server)
760</enscript>
761
762Alternatively the following version can be used to generate appropriate handlers for several domains:
763
764<enscript highlight=scheme>
765(import spiffy intarweb uri-common)
766
767; Generates a handler that can be used in vhost-map that will cause all requests to that URL to be rewritten to the domain specified in 'to'.
768(define (canonicalise-domain to)
769  (let ((to (uri-reference to)))
770   (assert (equal? '(/ "") (uri-path to)))
771   (assert (null? (uri-query to)))
772   ; We don't see fragments on the server and choose not to care about usernames and password fields.
773    (lambda (continue)
774      (let* ((old-u (request-uri (current-request)))
775             (new-u (update-uri old-u
776                                scheme: (or (uri-scheme to) (uri-scheme old-u))
777                                port:   (or (uri-port   to) (uri-port   old-u))
778                                host:   (or (uri-host   to) (uri-host   old-u)))))
779        (with-headers `((location ,new-u))
780                      (lambda () (send-status 'moved-permanently)))))))
781
782(vhost-map `(("example.com"
783               . ,(canonicalise-domain "http://www.example.com"))
784 ("www.example.com" . ,(lambda (continue) (continue)))))
785</enscript>
786
787The above code hooks a handler for the regular expression {{"example.com"}},
788checks the request's URI and then updates only the host component, keeping
789all other components (like port and path) intact.  Then it sends a simple
790status response that indicates a 301 "Moved Permanently" response, using
791the new URI in the {{Location:}} header.
792
793The second entry just tells Spiffy to continue serving the request
794when it's made to {{www.example.com}}.  A request on any other host
795will receive a 404 not found response due to not having a vhost entry.
796More elaborate setups can parameterize some other aspects for each host
797before calling {{continue}}.
798
799=== Changelog
800
801* 6.1: Drop hard dependency on [[posix-groups]] egg so Spiffy can be installed on non-UNIX platforms more easily.
802* 6.0.1: Fix build dependency issue.
803* 6.0: Port to CHICKEN 5.
804* 5.4.2: Use proper 2-element vectors for {{date}} and {{last-modified}} headers, for compatibility with intarwebs newer than 1.6.1.
805* 5.4.1: Once again allow backslashes in paths on UNIX-like platforms in CHICKENs where {{make-pathname}} was fixed.  Improve request handling performance.  Remove over-reliance on undocumented {{alist-ref}} comparison procedure's argument order (which will be changed in CHICKEN 4.11).
806* 5.4: Fix critical security vulnerability (path traversal, CVE-2015-8235) by disallowing backslashes in request paths (thanks to Benedikt Rosenau for reporting the vulnerability).  Use {{spiffy}} in the gensym'ed name of spiffy's threads, for easier debugging.  Thanks to Evan Hanson.
807* 5.3.2 Fix tests so they still function correctly when {{SPIFFY_TEST_PORT}} is overridden.  Thanks to Kon Lovett for pointing this out.
808* 5.3.1 Don't try to handle another request when the input or output ports are closed (thanks to Thomas Hintz.  This makes [[/egg/websockets|websocket]] support work properly!)
809* 5.3 Improved presentation and correctness of HTML in simple-directory-handler (thanks to Roel van der Hoorn).  Fixed serving of static files on Windows due to missing {{#:binary}} option (thanks to "Jubjub").
810* 5.2.1 Handle disconnections more gracefully (as per intarweb 1.2), and improved debugging some more..
811* 5.2 Log prematurely broken connections to the debug-log, to aid in... debugging (thanks to Andy Bennett). Fix locking bug in tests, and disable the "unreadable file is 403" check when run as root.
812* 5.1 Fix static file handling of files which cannot be read by Spiffy (thanks to Andy Bennett). Use intarweb's new {{finish-response-body}} procedure where needed. Add image/svg+xml to the default mime-type lists.
813* 5.0 Work around race conditions in {{simple-directory-handler}} related to deleted files or self-referential symlinks (reported by [[/users/mario-domenech-goulart|Mario]]). Move deprecated dynamic content handlers into separate [[spiffy-dynamic-handlers]] egg. Move cgi handler into separate [[spiffy-cgi-handlers]] egg.
814* 4.15 Disallow non-{{GET}}/{{HEAD}} requests for normal files in {{handle-file}}.  Remove dependencies on [[/eggref/4/matchable|matchable]] and [[/eggref/4/regex|regex]] eggs.  Allow request-uri overrides in user lambdas to affect the default handler (Thanks to [[/users/mario-domenech-goulart|Mario]]).
815* 4.14 Fix ssp-handler's pathname construction so it doesn't error on files in the rootdirectory.  Fix incompatibility with version 1.6.1 of the OpenSSL egg [Thanks to [[/users/mario-domenech-goulart|Mario]] and [[/users/thomas-chust|Thomas]].]
816* 4.13 Add trusted-proxies parameter so remote-address can have a meaningful value in the presence of reverse proxies [thanks to John J Foerch]. Allow cgi scripts to be outside the docroot. Pass on query-strings to CGI scripts as-is, if possible, to avoid breaking scripts that rely on a particular variable-separator (reported by Felix Winkelmann).  Refactor to use symbolic response codes from Intarweb.
817* 4.12 Make Spiffy optionally fully independent of Unit tcp [thanks to [[/users/jim-ursetto|Jim Ursetto]]]  Tests now also work with Chickens greater than 4.6.0 (removed deprecated {{getenv}} calls)
818* 4.11 Drop openssl requirement (suggested by Felix Winkelmann); split up listening and accept loop invocation to allow loading code after dropping root privileges (suggested by Mario Goulart).
819* 4.10 Fix serious bug which caused Spiffy to exit after handling N requests. Fix handling of empty path components now that uri-generic preserves them.
820* 4.9 Export {{file-extension->mime-type}}. Add more info about exceptional situations to the debugging logs when enabled.
821* 4.8 Fix ssl support. Get rid of {{spiffy-root-uri}}. Add timestamp and request info to error log.
822* 4.7 Fix redirects for directories again (thanks to [[/users/mario-domenech-goulart|Mario]]). NO TESTCASE (possibly to be fixed in [[uri-generic]])
823* 4.6 Fix redirects for directories with special URI characters in them (thanks to [[/users/felix-winkelmann|Felix]])
824* 4.5 Add {{send-response}} procedure; flush output after request is handled; use proper IANA-assigned MIME types in default {{mime-type-map}}; fix {{server-root-uri}} when spiffy is behind a proxy [thanks to zbigniew]
825* 4.4 Fix a problem with 304 "not modified", for which Safari incorrectly tries to read a content-body when a content-length is present.
826* 4.3 Fix crash with extensionless files and nonempty extension handlers
827* 4.2 Add support for caching headers and proper If-Modified-Since support
828* 4.1 Make cgi-handler cope with missing pathinfo. Fix statuscode related crash in cgi-handler. Make ssp handler's open/close tags parameters, so it actually matches what the documentation says. Add {{ssp-cache-dir}}. Update for latest intarweb changes (0.2)
829* 4.0 Rewrite from scratch, using Intarweb
830* pre-4.0 See the changelog for the [[/eggref/3/spiffy|old spiffy]]
831
832=== License
833
834  Copyright (c) 2005-2018, Felix L. Winkelmann and Peter Bex
835  All rights reserved.
836 
837  Redistribution and use in source and binary forms, with or without
838  modification, are permitted provided that the following conditions are
839  met:
840 
841  Redistributions of source code must retain the above copyright
842  notice, this list of conditions and the following disclaimer.
843 
844  Redistributions in binary form must reproduce the above copyright
845  notice, this list of conditions and the following disclaimer in the
846  documentation and/or other materials provided with the distribution.
847 
848  Neither the name of the author nor the names of its contributors may
849  be used to endorse or promote products derived from this software
850  without specific prior written permission.
851 
852  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
853  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
854  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
855  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
856  COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
857  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
858  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
859  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
860  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
861  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
862  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
863  OF THE POSSIBILITY OF SUCH DAMAGE.
Note: See TracBrowser for help on using the repository browser.