Commit | Line | Data |
---|---|---|
e414bf21 AW |
1 | ;;; Toy web server |
2 | ||
3 | ;; Copyright (C) 2010 Free Software Foundation, Inc. | |
4 | ||
5 | ;; This library is free software; you can redistribute it and/or | |
6 | ;; modify it under the terms of the GNU Lesser General Public | |
7 | ;; License as published by the Free Software Foundation; either | |
8 | ;; version 3 of the License, or (at your option) any later version. | |
9 | ;; | |
10 | ;; This library is distributed in the hope that it will be useful, | |
11 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | ;; Lesser General Public License for more details. | |
14 | ;; | |
15 | ;; You should have received a copy of the GNU Lesser General Public | |
16 | ;; License along with this library; if not, write to the Free Software | |
17 | ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
18 | ;; 02110-1301 USA | |
19 | ||
20 | ;;; Code: | |
21 | ||
22 | (define-module (web toy-server) | |
23 | #:use-module (rnrs bytevectors) | |
24 | #:use-module (web request) | |
25 | #:use-module (web response) | |
d41c62f5 AW |
26 | #:use-module (system repl error-handling) |
27 | #:use-module (ice-9 control) | |
e414bf21 AW |
28 | #:export (run-server simple-get-handler)) |
29 | ||
30 | (define (make-default-socket family addr port) | |
31 | (let ((sock (socket PF_INET SOCK_STREAM 0))) | |
32 | (setsockopt sock SOL_SOCKET SO_REUSEADDR 1) | |
33 | (bind sock family addr port) | |
34 | sock)) | |
35 | ||
36 | (define call-with-sigint | |
37 | (if (not (provided? 'posix)) | |
38 | (lambda (thunk) (thunk)) | |
39 | (lambda (thunk) | |
40 | (let ((handler #f)) | |
41 | (dynamic-wind | |
42 | (lambda () | |
43 | (set! handler | |
44 | (sigaction SIGINT (lambda (sig) (throw 'interrupt))))) | |
45 | thunk | |
46 | (lambda () | |
47 | (if handler | |
48 | ;; restore Scheme handler, SIG_IGN or SIG_DFL. | |
49 | (sigaction SIGINT (car handler) (cdr handler)) | |
50 | ;; restore original C handler. | |
51 | (sigaction SIGINT #f)))))))) | |
52 | ||
53 | (define (accept-new-client server-socket) | |
54 | (catch #t | |
55 | (lambda () (call-with-sigint (lambda () (accept server-socket)))) | |
56 | (lambda (k . args) | |
57 | (cond | |
58 | ((port-closed? server-socket) | |
59 | ;; Shutting down. | |
60 | #f) | |
61 | ((eq? k 'interrupt) | |
62 | ;; Interrupt. | |
63 | (close-port server-socket) | |
64 | #f) | |
65 | (else | |
66 | (warn "Error accepting client" k args) | |
67 | ;; Retry after a timeout. | |
68 | (sleep 1) | |
69 | (accept-new-client server-socket)))))) | |
70 | ||
71 | (define* (simple-get-handler handler #:optional (content-type '("text" "plain"))) | |
72 | (lambda (request request-body) | |
73 | (if (eq? (request-method request) 'GET) | |
74 | (let* ((x (handler (request-absolute-uri request))) | |
75 | (bv (cond ((bytevector? x) x) | |
76 | ((string? x) (string->utf8 x)) | |
77 | (else | |
78 | (error "unexpected val from simple get handler" x))))) | |
79 | (values (build-response | |
80 | #:headers `((content-type . ,content-type) | |
81 | (content-length . ,(bytevector-length bv)))) | |
82 | bv)) | |
83 | (build-response #:code 405)))) | |
84 | ||
d41c62f5 AW |
85 | (define (with-stack-and-prompt thunk) |
86 | (call-with-prompt (default-prompt-tag) | |
87 | (lambda () (start-stack #t (thunk))) | |
88 | (lambda (k proc) | |
89 | (with-stack-and-prompt (lambda () (proc k)))))) | |
90 | ||
e414bf21 | 91 | (define (serve-client handler sock addr) |
d41c62f5 AW |
92 | (define *on-toy-server-error* (if (batch-mode?) 'pass 'debug)) |
93 | (define *on-handler-error* (if (batch-mode?) 'pass 'debug)) | |
94 | ||
95 | (call-with-values | |
96 | (lambda () | |
97 | (call-with-error-handling | |
98 | (lambda () | |
99 | (let* ((req (read-request sock)) | |
100 | (body-str (read-request-body/latin-1 req))) | |
101 | (call-with-error-handling | |
102 | (lambda () | |
103 | (with-stack-and-prompt | |
104 | (lambda () | |
105 | (handler req body-str)))) | |
106 | #:pass-keys '(quit interrupt) | |
107 | #:on-error *on-handler-error* | |
108 | #:post-error | |
109 | (lambda (k . args) | |
110 | (warn "Error while serving client" k args) | |
111 | (build-response #:code 500))))) | |
112 | #:pass-keys '(quit interrupt) | |
113 | #:on-error *on-toy-server-error* | |
114 | #:post-error | |
115 | (lambda (k . args) | |
116 | (warn "Error reading request" k args) | |
117 | (build-response #:code 400)))) | |
118 | (lambda* (response #:optional body) | |
119 | (call-with-error-handling | |
120 | (lambda () | |
121 | (let ((response (write-response response sock))) | |
122 | (cond | |
123 | ((not body)) ; pass | |
124 | ((string? body) | |
125 | (write-response-body/latin-1 response body)) | |
126 | ((bytevector? body) | |
127 | (write-response-body/bytevector response body)) | |
128 | (else | |
129 | (error "Expected a string or bytevector for body" body))))) | |
130 | #:on-error *on-toy-server-error* | |
131 | #:pass-keys '(quit interrupt)))) | |
132 | (close-port sock)) ; FIXME: keep socket alive. requires select? | |
e414bf21 AW |
133 | |
134 | (define* (run-server handler | |
135 | #:key | |
136 | (host #f) | |
137 | (family AF_INET) | |
138 | (addr (if host | |
139 | (inet-pton family host) | |
140 | INADDR_LOOPBACK)) | |
141 | (port 8080) | |
142 | (server-socket (make-default-socket family addr port))) | |
143 | (listen server-socket 5) | |
144 | (let lp ((client (accept-new-client server-socket))) | |
145 | ;; If client is false, we are shutting down. | |
146 | (if client | |
147 | (let ((client-socket (car client)) | |
148 | (client-addr (cdr client))) | |
149 | (catch 'interrupt | |
150 | (lambda () | |
151 | (call-with-sigint | |
152 | (lambda () | |
153 | (serve-client handler client-socket client-addr)))) | |
154 | (lambda (k . args) | |
155 | (warn "Interrupt while serving client") | |
d41c62f5 | 156 | (close-port client-socket))) |
e414bf21 | 157 | (lp (accept-new-client server-socket)))))) |