add generic web server with http-over-tcp backend
[bpt/guile.git] / module / web / toy-server.scm
CommitLineData
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))))))