client.scm 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. ;;; Web client
  2. ;; Copyright (C) 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. ;;; 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 par-map or futures.
  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 rdelim)
  34. #:use-module (web request)
  35. #:use-module (web response)
  36. #:use-module (web uri)
  37. #:export (open-socket-for-uri
  38. http-get))
  39. (define (open-socket-for-uri uri)
  40. (let* ((ai (car (getaddrinfo (uri-host uri)
  41. (cond
  42. ((uri-port uri) => number->string)
  43. (else (symbol->string (uri-scheme uri)))))))
  44. (s (socket (addrinfo:fam ai) (addrinfo:socktype ai)
  45. (addrinfo:protocol ai))))
  46. (set-port-encoding! s "ISO-8859-1")
  47. (connect s (addrinfo:addr ai))
  48. ;; Buffer input and output on this port.
  49. (setvbuf s _IOFBF)
  50. ;; Enlarge the receive buffer.
  51. (setsockopt s SOL_SOCKET SO_RCVBUF (* 12 1024))
  52. s))
  53. (define (decode-string bv encoding)
  54. (if (string-ci=? encoding "utf-8")
  55. (utf8->string bv)
  56. (let ((p (open-bytevector-input-port bv)))
  57. (set-port-encoding! p encoding)
  58. (let ((res (read-delimited "" p)))
  59. (close-port p)
  60. res))))
  61. (define (text-type? type)
  62. (let ((type (symbol->string type)))
  63. (or (string-prefix? "text/" type)
  64. (string-suffix? "/xml" type)
  65. (string-suffix? "+xml" type))))
  66. ;; Logically the inverse of (web server)'s `sanitize-response'.
  67. ;;
  68. (define (decode-response-body response body)
  69. ;; `body' is either #f or a bytevector.
  70. (cond
  71. ((not body) body)
  72. ((bytevector? body)
  73. (let ((rlen (response-content-length response))
  74. (blen (bytevector-length body)))
  75. (cond
  76. ((and rlen (not (= rlen blen)))
  77. (error "bad content-length" rlen blen))
  78. ((response-content-type response)
  79. => (lambda (type)
  80. (cond
  81. ((text-type? (car type))
  82. (decode-string body (or (assq-ref (cdr type) 'charset)
  83. "iso-8859-1")))
  84. (else body))))
  85. (else body))))
  86. (else
  87. (error "unexpected body type" body))))
  88. (define* (http-get uri #:key (port (open-socket-for-uri uri))
  89. (version '(1 . 1)) (keep-alive? #f) (extra-headers '())
  90. (decode-body? #t))
  91. (let ((req (build-request uri #:version version
  92. #:headers (if keep-alive?
  93. extra-headers
  94. (cons '(connection close)
  95. extra-headers)))))
  96. (write-request req port)
  97. (force-output port)
  98. (if (not keep-alive?)
  99. (shutdown port 1))
  100. (let* ((res (read-response port))
  101. (body (read-response-body res)))
  102. (if (not keep-alive?)
  103. (close-port port))
  104. (values res
  105. (if decode-body?
  106. (decode-response-body res body)
  107. body)))))