fiberized.scm 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. ;;; Web I/O: Non-blocking HTTP
  2. ;; SPDX-License-Identifier: LGPL-3.0-or-later
  3. ;; Copyright (C) 2012, 2018 Free Software Foundation, Inc.
  4. ;; This library is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public
  6. ;; License as published by the Free Software Foundation; either
  7. ;; version 3 of the License, or (at your option) any later version.
  8. ;;
  9. ;; This library is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public
  15. ;; License along with this library; if not, write to the Free Software
  16. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. ;; 02110-1301 USA
  18. ;;; Commentary:
  19. ;;;
  20. ;;; This is the non-blocking HTTP implementation of the (web server)
  21. ;;; interface.
  22. ;;;
  23. ;;; It is a modified version of (web server fibers) from Fibers 1.0.0 that
  24. ;;; does not create new threads and does not call 'run-fibers'. Instead it
  25. ;;; expects to be running directly in a fiberized program.
  26. ;;;
  27. ;;; (Modifications by Ludovic Courtès, 2018-01.)
  28. ;;;
  29. ;;; More commentary: Cuirass code has been removed
  30. ;;;
  31. ;;;
  32. ;;; Code:
  33. (define-module (web server fiberized)
  34. #:use-module ((srfi srfi-1) #:select (fold
  35. alist-delete
  36. alist-cons))
  37. #:use-module (srfi srfi-9)
  38. #:use-module (srfi srfi-9 gnu)
  39. #:use-module (web http)
  40. #:use-module (web request)
  41. #:use-module (web response)
  42. #:use-module (web server)
  43. #:use-module (ice-9 binary-ports)
  44. #:use-module (ice-9 match)
  45. #:use-module (fibers)
  46. #:use-module (fibers channels))
  47. (define (make-default-socket family addr port)
  48. (let ((sock (socket PF_INET SOCK_STREAM 0)))
  49. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  50. (fcntl sock F_SETFD FD_CLOEXEC)
  51. (bind sock family addr port)
  52. (fcntl sock F_SETFL (logior O_NONBLOCK (fcntl sock F_GETFL)))
  53. sock))
  54. (define-record-type <server>
  55. (make-server request-channel)
  56. server?
  57. (request-channel server-request-channel))
  58. ;; -> server
  59. (define* (open-server #:key
  60. (host #f)
  61. (family AF_INET)
  62. (addr (if host
  63. (inet-pton family host)
  64. INADDR_LOOPBACK))
  65. (port 8080)
  66. (socket (make-default-socket family addr port)))
  67. ;; We use a large backlog by default. If the server is suddenly hit
  68. ;; with a number of connections on a small backlog, clients won't
  69. ;; receive confirmation for their SYN, leading them to retry --
  70. ;; probably successfully, but with a large latency.
  71. (listen socket 1024)
  72. (fcntl socket F_SETFL (logior O_NONBLOCK (fcntl socket F_GETFL)))
  73. (sigaction SIGPIPE SIG_IGN)
  74. (let ((request-channel (make-channel)))
  75. (spawn-fiber
  76. (lambda ()
  77. (socket-loop socket request-channel)))
  78. (make-server request-channel)))
  79. (define (bad-request msg . args)
  80. (throw 'bad-request msg args))
  81. (define (keep-alive? response)
  82. (let ((v (response-version response)))
  83. (and (or (< (response-code response) 400)
  84. (= (response-code response) 404))
  85. (case (car v)
  86. ((1)
  87. (case (cdr v)
  88. ((1) (not (memq 'close (response-connection response))))
  89. ((0) (memq 'keep-alive (response-connection response)))))
  90. (else #f)))))
  91. ;; This procedure and the next one are copied from (guix scripts publish).
  92. (define (strip-headers response)
  93. "Return RESPONSE's headers minus 'Content-Length' and our internal headers."
  94. (fold alist-delete
  95. (response-headers response)
  96. '(content-length x-raw-file x-nar-compression)))
  97. (define (with-content-length response length)
  98. "Return RESPONSE with a 'content-length' header set to LENGTH."
  99. (set-field response (response-headers)
  100. (alist-cons 'content-length length
  101. (strip-headers response))))
  102. (define (client-loop client have-request)
  103. ;; Always disable Nagle's algorithm, as we handle buffering
  104. ;; ourselves.
  105. (setsockopt client IPPROTO_TCP TCP_NODELAY 1)
  106. (setvbuf client 'block 1024)
  107. (catch #t
  108. (lambda ()
  109. (let ((response-channel (make-channel)))
  110. (let loop ()
  111. (cond
  112. ((eof-object? (lookahead-u8 client))
  113. (close-port client))
  114. (else
  115. (call-with-values
  116. (lambda ()
  117. (catch #t
  118. (lambda ()
  119. (let* ((request (read-request client))
  120. (body (read-request-body request)))
  121. (have-request response-channel request body)))
  122. (lambda (key . args)
  123. (display "While reading request:\n"
  124. (current-error-port))
  125. (print-exception (current-error-port) #f key args)
  126. (values (build-response #:version '(1 . 0) #:code 400
  127. #:headers
  128. '((content-length . 0)))
  129. #vu8()))))
  130. (lambda (response body)
  131. (match (assoc-ref (response-headers response) 'x-raw-file)
  132. ((? string? file)
  133. (non-blocking
  134. (call-with-input-file file
  135. (lambda (input)
  136. (let* ((size (stat:size (stat input)))
  137. (response (write-response
  138. (with-content-length response size)
  139. client))
  140. (output (response-port response)))
  141. (setsockopt client SOL_SOCKET SO_SNDBUF
  142. (* 128 1024))
  143. (if (file-port? output)
  144. (sendfile output input size)
  145. (dump-port input output))
  146. (close-port output)
  147. (values))))))
  148. (#f (begin
  149. (write-response response client)
  150. (when body
  151. (put-bytevector client body))
  152. (force-output client))
  153. (if (and (keep-alive? response)
  154. (not (eof-object? (peek-char client))))
  155. (loop)
  156. (close-port client)))))))))))
  157. (lambda args
  158. ;; Ignore premature client disconnections.
  159. (unless (memv (system-error-errno args)
  160. (list EPIPE ECONNRESET))
  161. (apply throw args)))
  162. (lambda (k . args)
  163. (catch #t
  164. (lambda () (close-port client))
  165. (lambda (k . args)
  166. (display "While closing port:\n" (current-error-port))
  167. (print-exception (current-error-port) #f k args))))))
  168. (define (socket-loop socket request-channel)
  169. (define (have-request response-channel request body)
  170. (put-message request-channel (vector response-channel request body))
  171. (match (get-message response-channel)
  172. (#(response body)
  173. (values response body))))
  174. (let loop ()
  175. (match (accept socket (logior SOCK_NONBLOCK SOCK_CLOEXEC))
  176. ((client . sockaddr)
  177. (spawn-fiber (lambda () (client-loop client have-request))
  178. #:parallel? #t)
  179. (loop)))))
  180. ;; -> (client request body | #f #f #f)
  181. (define (server-read server)
  182. (match (get-message (server-request-channel server))
  183. (#(response-channel request body)
  184. (let ((client response-channel))
  185. (values client request body)))))
  186. ;; -> 0 values
  187. (define (server-write server client response body)
  188. (let ((response-channel client))
  189. (put-message response-channel (vector response body)))
  190. (values))
  191. ;; -> unspecified values
  192. (define (close-server server)
  193. ;; FIXME: We should terminate the 'socket-loop' fiber somehow.
  194. *unspecified*)
  195. (define-server-impl fiberized
  196. open-server
  197. server-read
  198. server-write
  199. close-server)