client.scm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. ;;; Web client
  2. ;; Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016 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. ;;; Commentary:
  18. ;;;
  19. ;;; (web client) is a simple HTTP URL fetcher for Guile.
  20. ;;;
  21. ;;; In its current incarnation, (web client) is synchronous. If you
  22. ;;; want to fetch a number of URLs at once, probably the best thing to
  23. ;;; do is to write an event-driven URL fetcher, similar in structure to
  24. ;;; the web server.
  25. ;;;
  26. ;;; Another option, good but not as performant, would be to use threads,
  27. ;;; possibly via a thread pool.
  28. ;;;
  29. ;;; Code:
  30. (define-module (web client)
  31. #:use-module (rnrs bytevectors)
  32. #:use-module (ice-9 binary-ports)
  33. #:use-module (ice-9 iconv)
  34. #:use-module (ice-9 rdelim)
  35. #:use-module (web request)
  36. #:use-module (web response)
  37. #:use-module (web uri)
  38. #:use-module (web http)
  39. #:use-module (srfi srfi-1)
  40. #:use-module (srfi srfi-9)
  41. #:use-module (srfi srfi-9 gnu)
  42. #:use-module ((rnrs io ports)
  43. #:prefix rnrs-ports:)
  44. #:export (current-http-proxy
  45. open-socket-for-uri
  46. open-connection-for-uri
  47. http-get
  48. http-get*
  49. http-head
  50. http-post
  51. http-put
  52. http-delete
  53. http-trace
  54. http-options))
  55. (define %http-receive-buffer-size
  56. ;; Size of the HTTP receive buffer.
  57. 65536)
  58. ;; Autoload GnuTLS so that this module can be used even when GnuTLS is
  59. ;; not available. At compile time, this yields "possibly unbound
  60. ;; variable" warnings, but these are OK: we know that the variables will
  61. ;; be bound if we need them, because (guix download) adds GnuTLS as an
  62. ;; input in that case.
  63. ;; XXX: Use this hack instead of #:autoload to avoid compilation errors.
  64. ;; See <http://bugs.gnu.org/12202>.
  65. (module-autoload! (current-module)
  66. '(gnutls) '(make-session connection-end/client))
  67. (define gnutls-module
  68. (delay
  69. (catch 'misc-error
  70. (lambda ()
  71. (let ((module (resolve-interface '(gnutls))))
  72. ;; In some 2.1/2.2 installations installed alongside Guile 2.0, gnutls
  73. ;; can be imported but the bindings are broken as "unknown type".
  74. ;; Here we check that gnutls-version is the right type (a procedure)
  75. ;; to make sure the bindings are ok.
  76. (if (procedure? (module-ref module 'gnutls-version))
  77. module
  78. #f)))
  79. (const #f))))
  80. (define (ensure-gnutls)
  81. (if (not (force gnutls-module))
  82. (throw 'gnutls-not-available "(gnutls) module not available")))
  83. (define current-http-proxy
  84. (make-parameter (let ((proxy (getenv "http_proxy")))
  85. (and (not (equal? proxy ""))
  86. proxy))))
  87. (define (tls-wrap port server)
  88. "Return PORT wrapped in a TLS connection to SERVER. SERVER must be a DNS
  89. host name without trailing dot."
  90. (define (log level str)
  91. (format (current-error-port)
  92. "gnutls: [~a|~a] ~a" (getpid) level str))
  93. (ensure-gnutls)
  94. (let ((session (make-session connection-end/client)))
  95. ;; Some servers such as 'cloud.github.com' require the client to support
  96. ;; the 'SERVER NAME' extension. However, 'set-session-server-name!' is
  97. ;; not available in older GnuTLS releases. See
  98. ;; <http://bugs.gnu.org/18526> for details.
  99. (if (module-defined? (force gnutls-module)
  100. 'set-session-server-name!)
  101. (set-session-server-name! session server-name-type/dns server)
  102. (format (current-error-port)
  103. "warning: TLS 'SERVER NAME' extension not supported~%"))
  104. (set-session-transport-fd! session (fileno port))
  105. (set-session-default-priority! session)
  106. ;; The "%COMPAT" bit allows us to work around firewall issues (info
  107. ;; "(gnutls) Priority Strings"); see <http://bugs.gnu.org/23311>.
  108. ;; Explicitly disable SSLv3, which is insecure:
  109. ;; <https://tools.ietf.org/html/rfc7568>.
  110. (set-session-priorities! session "NORMAL:%COMPAT:-VERS-SSL3.0")
  111. (set-session-credentials! session (make-certificate-credentials))
  112. ;; Uncomment the following lines in case of debugging emergency.
  113. ;;(set-log-level! 10)
  114. ;;(set-log-procedure! log)
  115. (handshake session)
  116. (let ((record (session-record-port session)))
  117. (define (read! bv start count)
  118. (define read-bv (get-bytevector-n record count))
  119. (if (eof-object? read-bv)
  120. 0 ; read! returns 0 on eof-object
  121. (let ((read-bv-len (bytevector-length read-bv)))
  122. (bytevector-copy! read-bv 0 bv start read-bv-len)
  123. read-bv-len)))
  124. (define (write! bv start count)
  125. (put-bytevector record bv start count)
  126. count)
  127. (define (get-position)
  128. (rnrs-ports:port-position record))
  129. (define (set-position! new-position)
  130. (rnrs-ports:set-port-position! record new-position))
  131. (define (close)
  132. (unless (port-closed? port)
  133. (close-port port))
  134. (unless (port-closed? record)
  135. (close-port record)))
  136. (make-custom-binary-input/output-port "gnutls wrapped port" read! write!
  137. get-position set-position!
  138. close))))
  139. (define (ensure-uri uri-or-string)
  140. (cond
  141. ((string? uri-or-string) (string->uri uri-or-string))
  142. ((uri? uri-or-string) uri-or-string)
  143. (else (error "Invalid URI" uri-or-string))))
  144. (define (open-socket-for-uri uri-or-string)
  145. "Return an open input/output port for a connection to URI."
  146. (define http-proxy (current-http-proxy))
  147. (define uri (ensure-uri (or http-proxy uri-or-string)))
  148. (define addresses
  149. (let ((port (uri-port uri)))
  150. (delete-duplicates
  151. (getaddrinfo (uri-host uri)
  152. (cond (port => number->string)
  153. ((uri-scheme uri) => symbol->string)
  154. (else (error "Not an absolute URI" uri)))
  155. (if port
  156. AI_NUMERICSERV
  157. 0))
  158. (lambda (ai1 ai2)
  159. (equal? (addrinfo:addr ai1) (addrinfo:addr ai2))))))
  160. (define https?
  161. (eq? 'https (uri-scheme uri)))
  162. (define (open-socket)
  163. (let loop ((addresses addresses))
  164. (let* ((ai (car addresses))
  165. (s (with-fluids ((%default-port-encoding #f))
  166. ;; Restrict ourselves to TCP.
  167. (socket (addrinfo:fam ai) SOCK_STREAM IPPROTO_IP))))
  168. (catch 'system-error
  169. (lambda ()
  170. (connect s (addrinfo:addr ai))
  171. ;; Buffer input and output on this port.
  172. (setvbuf s 'block)
  173. ;; If we're using a proxy, make a note of that.
  174. (when http-proxy (set-http-proxy-port?! s #t))
  175. s)
  176. (lambda args
  177. ;; Connection failed, so try one of the other addresses.
  178. (close s)
  179. (if (null? (cdr addresses))
  180. (apply throw args)
  181. (loop (cdr addresses))))))))
  182. (let-syntax ((with-https-proxy
  183. (syntax-rules ()
  184. ((_ exp)
  185. ;; For HTTPS URIs, honor 'https_proxy', not 'http_proxy'.
  186. ;; FIXME: Proxying is not supported for https.
  187. (let ((thunk (lambda () exp)))
  188. (if (and https?
  189. current-http-proxy)
  190. (parameterize ((current-http-proxy #f))
  191. (when (and=> (getenv "https_proxy")
  192. (negate string-null?))
  193. (format (current-error-port)
  194. "warning: 'https_proxy' is ignored~%"))
  195. (thunk))
  196. (thunk)))))))
  197. (with-https-proxy
  198. (let ((s (open-socket)))
  199. ;; Buffer input and output on this port.
  200. (setvbuf s _IOFBF %http-receive-buffer-size)
  201. (if https?
  202. (tls-wrap s (uri-host uri))
  203. s)))))
  204. (define (extend-request r k v . additional)
  205. (let ((r (set-field r (request-headers)
  206. (assoc-set! (copy-tree (request-headers r))
  207. k v))))
  208. (if (null? additional)
  209. r
  210. (apply extend-request r additional))))
  211. ;; -> request body
  212. (define (sanitize-request request body)
  213. "\"Sanitize\" the given request and body, ensuring that they are
  214. complete and coherent. This method is most useful for methods that send
  215. data to the server, like POST, but can be used for any method. Return
  216. two values: a request and a bytevector, possibly the same ones that were
  217. passed as arguments.
  218. If BODY is a string, encodes the string to a bytevector, in an encoding
  219. appropriate for REQUEST. Adds a ‘content-length’ and ‘content-type’
  220. header, as necessary.
  221. If BODY is a procedure, it is called with a port as an argument, and the
  222. output collected as a bytevector. In the future we might try to instead
  223. use a compressing, chunk-encoded port, and call this procedure later.
  224. Authors are advised not to rely on the procedure being called at any
  225. particular time.
  226. Note that we rely on the request itself already having been validated,
  227. as is the case by default with a request returned by `build-request'."
  228. (cond
  229. ((not body)
  230. (let ((length (request-content-length request)))
  231. (if length
  232. ;; FIXME make this stricter: content-length header should be
  233. ;; prohibited if there's no body, even if the content-length
  234. ;; is 0.
  235. (unless (zero? length)
  236. (error "content-length, but no body"))
  237. (when (assq 'transfer-encoding (request-headers request))
  238. (error "transfer-encoding not allowed with no body")))
  239. (values request #vu8())))
  240. ((string? body)
  241. (let* ((type (request-content-type request '(text/plain)))
  242. (declared-charset (assq-ref (cdr type) 'charset))
  243. (charset (or declared-charset "utf-8")))
  244. (sanitize-request
  245. (if declared-charset
  246. request
  247. (extend-request request 'content-type
  248. `(,@type (charset . ,charset))))
  249. (string->bytevector body charset))))
  250. ((procedure? body)
  251. (let* ((type (request-content-type request
  252. '(text/plain)))
  253. (declared-charset (assq-ref (cdr type) 'charset))
  254. (charset (or declared-charset "utf-8")))
  255. (sanitize-request
  256. (if declared-charset
  257. request
  258. (extend-request request 'content-type
  259. `(,@type (charset . ,charset))))
  260. (call-with-encoded-output-string charset body))))
  261. ((not (bytevector? body))
  262. (error "unexpected body type"))
  263. (else
  264. (values (let ((rlen (request-content-length request))
  265. (blen (bytevector-length body)))
  266. (cond
  267. (rlen (if (= rlen blen)
  268. request
  269. (error "bad content-length" rlen blen)))
  270. (else (extend-request request 'content-length blen))))
  271. body))))
  272. (define (decode-response-body response body)
  273. ;; `body' is either #f or a bytevector.
  274. (cond
  275. ((not body) body)
  276. ((bytevector? body)
  277. (let ((rlen (response-content-length response))
  278. (blen (bytevector-length body)))
  279. (cond
  280. ((and rlen (not (= rlen blen)))
  281. (error "bad content-length" rlen blen))
  282. ((response-content-type response)
  283. => (lambda (type)
  284. (cond
  285. ((text-content-type? (car type))
  286. ;; RFC 2616 3.7.1: "When no explicit charset parameter is
  287. ;; provided by the sender, media subtypes of the "text"
  288. ;; type are defined to have a default charset value of
  289. ;; "ISO-8859-1" when received via HTTP."
  290. (bytevector->string body (or (assq-ref (cdr type) 'charset)
  291. "iso-8859-1")))
  292. (else body))))
  293. (else body))))
  294. (else
  295. (error "unexpected body type" body))))
  296. ;; We could expose this to user code if there is demand.
  297. (define* (request uri #:key
  298. (body #f)
  299. (port (open-socket-for-uri uri))
  300. (method 'GET)
  301. (version '(1 . 1))
  302. (keep-alive? #f)
  303. (headers '())
  304. (decode-body? #t)
  305. (streaming? #f)
  306. (request
  307. (build-request
  308. (ensure-uri uri)
  309. #:method method
  310. #:version version
  311. #:headers (if keep-alive?
  312. headers
  313. (cons '(connection close) headers))
  314. #:port port)))
  315. (call-with-values (lambda () (sanitize-request request body))
  316. (lambda (request body)
  317. (let ((request (write-request request port)))
  318. (when body
  319. (write-request-body request body))
  320. (force-output (request-port request))
  321. (let ((response (read-response port)))
  322. (cond
  323. ((eq? (request-method request) 'HEAD)
  324. (unless keep-alive?
  325. (close-port port))
  326. (values response #f))
  327. (streaming?
  328. (values response
  329. (response-body-port response
  330. #:keep-alive? keep-alive?
  331. #:decode? decode-body?)))
  332. (else
  333. (let ((body (read-response-body response)))
  334. (unless keep-alive?
  335. (close-port port))
  336. (values response
  337. (if decode-body?
  338. (decode-response-body response body)
  339. body))))))))))
  340. (define* (http-get uri #:key
  341. (body #f)
  342. (port (open-socket-for-uri uri))
  343. (version '(1 . 1)) (keep-alive? #f)
  344. ;; #:headers is the new name of #:extra-headers.
  345. (extra-headers #f) (headers (or extra-headers '()))
  346. (decode-body? #t) (streaming? #f))
  347. "Connect to the server corresponding to URI and ask for the
  348. resource, using the ‘GET’ method. If you already have a port open,
  349. pass it as PORT. The port will be closed at the end of the
  350. request unless KEEP-ALIVE? is true. Any extra headers in the
  351. alist HEADERS will be added to the request.
  352. If BODY is not ‘#f’, a message body will also be sent with the HTTP
  353. request. If BODY is a string, it is encoded according to the
  354. content-type in HEADERS, defaulting to UTF-8. Otherwise BODY should be
  355. a bytevector, or ‘#f’ for no body. Although it's allowed to send a
  356. message body along with any request, usually only POST and PUT requests
  357. have bodies. See ‘http-put’ and ‘http-post’ documentation, for more.
  358. If DECODE-BODY? is true, as is the default, the body of the
  359. response will be decoded to string, if it is a textual content-type.
  360. Otherwise it will be returned as a bytevector.
  361. However, if STREAMING? is true, instead of eagerly reading the response
  362. body from the server, this function only reads off the headers. The
  363. response body will be returned as a port on which the data may be read.
  364. Unless KEEP-ALIVE? is true, the port will be closed after the full
  365. response body has been read.
  366. Returns two values: the response read from the server, and the response
  367. body as a string, bytevector, #f value, or as a port (if STREAMING? is
  368. true)."
  369. (when extra-headers
  370. (issue-deprecation-warning
  371. "The #:extra-headers argument to http-get has been renamed to #:headers. "
  372. "Please update your code."))
  373. (request uri #:method 'GET #:body body
  374. #:port port #:version version #:keep-alive? keep-alive?
  375. #:headers headers #:decode-body? decode-body?
  376. #:streaming? streaming?))
  377. (define* (http-get* uri #:key
  378. (body #f)
  379. (port (open-socket-for-uri uri))
  380. (version '(1 . 1)) (keep-alive? #f)
  381. ;; #:headers is the new name of #:extra-headers.
  382. (extra-headers #f) (headers (or extra-headers '()))
  383. (decode-body? #t))
  384. "Deprecated in favor of (http-get #:streaming? #t)."
  385. (issue-deprecation-warning
  386. "`http-get*' has been deprecated. "
  387. "Instead, use `http-get' with the #:streaming? #t keyword argument.")
  388. (http-get uri #:body body
  389. #:port port #:version version #:keep-alive? keep-alive?
  390. #:headers headers #:decode-body? #t #:streaming? #t))
  391. (define-syntax-rule (define-http-verb http-verb method doc)
  392. (define* (http-verb uri #:key
  393. (body #f)
  394. (port (open-socket-for-uri uri))
  395. (version '(1 . 1))
  396. (keep-alive? #f)
  397. (headers '())
  398. (decode-body? #t)
  399. (streaming? #f))
  400. doc
  401. (request uri
  402. #:body body #:method method
  403. #:port port #:version version #:keep-alive? keep-alive?
  404. #:headers headers #:decode-body? decode-body?
  405. #:streaming? streaming?)))
  406. (define-http-verb http-head
  407. 'HEAD
  408. "Fetch message headers for the given URI using the HTTP \"HEAD\"
  409. method.
  410. This function is similar to ‘http-get’, except it uses the \"HEAD\"
  411. method. See ‘http-get’ for full documentation on the various keyword
  412. arguments that are accepted by this function.
  413. Returns two values: the resulting response, and ‘#f’. Responses to HEAD
  414. requests do not have a body. The second value is only returned so that
  415. other procedures can treat all of the http-foo verbs identically.")
  416. (define-http-verb http-post
  417. 'POST
  418. "Post data to the given URI using the HTTP \"POST\" method.
  419. This function is similar to ‘http-get’, except it uses the \"POST\"
  420. method. See ‘http-get’ for full documentation on the various keyword
  421. arguments that are accepted by this function.
  422. Returns two values: the resulting response, and the response body.")
  423. (define-http-verb http-put
  424. 'PUT
  425. "Put data at the given URI using the HTTP \"PUT\" method.
  426. This function is similar to ‘http-get’, except it uses the \"PUT\"
  427. method. See ‘http-get’ for full documentation on the various keyword
  428. arguments that are accepted by this function.
  429. Returns two values: the resulting response, and the response body.")
  430. (define-http-verb http-delete
  431. 'DELETE
  432. "Delete data at the given URI using the HTTP \"DELETE\" method.
  433. This function is similar to ‘http-get’, except it uses the \"DELETE\"
  434. method. See ‘http-get’ for full documentation on the various keyword
  435. arguments that are accepted by this function.
  436. Returns two values: the resulting response, and the response body.")
  437. (define-http-verb http-trace
  438. 'TRACE
  439. "Send an HTTP \"TRACE\" request.
  440. This function is similar to ‘http-get’, except it uses the \"TRACE\"
  441. method. See ‘http-get’ for full documentation on the various keyword
  442. arguments that are accepted by this function.
  443. Returns two values: the resulting response, and the response body.")
  444. (define-http-verb http-options
  445. 'OPTIONS
  446. "Query characteristics of an HTTP resource using the HTTP \"OPTIONS\"
  447. method.
  448. This function is similar to ‘http-get’, except it uses the \"OPTIONS\"
  449. method. See ‘http-get’ for full documentation on the various keyword
  450. arguments that are accepted by this function.
  451. Returns two values: the resulting response, and the response body.")