Skip to content

Commit a27b98f

Browse files
committed
Don't use Writer for String response bodies
Change the implementation of write-body-to-stream for Strings such that they are written directly to the OutputStream, rather than going through a Writer. This avoids an unnecessary flush before a close, which prevents adapters from guessing the Content-Length of streams that fit within their buffer. Fixes #519.
1 parent 3e8cc91 commit a27b98f

File tree

2 files changed

+39
-10
lines changed

2 files changed

+39
-10
lines changed

ring-core-protocols/src/ring/core/protocols.clj

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
(ns ring.core.protocols
22
"Protocols necessary for Ring."
33
{:added "1.6"}
4-
(:import [java.io Writer OutputStream])
4+
(:import [java.io OutputStream OutputStreamWriter Writer])
55
(:require [clojure.java.io :as io]))
66

77
(defprotocol ^{:added "1.6"} StreamableResponseBody
@@ -26,15 +26,18 @@
2626
(when-let [m (re-find re-charset content-type)]
2727
(or (m 1) (m 2))))
2828

29-
(defn- response-charset [response]
29+
(defn- response-charset ^String [response]
3030
(some->> (:headers response)
3131
(some #(when (.equalsIgnoreCase "content-type" (key %)) (val %)))
3232
(find-charset-in-content-type)))
3333

34-
(defn- response-writer ^Writer [response output-stream]
34+
(defn- str->bytes [^String s ^String charset]
35+
(if charset (.getBytes s charset) (.getBytes s)))
36+
37+
(defn- response-writer ^Writer [response ^OutputStream output-stream]
3538
(if-let [charset (response-charset response)]
36-
(io/writer output-stream :encoding charset)
37-
(io/writer output-stream)))
39+
(OutputStreamWriter. output-stream charset)
40+
(OutputStreamWriter. output-stream)))
3841

3942
;; Extending primitive arrays prior to Clojure 1.12 requires using the low-level
4043
;; extend function.
@@ -45,12 +48,17 @@
4548
(.write output-stream ^bytes body)
4649
(.close output-stream))})
4750

51+
;; Note: output streams are deliberately not closed on error, so that the
52+
;; adapter or error middleware can potentially send extra error information to
53+
;; the client.
54+
4855
(extend-protocol StreamableResponseBody
4956
String
5057
(write-body-to-stream [body response output-stream]
51-
(doto (response-writer response output-stream)
52-
(.write body)
53-
(.close)))
58+
;; No need to use a writer for a single string, and this prevents a
59+
;; flush being used for a value of fixed length.
60+
(.write output-stream (str->bytes body (response-charset response)))
61+
(.close output-stream))
5462
clojure.lang.ISeq
5563
(write-body-to-stream [body response output-stream]
5664
(let [writer (response-writer response output-stream)]

ring-core-protocols/test/ring/core/test/protocols.clj

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
response {:headers {"Content-Type" "text/plain; charset=UTF-16"}
2323
:body "Hello World"}]
2424
(write-body-to-stream (:body response) response output)
25-
(is (= "Hello World" (.toString output "UTF-16")))))
25+
(is (= (seq (.getBytes "Hello World" "UTF-16"))
26+
(seq (.toByteArray output))))))
2627

2728
(testing "seqs"
2829
(let [output (java.io.ByteArrayOutputStream.)
@@ -35,7 +36,8 @@
3536
response {:headers {"Content-Type" "text/plain; charset=UTF-16"}
3637
:body (list "Hello" " " "World")}]
3738
(write-body-to-stream (:body response) response output)
38-
(is (= "Hello World" (.toString output "UTF-16")))))
39+
(is (= (seq (.getBytes "Hello World" "UTF-16"))
40+
(seq (.toByteArray output))))))
3941

4042
(testing "input streams"
4143
(let [output (java.io.ByteArrayOutputStream.)
@@ -92,3 +94,22 @@
9294
(is (thrown? IOException
9395
(write-body-to-stream (:body response) response output)))
9496
(is (= false @closed?)))))
97+
98+
(defn output-stream-with-flush-flag []
99+
(let [stream-flushed? (atom false)
100+
output-stream (proxy [OutputStream] []
101+
(write
102+
([])
103+
([^bytes _])
104+
([^bytes _ _ _]))
105+
(flush []
106+
(reset! stream-flushed? true))
107+
(close []))]
108+
[output-stream stream-flushed?]))
109+
110+
(deftest test-response-flushing []
111+
(testing "response doesn't flush for strings"
112+
(let [[output flushed?] (output-stream-with-flush-flag)
113+
response {:body "Hello World"}]
114+
(write-body-to-stream (:body response) response output)
115+
(is (not @flushed?)))))

0 commit comments

Comments
 (0)