mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-02 04:40:29 +02:00
* module/web/server/http.scm (http-read): Don't play the setvbuf dance, it was throwing away buffered input. Instead just call setvbuf once before doing any reads or writes.
170 lines
5.8 KiB
Scheme
170 lines
5.8 KiB
Scheme
;;; Web I/O: HTTP
|
|
|
|
;; Copyright (C) 2010 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 HTTP implementation of the (web server) interface.
|
|
;;;
|
|
;;; `read-request' sets the character encoding on the new port to
|
|
;;; latin-1. See the note in request.scm regarding character sets,
|
|
;;; strings, and bytevectors for more information.
|
|
;;;
|
|
;;; Code:
|
|
|
|
(define-module (web server http)
|
|
#:use-module ((srfi srfi-1) #:select (fold))
|
|
#:use-module (srfi srfi-9)
|
|
#:use-module (rnrs bytevectors)
|
|
#:use-module (web request)
|
|
#:use-module (web response)
|
|
#:use-module (web server)
|
|
#:use-module (ice-9 poll))
|
|
|
|
|
|
(define (make-default-socket family addr port)
|
|
(let ((sock (socket PF_INET SOCK_STREAM 0)))
|
|
(setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
|
|
(bind sock family addr port)
|
|
sock))
|
|
|
|
(define-record-type <http-server>
|
|
(make-http-server socket poll-idx poll-set)
|
|
http-server?
|
|
(socket http-socket)
|
|
(poll-idx http-poll-idx set-http-poll-idx!)
|
|
(poll-set http-poll-set))
|
|
|
|
(define *error-events* (logior POLLHUP POLLERR))
|
|
(define *read-events* POLLIN)
|
|
(define *events* (logior *error-events* *read-events*))
|
|
|
|
;; -> server
|
|
(define* (http-open #:key
|
|
(host #f)
|
|
(family AF_INET)
|
|
(addr (if host
|
|
(inet-pton family host)
|
|
INADDR_LOOPBACK))
|
|
(port 8080)
|
|
(socket (make-default-socket family addr port)))
|
|
(listen socket 128)
|
|
(sigaction SIGPIPE SIG_IGN)
|
|
(let ((poll-set (make-empty-poll-set)))
|
|
(poll-set-add! poll-set socket *events*)
|
|
(make-http-server socket 0 poll-set)))
|
|
|
|
;; -> (client request body | #f #f #f)
|
|
(define (http-read server)
|
|
(let* ((poll-set (http-poll-set server)))
|
|
(let lp ((idx (http-poll-idx server)))
|
|
(let ((revents (poll-set-revents poll-set idx)))
|
|
(cond
|
|
((zero? idx)
|
|
;; The server socket, and the end of our downward loop.
|
|
(cond
|
|
((zero? revents)
|
|
;; No client ready, and no error; poll and loop.
|
|
(poll poll-set)
|
|
(lp (1- (poll-set-nfds poll-set))))
|
|
((not (zero? (logand revents *error-events*)))
|
|
;; An error.
|
|
(throw 'interrupt))
|
|
(else
|
|
;; A new client. Add to set, poll, and loop.
|
|
;;
|
|
;; FIXME: preserve meta-info.
|
|
(let ((client (accept (poll-set-port poll-set idx))))
|
|
;; Buffer input and output on this port.
|
|
(setvbuf (car client) _IOFBF)
|
|
;; From "HOP, A Fast Server for the Diffuse Web", Serrano.
|
|
(setsockopt (car client) SOL_SOCKET SO_SNDBUF (* 12 1024))
|
|
(poll-set-add! poll-set (car client) *events*)
|
|
(poll poll-set)
|
|
(lp (1- (poll-set-nfds poll-set)))))))
|
|
((zero? revents)
|
|
;; Nothing on this port.
|
|
(lp (1- idx)))
|
|
;; Otherwise, a client socket with some activity on
|
|
;; it. Remove it from the poll set.
|
|
(else
|
|
(let ((port (poll-set-remove! poll-set idx)))
|
|
(cond
|
|
((eof-object? (peek-char port))
|
|
;; EOF.
|
|
(close-port port)
|
|
(lp (1- idx)))
|
|
(else
|
|
;; Otherwise, try to read a request from this port.
|
|
;; Record the next index.
|
|
(set-http-poll-idx! server (1- idx))
|
|
(with-throw-handler
|
|
#t
|
|
(lambda ()
|
|
(let ((req (read-request port)))
|
|
(values port
|
|
req
|
|
(read-request-body/bytevector req))))
|
|
(lambda (k . args)
|
|
(false-if-exception (close-port port)))))))))))))
|
|
|
|
(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)))))
|
|
|
|
;; -> 0 values
|
|
(define (http-write server client response body)
|
|
(let* ((response (write-response response client))
|
|
(port (response-port response)))
|
|
(cond
|
|
((not body)) ; pass
|
|
((string? body)
|
|
(write-response-body/latin-1 response body))
|
|
((bytevector? body)
|
|
(write-response-body/bytevector response body))
|
|
(else
|
|
(error "Expected a string or bytevector for body" body)))
|
|
(cond
|
|
((keep-alive? response)
|
|
(force-output port)
|
|
(poll-set-add! (http-poll-set server) port *events*))
|
|
(else
|
|
(close-port port)))
|
|
(values)))
|
|
|
|
;; -> unspecified values
|
|
(define (http-close server)
|
|
(let ((poll-set (http-poll-set server)))
|
|
(let lp ((n (poll-set-nfds poll-set)))
|
|
(if (positive? n)
|
|
(begin
|
|
(close-port (poll-set-remove! poll-set (1- n)))
|
|
(lp (1- n)))))))
|
|
|
|
(define-server-impl http
|
|
http-open
|
|
http-read
|
|
http-write
|
|
http-close)
|