request.scm 10 KB

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