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