mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-05-19 19:20:23 +02:00
http: Do not buffer HTTP chunks.
Fixes <http://bugs.gnu.org/19939>. * module/web/http.scm (read-chunk, read-chunk-body): Remove. (make-chunked-input-port)[next-chunk, buffer-, buffer-size, buffer-pointer]: Remove. [chunk-size, remaining]: New variables. [read!]: Rewrite to write directly to BV. * test-suite/tests/web-http.test ("chunked encoding")["reads chunks without buffering", "reads across chunk boundaries"]: New tests.
This commit is contained in:
parent
d0d64e6bbf
commit
00d3ecf274
2 changed files with 84 additions and 36 deletions
|
@ -1,6 +1,6 @@
|
||||||
;;; HTTP messages
|
;;; HTTP messages
|
||||||
|
|
||||||
;; Copyright (C) 2010, 2011, 2012, 2013, 2014 Free Software Foundation, Inc.
|
;; Copyright (C) 2010-2015 Free Software Foundation, Inc.
|
||||||
|
|
||||||
;; This library is free software; you can redistribute it and/or
|
;; This library is free software; you can redistribute it and/or
|
||||||
;; modify it under the terms of the GNU Lesser General Public
|
;; modify it under the terms of the GNU Lesser General Public
|
||||||
|
@ -1907,6 +1907,7 @@ treated specially, and is just returned as a plain string."
|
||||||
|
|
||||||
;; Chunked Responses
|
;; Chunked Responses
|
||||||
(define (read-chunk-header port)
|
(define (read-chunk-header port)
|
||||||
|
"Read a chunk header and return the chunk size."
|
||||||
(let* ((str (read-line port))
|
(let* ((str (read-line port))
|
||||||
(extension-start (string-index str (lambda (c) (or (char=? c #\;)
|
(extension-start (string-index str (lambda (c) (or (char=? c #\;)
|
||||||
(char=? c #\return)))))
|
(char=? c #\return)))))
|
||||||
|
@ -1916,53 +1917,50 @@ treated specially, and is just returned as a plain string."
|
||||||
16)))
|
16)))
|
||||||
size))
|
size))
|
||||||
|
|
||||||
(define (read-chunk port)
|
|
||||||
(let ((size (read-chunk-header port)))
|
|
||||||
(read-chunk-body port size)))
|
|
||||||
|
|
||||||
(define (read-chunk-body port size)
|
|
||||||
(let ((bv (get-bytevector-n port size)))
|
|
||||||
(get-u8 port) ; CR
|
|
||||||
(get-u8 port) ; LF
|
|
||||||
bv))
|
|
||||||
|
|
||||||
(define* (make-chunked-input-port port #:key (keep-alive? #f))
|
(define* (make-chunked-input-port port #:key (keep-alive? #f))
|
||||||
"Returns a new port which translates HTTP chunked transfer encoded
|
"Returns a new port which translates HTTP chunked transfer encoded
|
||||||
data from PORT into a non-encoded format. Returns eof when it has
|
data from PORT into a non-encoded format. Returns eof when it has
|
||||||
read the final chunk from PORT. This does not necessarily mean
|
read the final chunk from PORT. This does not necessarily mean
|
||||||
that there is no more data on PORT. When the returned port is
|
that there is no more data on PORT. When the returned port is
|
||||||
closed it will also close PORT, unless the KEEP-ALIVE? is true."
|
closed it will also close PORT, unless the KEEP-ALIVE? is true."
|
||||||
(define (next-chunk)
|
|
||||||
(read-chunk port))
|
|
||||||
(define finished? #f)
|
|
||||||
(define (close)
|
(define (close)
|
||||||
(unless keep-alive?
|
(unless keep-alive?
|
||||||
(close-port port)))
|
(close-port port)))
|
||||||
(define buffer #vu8())
|
|
||||||
(define buffer-size 0)
|
(define chunk-size 0) ;size of the current chunk
|
||||||
(define buffer-pointer 0)
|
(define remaining 0) ;number of bytes left from the current chunk
|
||||||
|
(define finished? #f) ;did we get all the chunks?
|
||||||
|
|
||||||
(define (read! bv idx to-read)
|
(define (read! bv idx to-read)
|
||||||
(define (loop to-read num-read)
|
(define (loop to-read num-read)
|
||||||
(cond ((or finished? (zero? to-read))
|
(cond ((or finished? (zero? to-read))
|
||||||
num-read)
|
num-read)
|
||||||
((<= to-read (- buffer-size buffer-pointer))
|
((zero? remaining) ;get a new chunk
|
||||||
(bytevector-copy! buffer buffer-pointer
|
(let ((size (read-chunk-header port)))
|
||||||
bv (+ idx num-read)
|
(set! chunk-size size)
|
||||||
to-read)
|
(set! remaining size)
|
||||||
(set! buffer-pointer (+ buffer-pointer to-read))
|
(if (zero? size)
|
||||||
(loop 0 (+ num-read to-read)))
|
(begin
|
||||||
(else
|
(set! finished? #t)
|
||||||
(let ((n (- buffer-size buffer-pointer)))
|
num-read)
|
||||||
(bytevector-copy! buffer buffer-pointer
|
(loop to-read num-read))))
|
||||||
bv (+ idx num-read)
|
(else ;read from the current chunk
|
||||||
n)
|
(let* ((ask-for (min to-read remaining))
|
||||||
(set! buffer (next-chunk))
|
(read (get-bytevector-n! port bv (+ idx num-read)
|
||||||
(set! buffer-pointer 0)
|
ask-for)))
|
||||||
(set! buffer-size (bytevector-length buffer))
|
(if (eof-object? read)
|
||||||
(set! finished? (= buffer-size 0))
|
(begin ;premature termination
|
||||||
(loop (- to-read n)
|
(set! finished? #t)
|
||||||
(+ num-read n))))))
|
num-read)
|
||||||
|
(let ((left (- remaining read)))
|
||||||
|
(set! remaining left)
|
||||||
|
(when (zero? left)
|
||||||
|
;; We're done with this chunk; read CR and LF.
|
||||||
|
(get-u8 port) (get-u8 port))
|
||||||
|
(loop (- to-read read)
|
||||||
|
(+ num-read read))))))))
|
||||||
(loop to-read 0))
|
(loop to-read 0))
|
||||||
|
|
||||||
(make-custom-binary-input-port "chunked input port" read! #f #f close))
|
(make-custom-binary-input-port "chunked input port" read! #f #f close))
|
||||||
|
|
||||||
(define* (make-chunked-output-port port #:key (keep-alive? #f))
|
(define* (make-chunked-output-port port #:key (keep-alive? #f))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
;;;; web-uri.test --- URI library -*- mode: scheme; coding: utf-8; -*-
|
;;;; web-http.test --- HTTP library -*- mode: scheme; coding: utf-8; -*-
|
||||||
;;;;
|
;;;;
|
||||||
;;;; Copyright (C) 2010, 2011, 2014, 2015 Free Software Foundation, Inc.
|
;;;; Copyright (C) 2010, 2011, 2014, 2015 Free Software Foundation, Inc.
|
||||||
;;;;
|
;;;;
|
||||||
|
@ -20,6 +20,7 @@
|
||||||
(define-module (test-suite web-http)
|
(define-module (test-suite web-http)
|
||||||
#:use-module (web uri)
|
#:use-module (web uri)
|
||||||
#:use-module (web http)
|
#:use-module (web http)
|
||||||
|
#:use-module (rnrs bytevectors)
|
||||||
#:use-module (rnrs io ports)
|
#:use-module (rnrs io ports)
|
||||||
#:use-module (ice-9 regex)
|
#:use-module (ice-9 regex)
|
||||||
#:use-module (ice-9 control)
|
#:use-module (ice-9 control)
|
||||||
|
@ -363,7 +364,56 @@
|
||||||
(pass-if-equal
|
(pass-if-equal
|
||||||
"First line\n Second line"
|
"First line\n Second line"
|
||||||
(get-string-all p))
|
(get-string-all p))
|
||||||
(pass-if (port-eof? (make-chunked-input-port (open-input-string "0\r\n")))))
|
(pass-if (port-eof? (make-chunked-input-port (open-input-string "0\r\n"))))
|
||||||
|
|
||||||
|
(pass-if-equal "reads chunks without buffering"
|
||||||
|
;; Make sure the chunked input port does not read more than what
|
||||||
|
;; the client asked. See <http://bugs.gnu.org/19939>
|
||||||
|
`("First " "chunk." "Second " "chunk."
|
||||||
|
(1 1 1 6 6 1 1
|
||||||
|
1 1 1 7 6 1 1))
|
||||||
|
(let* ((str "C\r\nFirst chunk.\r\nD\r\nSecond chunk.\r\n")
|
||||||
|
(requests '())
|
||||||
|
(read! (let ((port (open-input-string str)))
|
||||||
|
(lambda (bv index count)
|
||||||
|
(set! requests (cons count requests))
|
||||||
|
(let ((n (get-bytevector-n! port bv index
|
||||||
|
count)))
|
||||||
|
(if (eof-object? n) 0 n)))))
|
||||||
|
(input (make-custom-binary-input-port "chunky" read!
|
||||||
|
#f #f #f))
|
||||||
|
(port (make-chunked-input-port input)))
|
||||||
|
(setvbuf input _IONBF)
|
||||||
|
(setvbuf port _IONBF)
|
||||||
|
(list (utf8->string (get-bytevector-n port 6))
|
||||||
|
(utf8->string (get-bytevector-n port 6))
|
||||||
|
(utf8->string (get-bytevector-n port 7))
|
||||||
|
(utf8->string (get-bytevector-n port 6))
|
||||||
|
(reverse requests))))
|
||||||
|
|
||||||
|
(pass-if-equal "reads across chunk boundaries"
|
||||||
|
;; Same, but read across chunk boundaries.
|
||||||
|
`("First " "chunk.Second " "chunk."
|
||||||
|
(1 1 1 6 6 1 1
|
||||||
|
1 1 1 7 6 1 1))
|
||||||
|
(let* ((str "C\r\nFirst chunk.\r\nD\r\nSecond chunk.\r\n")
|
||||||
|
(requests '())
|
||||||
|
(read! (let ((port (open-input-string str)))
|
||||||
|
(lambda (bv index count)
|
||||||
|
(set! requests (cons count requests))
|
||||||
|
(let ((n (get-bytevector-n! port bv index
|
||||||
|
count)))
|
||||||
|
(if (eof-object? n) 0 n)))))
|
||||||
|
(input (make-custom-binary-input-port "chunky" read!
|
||||||
|
#f #f #f))
|
||||||
|
(port (make-chunked-input-port input)))
|
||||||
|
(setvbuf input _IONBF)
|
||||||
|
(setvbuf port _IONBF)
|
||||||
|
(list (utf8->string (get-bytevector-n port 6))
|
||||||
|
(utf8->string (get-bytevector-n port 13))
|
||||||
|
(utf8->string (get-bytevector-n port 6))
|
||||||
|
(reverse requests)))))
|
||||||
|
|
||||||
(pass-if-equal
|
(pass-if-equal
|
||||||
(call-with-output-string
|
(call-with-output-string
|
||||||
(lambda (out-raw)
|
(lambda (out-raw)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue