http.scm 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2015, 2016, 2017, 2019 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2021 Maxime Devos <maximedevos@telenet.be>
  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 tests http)
  20. #:use-module (ice-9 threads)
  21. #:use-module (web server)
  22. #:use-module (web server http)
  23. #:use-module (web response)
  24. #:use-module (srfi srfi-11)
  25. #:use-module (srfi srfi-39)
  26. #:use-module (ice-9 match)
  27. #:export (with-http-server
  28. call-with-http-server
  29. %http-server-port
  30. %local-url))
  31. ;;; Commentary:
  32. ;;;
  33. ;;; Code to spawn a Web server for testing purposes.
  34. ;;;
  35. ;;; Code:
  36. (define %http-server-port
  37. ;; TCP port to use for the stub HTTP server.
  38. ;; If 0, the OS will automatically choose
  39. ;; a port.
  40. (make-parameter 0))
  41. (define (open-http-server-socket)
  42. "Return a listening socket for the web server and the port
  43. actually listened at (in case %http-server-port was 0)."
  44. (catch 'system-error
  45. (lambda ()
  46. (let ((sock (socket PF_INET SOCK_STREAM 0)))
  47. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  48. (bind sock
  49. (make-socket-address AF_INET INADDR_LOOPBACK
  50. (%http-server-port)))
  51. (values sock
  52. (sockaddr:port (getsockname sock)))))
  53. (lambda args
  54. (let ((err (system-error-errno args)))
  55. (format (current-error-port)
  56. "warning: cannot run Web server for tests: ~a~%"
  57. (strerror err))
  58. (values #f #f)))))
  59. (define* (%local-url #:optional (port (%http-server-port)))
  60. (when (= port 0)
  61. (error "no web server is running!"))
  62. ;; URL to use for 'home-page' tests.
  63. (string-append "http://localhost:" (number->string port)
  64. "/foo/bar"))
  65. (define* (call-with-http-server responses+data thunk)
  66. "Call THUNK with an HTTP server running and returning RESPONSES+DATA on HTTP
  67. requests. Each element of RESPONSES+DATA must be a tuple containing a
  68. response and a string, or an HTTP response code and a string.
  69. %http-server-port will be set to the port listened at
  70. The port listened at will be set for the dynamic extent of THUNK."
  71. (define responses
  72. (map (match-lambda
  73. (((? response? response) data)
  74. (list response data))
  75. (((? integer? code) data)
  76. (list (build-response #:code code
  77. #:reason-phrase "Such is life")
  78. data)))
  79. responses+data))
  80. (define (http-write server client response body)
  81. "Write RESPONSE."
  82. (let* ((response (write-response response client))
  83. (port (response-port response)))
  84. (cond
  85. ((not body)) ;pass
  86. (else
  87. (write-response-body response body)))
  88. (close-port port)
  89. (when (null? responses)
  90. (quit #t)) ;exit the server thread
  91. (values)))
  92. ;; Mutex and condition variable to synchronize with the HTTP server.
  93. (define %http-server-lock (make-mutex))
  94. (define %http-server-ready (make-condition-variable))
  95. (define %http-real-server-port #f)
  96. (define (http-open . args)
  97. "Start listening for HTTP requests and signal %HTTP-SERVER-READY."
  98. (with-mutex %http-server-lock
  99. (let ((result (apply (@@ (web server http) http-open) args)))
  100. (signal-condition-variable %http-server-ready)
  101. result)))
  102. (define-server-impl stub-http-server
  103. ;; Stripped-down version of Guile's built-in HTTP server.
  104. http-open
  105. (@@ (web server http) http-read)
  106. http-write
  107. (@@ (web server http) http-close))
  108. (define (server-body)
  109. (define (handle request body)
  110. (match responses
  111. (((response data) rest ...)
  112. (set! responses rest)
  113. (values response data))))
  114. (let-values (((socket port) (open-http-server-socket)))
  115. (set! %http-real-server-port port)
  116. (catch 'quit
  117. (lambda ()
  118. (run-server handle stub-http-server
  119. `(#:socket ,socket)))
  120. (lambda _
  121. (close-port socket)))))
  122. (with-mutex %http-server-lock
  123. (let ((server (make-thread server-body)))
  124. (wait-condition-variable %http-server-ready %http-server-lock)
  125. ;; Normally SERVER exits automatically once it has received a request.
  126. (parameterize ((%http-server-port %http-real-server-port))
  127. (thunk)))))
  128. (define-syntax with-http-server
  129. (syntax-rules ()
  130. ((_ responses+data body ...)
  131. (call-with-http-server responses+data (lambda () body ...)))))
  132. ;;; http.scm ends here