1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-29 19:30:36 +02:00

add (web server ethreads)

* module/web/server/ethreads.scm: New file, an ethreads-based HTTP
  server.

* module/Makefile.am: Add to build.
This commit is contained in:
Andy Wingo 2012-03-18 10:41:18 +01:00
parent 183f3db22e
commit 367d9bf0ce
2 changed files with 173 additions and 0 deletions

View file

@ -361,6 +361,7 @@ SOURCES = \
web/response.scm \
web/server.scm \
web/server/http.scm \
web/server/ethreads.scm \
web/uri.scm
ELISP_SOURCES = \

View file

@ -0,0 +1,172 @@
;;; Web I/O: Non-blocking HTTP
;; Copyright (C) 2012 Free Software Foundation, Inc.
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 3 of the License, or (at your option) any later version.
;;
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
;; 02110-1301 USA
;;; Commentary:
;;;
;;; This is the non-blocking HTTP implementation of the (web server)
;;; interface.
;;;
;;; Code:
(define-module (web server ethreads)
#:use-module ((srfi srfi-1) #:select (fold))
#:use-module (srfi srfi-9)
#:use-module (web http)
#:use-module (web request)
#:use-module (web response)
#:use-module (web server)
#:use-module (ice-9 binary-ports)
#:use-module (ice-9 suspendable-ports)
#:use-module (ice-9 match)
#:use-module (ice-9 ethreads))
(define (set-nonblocking! port)
(fcntl port F_SETFL (logior O_NONBLOCK (fcntl port F_GETFL)))
(setvbuf port 'block 1024))
(define (make-default-socket family addr port)
(let ((sock (socket PF_INET SOCK_STREAM 0)))
(setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
(fcntl sock F_SETFD FD_CLOEXEC)
(bind sock family addr port)
(set-nonblocking! sock)
sock))
(define-record-type <server>
(make-server econtext have-request-prompt)
server?
(econtext server-econtext)
(have-request-prompt server-have-request-prompt))
;; -> server
(define* (open-server #:key
(host #f)
(family AF_INET)
(addr (if host
(inet-pton family host)
INADDR_LOOPBACK))
(port 8080)
(socket (make-default-socket family addr port)))
(install-suspendable-ports!)
;; We use a large backlog by default. If the server is suddenly hit
;; with a number of connections on a small backlog, clients won't
;; receive confirmation for their SYN, leading them to retry --
;; probably successfully, but with a large latency.
(listen socket 1024)
(set-nonblocking! socket)
(sigaction SIGPIPE SIG_IGN)
(let* ((ctx (make-econtext))
(server (make-server ctx (make-prompt-tag "have-request"))))
(spawn (lambda () (socket-loop server socket)) ctx)
server))
(define (bad-request msg . args)
(throw 'bad-request msg args))
(define (keep-alive? response)
(let ((v (response-version response)))
(and (or (< (response-code response) 400)
(= (response-code response) 404))
(case (car v)
((1)
(case (cdr v)
((1) (not (memq 'close (response-connection response))))
((0) (memq 'keep-alive (response-connection response)))))
(else #f)))))
(define (client-loop client have-request)
(with-throw-handler #t
(lambda ()
(let loop ()
(cond
((eof-object? (lookahead-u8 client))
(close-port client))
(else
(call-with-values
(lambda ()
(catch #t
(lambda ()
(let* ((request (read-request client))
(body (read-request-body request)))
(suspend
(lambda (ctx thread)
(have-request thread request body)))))
(lambda (key . args)
(display "While reading request:\n" (current-error-port))
(print-exception (current-error-port) #f key args)
(values (build-response #:version '(1 . 0) #:code 400
#:headers '((content-length . 0)))
#vu8()))))
(lambda (response body)
(write-response response client)
(when body
(put-bytevector client body))
(force-output client)
(if (and (keep-alive? response)
(not (eof-object? (peek-char client))))
(loop)
(close-port client))))))))
(lambda (k . args)
(catch #t
(lambda () (close-port client))
(lambda (k . args)
(display "While closing port:\n" (current-error-port))
(print-exception (current-error-port) #f k args))))))
(define (socket-loop server socket)
(define (have-request client-thread request body)
(abort-to-prompt (server-have-request-prompt server)
client-thread request body))
(let loop ()
(match (accept socket)
((client . sockaddr)
;; From "HOP, A Fast Server for the Diffuse Web", Serrano.
(setsockopt client SOL_SOCKET SO_SNDBUF (* 12 1024))
(set-nonblocking! client)
;; Always disable Nagle's algorithm, as we handle buffering
;; ourselves. Ignore exceptions if it's not a TCP port, or
;; TCP_NODELAY is not defined on this platform.
(false-if-exception
(setsockopt client IPPROTO_TCP TCP_NODELAY 0))
(spawn (lambda () (client-loop client have-request)))
(loop)))))
;; -> (client request body | #f #f #f)
(define (server-read server)
(call-with-prompt
(server-have-request-prompt server)
(lambda ()
(run (server-econtext server)))
(lambda (k client request body)
(values client request body))))
;; -> 0 values
(define (server-write server client response body)
(resume client (lambda () (values response body)) (server-econtext server))
(values))
;; -> unspecified values
(define (close-server server)
(destroy-econtext (server-econtext server)))
(define-server-impl ethreads
open-server
server-read
server-write
close-server)