source: project/wiki/eggref/4/spiffy @ 32241

Last change on this file since 32241 was 32241, checked in by felix winkelmann, 6 years ago

removed some call/cc.org links

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