http.scm 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2015, 2016, 2017, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;;
  4. ;;; This file is part of GNU Guix.
  5. ;;;
  6. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  7. ;;; under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation; either version 3 of the License, or (at
  9. ;;; your option) any later version.
  10. ;;;
  11. ;;; GNU Guix is distributed in the hope that it will be useful, but
  12. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (guix tests http)
  19. #:use-module (ice-9 threads)
  20. #:use-module (web server)
  21. #:use-module (web server http)
  22. #:use-module (web response)
  23. #:use-module (srfi srfi-39)
  24. #:use-module (ice-9 match)
  25. #:export (with-http-server
  26. call-with-http-server
  27. %http-server-port
  28. http-server-can-listen?
  29. %local-url))
  30. ;;; Commentary:
  31. ;;;
  32. ;;; Code to spawn a Web server for testing purposes.
  33. ;;;
  34. ;;; Code:
  35. (define %http-server-port
  36. ;; TCP port to use for the stub HTTP server.
  37. (make-parameter 9999))
  38. (define (open-http-server-socket)
  39. "Return a listening socket for the web server. It is useful to export it so
  40. that tests can check whether we succeeded opening the socket and tests skip if
  41. needed."
  42. (catch 'system-error
  43. (lambda ()
  44. (let ((sock (socket PF_INET SOCK_STREAM 0)))
  45. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  46. (bind sock
  47. (make-socket-address AF_INET INADDR_LOOPBACK
  48. (%http-server-port)))
  49. sock))
  50. (lambda args
  51. (let ((err (system-error-errno args)))
  52. (format (current-error-port)
  53. "warning: cannot run Web server for tests: ~a~%"
  54. (strerror err))
  55. #f))))
  56. (define (http-server-can-listen?)
  57. "Return #t if we managed to open a listening socket."
  58. (and=> (open-http-server-socket)
  59. (lambda (socket)
  60. (close-port socket)
  61. #t)))
  62. (define* (%local-url #:optional (port (%http-server-port)))
  63. ;; URL to use for 'home-page' tests.
  64. (string-append "http://localhost:" (number->string port)
  65. "/foo/bar"))
  66. (define* (call-with-http-server responses+data thunk)
  67. "Call THUNK with an HTTP server running and returning RESPONSES+DATA on HTTP
  68. requests. Each element of RESPONSES+DATA must be a tuple containing a
  69. response and a string, or an HTTP response code and a string."
  70. (define responses
  71. (map (match-lambda
  72. (((? response? response) data)
  73. (list response data))
  74. (((? integer? code) data)
  75. (list (build-response #:code code
  76. #:reason-phrase "Such is life")
  77. data)))
  78. responses+data))
  79. (define (http-write server client response body)
  80. "Write RESPONSE."
  81. (let* ((response (write-response response client))
  82. (port (response-port response)))
  83. (cond
  84. ((not body)) ;pass
  85. (else
  86. (write-response-body response body)))
  87. (close-port port)
  88. (when (null? responses)
  89. (quit #t)) ;exit the server thread
  90. (values)))
  91. ;; Mutex and condition variable to synchronize with the HTTP server.
  92. (define %http-server-lock (make-mutex))
  93. (define %http-server-ready (make-condition-variable))
  94. (define (http-open . args)
  95. "Start listening for HTTP requests and signal %HTTP-SERVER-READY."
  96. (with-mutex %http-server-lock
  97. (let ((result (apply (@@ (web server http) http-open) args)))
  98. (signal-condition-variable %http-server-ready)
  99. result)))
  100. (define-server-impl stub-http-server
  101. ;; Stripped-down version of Guile's built-in HTTP server.
  102. http-open
  103. (@@ (web server http) http-read)
  104. http-write
  105. (@@ (web server http) http-close))
  106. (define (server-body)
  107. (define (handle request body)
  108. (match responses
  109. (((response data) rest ...)
  110. (set! responses rest)
  111. (values response data))))
  112. (let ((socket (open-http-server-socket)))
  113. (catch 'quit
  114. (lambda ()
  115. (run-server handle stub-http-server
  116. `(#:socket ,socket)))
  117. (lambda _
  118. (close-port socket)))))
  119. (with-mutex %http-server-lock
  120. (let ((server (make-thread server-body)))
  121. (wait-condition-variable %http-server-ready %http-server-lock)
  122. ;; Normally SERVER exits automatically once it has received a request.
  123. (thunk))))
  124. (define-syntax with-http-server
  125. (syntax-rules ()
  126. ((_ responses+data body ...)
  127. (call-with-http-server responses+data (lambda () body ...)))))
  128. ;;; http.scm ends here