add (web client)
[bpt/guile.git] / module / web / request.scm
CommitLineData
ad05d4e8
AW
1;;; HTTP request objects
2
be1be3e5 3;; Copyright (C) 2010, 2011 Free Software Foundation, Inc.
ad05d4e8
AW
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 request)
23 #:use-module (rnrs bytevectors)
6854c324 24 #:use-module (ice-9 binary-ports)
ad05d4e8
AW
25 #:use-module (ice-9 rdelim)
26 #:use-module (srfi srfi-9)
27 #:use-module (web uri)
28 #:use-module (web http)
29 #:export (request?
30 request-method
31 request-uri
32 request-version
33 request-headers
d4b6200a 34 request-meta
ad05d4e8
AW
35 request-port
36
37 read-request
38 build-request
39 write-request
40
3475fbb5
AW
41 read-request-body
42 write-request-body
ad05d4e8
AW
43
44 ;; General headers
45 ;;
46 request-cache-control
47 request-connection
48 request-date
49 request-pragma
50 request-trailer
51 request-transfer-encoding
52 request-upgrade
53 request-via
54 request-warning
55
56 ;; Entity headers
57 ;;
58 request-allow
59 request-content-encoding
60 request-content-language
61 request-content-length
62 request-content-location
63 request-content-md5
64 request-content-range
65 request-content-type
66 request-expires
67 request-last-modified
68
69 ;; Request headers
70 ;;
71 request-accept
72 request-accept-charset
73 request-accept-encoding
74 request-accept-language
75 request-authorization
76 request-expect
77 request-from
78 request-host
79 request-if-match
80 request-if-modified-since
81 request-if-none-match
82 request-if-range
83 request-if-unmodified-since
84 request-max-forwards
85 request-proxy-authorization
86 request-range
87 request-referer
88 request-te
89 request-user-agent
90
91 ;; Misc
92 request-absolute-uri))
93
94
95;;; {Character Encodings, Strings, and Bytevectors}
96;;;
97;;; Requests are read from over the wire, and as such have to be treated
98;;; very carefully.
99;;;
100;;; The header portion of the message is defined to be in a subset of
101;;; ASCII, and may be processed either byte-wise (using bytevectors and
102;;; binary I/O) or as characters in a single-byte ASCII-compatible
103;;; encoding.
104;;;
105;;; We choose the latter, processing as strings in the latin-1
106;;; encoding. This allows us to use all the read-delimited machinery,
107;;; character sets, and regular expressions, shared substrings, etc.
108;;;
109;;; The characters in the header values may themselves encode other
110;;; bytes or characters -- basically each header has its own parser. We
111;;; leave that as a header-specific topic.
112;;;
113;;; The body is present if the content-length header is present. Its
114;;; format and, if textual, encoding is determined by the headers, but
115;;; its length is encoded in bytes. So we just slurp that number of
116;;; characters in latin-1, knowing that the number of characters
117;;; corresponds to the number of bytes, and then convert to a
118;;; bytevector, perhaps for later decoding.
119;;;
120
121(define-record-type <request>
d4b6200a 122 (make-request method uri version headers meta port)
ad05d4e8
AW
123 request?
124 (method request-method)
125 (uri request-uri)
126 (version request-version)
127 (headers request-headers)
d4b6200a 128 (meta request-meta)
ad05d4e8
AW
129 (port request-port))
130
131(define (bad-request message . args)
132 (throw 'bad-request message args))
133
134(define (non-negative-integer? n)
135 (and (number? n) (>= n 0) (exact? n) (integer? n)))
136
137(define (validate-headers headers)
138 (if (pair? headers)
139 (let ((h (car headers)))
140 (if (pair? h)
141 (let ((k (car h)) (v (cdr h)))
be1be3e5
AW
142 (if (valid-header? k v)
143 (validate-headers (cdr headers))
144 (bad-request "Bad value for header ~a: ~s" k v)))
ad05d4e8
AW
145 (bad-request "Header not a pair: ~a" h)))
146 (if (not (null? headers))
147 (bad-request "Headers not a list: ~a" headers))))
148
f944ee8f 149(define* (build-request uri #:key (method 'GET) (version '(1 . 1))
d4b6200a
AW
150 (headers '()) port (meta '())
151 (validate-headers? #t))
cc1e26c2
AW
152 "Construct an HTTP request object. If @var{validate-headers?} is true,
153the headers are each run through their respective validators."
ad05d4e8
AW
154 (cond
155 ((not (and (pair? version)
156 (non-negative-integer? (car version))
157 (non-negative-integer? (cdr version))))
158 (bad-request "Bad version: ~a" version))
159 ((not (uri? uri))
160 (bad-request "Bad uri: ~a" uri))
161 ((and (not port) (memq method '(POST PUT)))
162 (bad-request "Missing port for message ~a" method))
d4b6200a
AW
163 ((not (list? meta))
164 (bad-request "Bad metadata alist" meta))
ad05d4e8
AW
165 (else
166 (if validate-headers?
167 (validate-headers headers))))
d4b6200a 168 (make-request method uri version headers meta port))
ad05d4e8 169
d4b6200a 170(define* (read-request port #:optional (meta '()))
cc1e26c2
AW
171 "Read an HTTP request from @var{port}, optionally attaching the given
172metadata, @var{meta}.
173
174As a side effect, sets the encoding on @var{port} to
175ISO-8859-1 (latin-1), so that reading one character reads one byte. See
176the discussion of character sets in \"HTTP Requests\" in the manual, for
177more information."
ad05d4e8
AW
178 (set-port-encoding! port "ISO-8859-1")
179 (call-with-values (lambda () (read-request-line port))
180 (lambda (method uri version)
d4b6200a 181 (make-request method uri version (read-headers port) meta port))))
ad05d4e8 182
d4b6200a 183;; FIXME: really return a new request?
ad05d4e8 184(define (write-request r port)
cc1e26c2
AW
185 "Write the given HTTP request to @var{port}.
186
187Returns a new request, whose @code{request-port} will continue writing
188on @var{port}, perhaps using some transfer encoding."
ad05d4e8
AW
189 (write-request-line (request-method r) (request-uri r)
190 (request-version r) port)
191 (write-headers (request-headers r) port)
192 (display "\r\n" port)
193 (if (eq? port (request-port r))
194 r
195 (make-request (request-method r) (request-uri r) (request-version r)
d4b6200a 196 (request-headers r) (request-meta r) port)))
ad05d4e8 197
3475fbb5 198(define (read-request-body r)
cc1e26c2
AW
199 "Reads the request body from @var{r}, as a bytevector. Returns
200@code{#f} if there was no request body."
ad05d4e8
AW
201 (let ((nbytes (request-content-length r)))
202 (and nbytes
203 (let ((bv (get-bytevector-n (request-port r) nbytes)))
204 (if (= (bytevector-length bv) nbytes)
205 bv
206 (bad-request "EOF while reading request body: ~a bytes of ~a"
207 (bytevector-length bv) nbytes))))))
208
3475fbb5 209(define (write-request-body r bv)
cc1e26c2
AW
210 "Write @var{body}, a bytevector, to the port corresponding to the HTTP
211request @var{r}."
ad05d4e8
AW
212 (put-bytevector (request-port r) bv))
213
214(define-syntax define-request-accessor
215 (lambda (x)
216 (syntax-case x ()
217 ((_ field)
218 #'(define-request-accessor field #f))
219 ((_ field def) (identifier? #'field)
220 #`(define* (#,(datum->syntax
221 #'field
222 (symbol-append 'request- (syntax->datum #'field)))
223 request
224 #:optional (default def))
225 (cond
226 ((assq 'field (request-headers request)) => cdr)
227 (else default)))))))
228
229;; General headers
230;;
231(define-request-accessor cache-control '())
232(define-request-accessor connection '())
233(define-request-accessor date #f)
234(define-request-accessor pragma '())
235(define-request-accessor trailer '())
236(define-request-accessor transfer-encoding '())
237(define-request-accessor upgrade '())
238(define-request-accessor via '())
239(define-request-accessor warning '())
240
241;; Entity headers
242;;
243(define-request-accessor allow '())
244(define-request-accessor content-encoding '())
245(define-request-accessor content-language '())
246(define-request-accessor content-length #f)
247(define-request-accessor content-location #f)
248(define-request-accessor content-md5 #f)
249(define-request-accessor content-range #f)
250(define-request-accessor content-type #f)
251(define-request-accessor expires #f)
252(define-request-accessor last-modified #f)
253
254;; Request headers
255;;
256(define-request-accessor accept '())
257(define-request-accessor accept-charset '())
258(define-request-accessor accept-encoding '())
259(define-request-accessor accept-language '())
260(define-request-accessor authorization #f)
261(define-request-accessor expect '())
262(define-request-accessor from #f)
263(define-request-accessor host #f)
264;; Absence of an if-directive appears to be different from `*'.
265(define-request-accessor if-match #f)
266(define-request-accessor if-modified-since #f)
267(define-request-accessor if-none-match #f)
268(define-request-accessor if-range #f)
269(define-request-accessor if-unmodified-since #f)
270(define-request-accessor max-forwards #f)
271(define-request-accessor proxy-authorization #f)
272(define-request-accessor range #f)
273(define-request-accessor referer #f)
274(define-request-accessor te '())
275(define-request-accessor user-agent #f)
276
277;; Misc accessors
278(define* (request-absolute-uri r #:optional default-host default-port)
279 (let ((uri (request-uri r)))
280 (if (uri-host uri)
281 uri
282 (let ((host
283 (or (request-host r)
284 (if default-host
285 (cons default-host default-port)
286 (bad-request
287 "URI not absolute, no Host header, and no default: ~s"
288 uri)))))
289 (build-uri (uri-scheme uri)
290 #:host (car host)
291 #:port (cdr host)
292 #:path (uri-path uri)
293 #:query (uri-query uri)
294 #:fragment (uri-fragment uri))))))