You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

244 lines
9.9 KiB

  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2012 Free Software Foundation, Inc.
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix http-client)
  20. #:use-module (guix utils)
  21. #:use-module (web uri)
  22. #:use-module (web client)
  23. #:use-module (web response)
  24. #:use-module (srfi srfi-11)
  25. #:use-module (srfi srfi-34)
  26. #:use-module (srfi srfi-35)
  27. #:use-module (rnrs io ports)
  28. #:use-module (rnrs bytevectors)
  29. #:use-module (guix ui)
  30. #:use-module (guix utils)
  31. #:export (&http-get-error
  32. http-get-error?
  33. http-get-error-uri
  34. http-get-error-code
  35. http-get-error-reason
  36. open-socket-for-uri
  37. http-fetch))
  38. ;;; Commentary:
  39. ;;;
  40. ;;; HTTP client portable among Guile versions, and with proper error condition
  41. ;;; reporting.
  42. ;;;
  43. ;;; Code:
  44. ;; HTTP GET error.
  45. (define-condition-type &http-get-error &error
  46. http-get-error?
  47. (uri http-get-error-uri) ; URI
  48. (code http-get-error-code) ; integer
  49. (reason http-get-error-reason)) ; string
  50. (define-syntax when-guile<=2.0.5
  51. (lambda (s)
  52. (syntax-case s ()
  53. ((_ body ...)
  54. ;; Always emit BODY, regardless of VERSION, because sometimes this code
  55. ;; might be compiled with a recent Guile and run with 2.0.5---e.g.,
  56. ;; when using "guix pull".
  57. #'(begin body ...)))))
  58. (when-guile<=2.0.5
  59. ;; Backport of Guile commit 312e79f8 ("Add HTTP Chunked Encoding support to
  60. ;; web modules.").
  61. (use-modules (ice-9 rdelim))
  62. ;; Chunked Responses
  63. (define (read-chunk-header port)
  64. (let* ((str (read-line port))
  65. (extension-start (string-index str (lambda (c) (or (char=? c #\;)
  66. (char=? c #\return)))))
  67. (size (string->number (if extension-start ; unnecessary?
  68. (substring str 0 extension-start)
  69. str)
  70. 16)))
  71. size))
  72. (define (read-chunk port)
  73. (let ((size (read-chunk-header port)))
  74. (read-chunk-body port size)))
  75. (define (read-chunk-body port size)
  76. (let ((bv (get-bytevector-n port size)))
  77. (get-u8 port) ; CR
  78. (get-u8 port) ; LF
  79. bv))
  80. (define* (make-chunked-input-port port #:key (keep-alive? #f))
  81. "Returns a new port which translates HTTP chunked transfer encoded
  82. data from PORT into a non-encoded format. Returns eof when it has
  83. read the final chunk from PORT. This does not necessarily mean
  84. that there is no more data on PORT. When the returned port is
  85. closed it will also close PORT, unless the KEEP-ALIVE? is true."
  86. (define (next-chunk)
  87. (read-chunk port))
  88. (define finished? #f)
  89. (define (close)
  90. (unless keep-alive?
  91. (close-port port)))
  92. (define buffer #vu8())
  93. (define buffer-size 0)
  94. (define buffer-pointer 0)
  95. (define (read! bv idx to-read)
  96. (define (loop to-read num-read)
  97. (cond ((or finished? (zero? to-read))
  98. num-read)
  99. ((<= to-read (- buffer-size buffer-pointer))
  100. (bytevector-copy! buffer buffer-pointer
  101. bv (+ idx num-read)
  102. to-read)
  103. (set! buffer-pointer (+ buffer-pointer to-read))
  104. (loop 0 (+ num-read to-read)))
  105. (else
  106. (let ((n (- buffer-size buffer-pointer)))
  107. (bytevector-copy! buffer buffer-pointer
  108. bv (+ idx num-read)
  109. n)
  110. (set! buffer (next-chunk))
  111. (set! buffer-pointer 0)
  112. (set! buffer-size (bytevector-length buffer))
  113. (set! finished? (= buffer-size 0))
  114. (loop (- to-read n)
  115. (+ num-read n))))))
  116. (loop to-read 0))
  117. (make-custom-binary-input-port "chunked input port" read! #f #f close))
  118. (define (read-response-body* r)
  119. "Reads the response body from @var{r}, as a bytevector. Returns
  120. @code{#f} if there was no response body."
  121. (define bad-response
  122. (@@ (web response) bad-response))
  123. (if (member '(chunked) (response-transfer-encoding r))
  124. (let ((chunk-port (make-chunked-input-port (response-port r)
  125. #:keep-alive? #t)))
  126. (get-bytevector-all chunk-port))
  127. (let ((nbytes (response-content-length r)))
  128. ;; Backport of Guile commit 84dfde82ae8f6ec247c1c147c1e2ae50b207bad9
  129. ;; ("fix response-body-port for responses without content-length").
  130. (if nbytes
  131. (let ((bv (get-bytevector-n (response-port r) nbytes)))
  132. (if (= (bytevector-length bv) nbytes)
  133. bv
  134. (bad-response "EOF while reading response body: ~a bytes of ~a"
  135. (bytevector-length bv) nbytes)))
  136. (get-bytevector-all (response-port r))))))
  137. ;; Install this patch only on Guile 2.0.5.
  138. (unless (guile-version>? "2.0.5")
  139. (module-set! (resolve-module '(web response))
  140. 'read-response-body read-response-body*)))
  141. ;; XXX: Work around <http://bugs.gnu.org/13095>, present in Guile
  142. ;; up to 2.0.7.
  143. (module-define! (resolve-module '(web client))
  144. 'shutdown (const #f))
  145. (define* (open-socket-for-uri uri #:key (buffered? #t))
  146. "Return an open port for URI. When BUFFERED? is false, the returned port is
  147. unbuffered."
  148. (define rmem-max
  149. ;; The maximum size for a receive buffer on Linux, see socket(7).
  150. "/proc/sys/net/core/rmem_max")
  151. (define buffer-size
  152. (if (file-exists? rmem-max)
  153. (call-with-input-file rmem-max read)
  154. 126976)) ; the default for Linux, per 'rmem_default'
  155. (let ((s ((@ (web client) open-socket-for-uri) uri)))
  156. ;; Work around <http://bugs.gnu.org/15368> by restoring a decent
  157. ;; buffer size.
  158. (setsockopt s SOL_SOCKET SO_RCVBUF buffer-size)
  159. (unless buffered?
  160. (setvbuf s _IONBF))
  161. s))
  162. (define* (http-fetch uri #:key port (text? #f) (buffered? #t))
  163. "Return an input port containing the data at URI, and the expected number of
  164. bytes available or #f. If TEXT? is true, the data at URI is considered to be
  165. textual. Follow any HTTP redirection. When BUFFERED? is #f, return an
  166. unbuffered port, suitable for use in `filtered-port'.
  167. Raise an '&http-get-error' condition if downloading fails."
  168. (let loop ((uri uri))
  169. (let ((port (or port
  170. (open-socket-for-uri uri
  171. #:buffered? buffered?))))
  172. (let*-values (((resp data)
  173. ;; Try hard to use the API du jour to get an input port.
  174. ;; On Guile 2.0.5 and before, we can only get a string or
  175. ;; bytevector, and not an input port. Work around that.
  176. (if (guile-version>? "2.0.7")
  177. (http-get uri #:streaming? #t #:port port) ; 2.0.9+
  178. (if (defined? 'http-get*)
  179. (http-get* uri #:decode-body? text?
  180. #:port port) ; 2.0.7
  181. (http-get uri #:decode-body? text?
  182. #:port port)))) ; 2.0.5-
  183. ((code)
  184. (response-code resp)))
  185. (case code
  186. ((200)
  187. (let ((len (response-content-length resp)))
  188. (cond ((not data)
  189. (begin
  190. ;; Guile 2.0.5 and earlier did not support chunked
  191. ;; transfer encoding, which is required for instance when
  192. ;; fetching %PACKAGE-LIST-URL (see
  193. ;; <http://lists.gnu.org/archive/html/guile-devel/2011-09/msg00089.html>).
  194. ;; Normally the `when-guile<=2.0.5' block above fixes
  195. ;; that, but who knows what could happen.
  196. (warning (_ "using Guile ~a, which does not support ~s encoding~%")
  197. (version)
  198. (response-transfer-encoding resp))
  199. (leave (_ "download failed; use a newer Guile~%")
  200. uri resp)))
  201. ((string? data) ; `http-get' from 2.0.5-
  202. (values (open-input-string data) len))
  203. ((bytevector? data) ; likewise
  204. (values (open-bytevector-input-port data) len))
  205. (else ; input port
  206. (values data len)))))
  207. ((301 ; moved permanently
  208. 302) ; found (redirection)
  209. (let ((uri (response-location resp)))
  210. (close-port port)
  211. (format #t (_ "following redirection to `~a'...~%")
  212. (uri->string uri))
  213. (loop uri)))
  214. (else
  215. (raise (condition (&http-get-error
  216. (uri uri)
  217. (code code)
  218. (reason (response-reason-phrase resp)))
  219. (&message
  220. (message "download failed"))))))))))
  221. ;;; http-client.scm ends here