Some tweaks to the R6RS support
[bpt/guile.git] / module / web / response.scm
CommitLineData
a9eeb2f4
AW
1;;; HTTP response objects
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 response)
23 #:use-module (rnrs bytevectors)
24 #:use-module (rnrs io ports)
25 #:use-module (ice-9 rdelim)
26 #:use-module (srfi srfi-9)
27 #:use-module (web http)
28 #:export (response?
29 response-version
30 response-code
31 response-reason-phrase
32 response-headers
33 response-port
34 read-response
35 build-response
3d959779 36 extend-response
a9eeb2f4
AW
37 write-response
38
39 read-response-body/latin-1
40 write-response-body/latin-1
41
42 read-response-body/bytevector
43 write-response-body/bytevector
44
45 ;; General headers
46 ;;
47 response-cache-control
48 response-connection
49 response-date
50 response-pragma
51 response-trailer
52 response-transfer-encoding
53 response-upgrade
54 response-via
55 response-warning
56
57 ;; Entity headers
58 ;;
59 response-allow
60 response-content-encoding
61 response-content-language
62 response-content-length
63 response-content-location
64 response-content-md5
65 response-content-range
66 response-content-type
67 response-expires
68 response-last-modified
69
70 ;; Response headers
71 ;;
72 response-accept-ranges
73 response-age
74 response-etag
75 response-location
76 response-proxy-authenticate
77 response-retry-after
78 response-server
79 response-vary
80 response-www-authenticate))
81
82
83(define-record-type <response>
84 (make-response version code reason-phrase headers port)
85 response?
86 (version response-version)
87 (code response-code)
88 (reason-phrase %response-reason-phrase)
89 (headers response-headers)
90 (port response-port))
91
92(define (bad-response message . args)
93 (throw 'bad-response message args))
94
95(define* (build-response #:key (version '(1 . 1)) (code 200) reason-phrase
96 (headers '()) port)
97 (make-response version code reason-phrase headers port))
98
3d959779
AW
99(define (extend-response r k v . additional)
100 (let ((r (build-response #:version (response-version r)
101 #:code (response-code r)
102 #:reason-phrase (%response-reason-phrase r)
103 #:headers
104 (assoc-set! (copy-tree (response-headers r))
105 k v)
106 #:port (response-port r))))
107 (if (null? additional)
108 r
109 (apply extend-response r additional))))
110
a9eeb2f4
AW
111(define *reason-phrases*
112 '((100 . "Continue")
113 (101 . "Switching Protocols")
114 (200 . "OK")
115 (201 . "Created")
116 (202 . "Accepted")
117 (203 . "Non-Authoritative Information")
118 (204 . "No Content")
119 (205 . "Reset Content")
120 (206 . "Partial Content")
121 (300 . "Multiple Choices")
122 (301 . "Moved Permanently")
123 (302 . "Found")
124 (303 . "See Other")
125 (304 . "Not Modified")
126 (305 . "Use Proxy")
127 (307 . "Temporary Redirect")
128 (400 . "Bad Request")
129 (401 . "Unauthorized")
130 (402 . "Payment Required")
131 (403 . "Forbidden")
132 (404 . "Not Found")
133 (405 . "Method Not Allowed")
134 (406 . "Not Acceptable")
135 (407 . "Proxy Authentication Required")
136 (408 . "Request Timeout")
137 (409 . "Conflict")
138 (410 . "Gone")
139 (411 . "Length Required")
140 (412 . "Precondition Failed")
141 (413 . "Request Entity Too Large")
142 (414 . "Request-URI Too Long")
143 (415 . "Unsupported Media Type")
144 (416 . "Requested Range Not Satisfiable")
145 (417 . "Expectation Failed")
146 (500 . "Internal Server Error")
147 (501 . "Not Implemented")
148 (502 . "Bad Gateway")
149 (503 . "Service Unavailable")
150 (504 . "Gateway Timeout")
151 (505 . "HTTP Version Not Supported")))
152
153(define (code->reason-phrase code)
154 (or (assv-ref *reason-phrases* code)
155 "(Unknown)"))
156
157(define (response-reason-phrase response)
158 (or (%response-reason-phrase response)
159 (code->reason-phrase (response-code response))))
160
161(define (read-response port)
162 (set-port-encoding! port "ISO-8859-1")
163 (call-with-values (lambda () (read-response-line port))
164 (lambda (version code reason-phrase)
165 (make-response version code reason-phrase (read-headers port) port))))
166
167(define (write-response r port)
168 (write-response-line (response-version r) (response-code r)
169 (response-reason-phrase r) port)
170 (write-headers (response-headers r) port)
171 (display "\r\n" port)
172 (if (eq? port (response-port r))
173 r
174 (make-response (response-version r) (response-code r)
175 (response-reason-phrase r) (response-headers r) port)))
176
177;; Probably not what you want to use "in production". Relies on one byte
178;; per char because we are in latin-1 encoding.
179;;
180(define (read-response-body/latin-1 r)
181 (let ((nbytes (response-content-length r)))
182 (and nbytes
183 (let ((buf (make-string nbytes)))
184 (read-delimited! "" buf (response-port r))
185 buf))))
186
187;; Likewise, assumes that body can be written in the latin-1 encoding,
188;; and that the latin-1 encoding is what is expected by the server.
189;;
190(define (write-response-body/latin-1 r body)
191 (display body (response-port r)))
192
193(define (read-response-body/bytevector r)
194 (let ((nbytes (response-content-length r)))
195 (and nbytes
196 (let ((bv (get-bytevector-n (response-port r) nbytes)))
197 (if (= (bytevector-length bv) nbytes)
198 bv
199 (bad-response "EOF while reading response body: ~a bytes of ~a"
200 (bytevector-length bv) nbytes))))))
201
202(define (write-response-body/bytevector r bv)
203 (put-bytevector (response-port r) bv))
204
205(define-syntax define-response-accessor
206 (lambda (x)
207 (syntax-case x ()
208 ((_ field)
209 #'(define-response-accessor field #f))
210 ((_ field def) (identifier? #'field)
211 #`(define* (#,(datum->syntax
212 #'field
213 (symbol-append 'response- (syntax->datum #'field)))
214 response
215 #:optional (default def))
216 (cond
217 ((assq 'field (response-headers response)) => cdr)
218 (else default)))))))
219
220;; General headers
221;;
222(define-response-accessor cache-control '())
223(define-response-accessor connection '())
224(define-response-accessor date #f)
225(define-response-accessor pragma '())
226(define-response-accessor trailer '())
227(define-response-accessor transfer-encoding '())
228(define-response-accessor upgrade '())
229(define-response-accessor via '())
230(define-response-accessor warning '())
231
232;; Entity headers
233;;
234(define-response-accessor allow '())
235(define-response-accessor content-encoding '())
236(define-response-accessor content-language '())
237(define-response-accessor content-length #f)
238(define-response-accessor content-location #f)
239(define-response-accessor content-md5 #f)
240(define-response-accessor content-range #f)
241(define-response-accessor content-type #f)
242(define-response-accessor expires #f)
243(define-response-accessor last-modified #f)
244
245;; Response headers
246;;
247(define-response-accessor accept-ranges #f)
248(define-response-accessor age #f)
249(define-response-accessor etag #f)
250(define-response-accessor location #f)
251(define-response-accessor proxy-authenticate #f)
252(define-response-accessor retry-after #f)
253(define-response-accessor server #f)
254(define-response-accessor vary '())
255(define-response-accessor www-authenticate #f)