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

Last change on this file since 36026 was 36026, checked in by sjamaan, 2 years ago

Add 6.0.1 to spiffy changelog

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