1
1
(ns ring.middleware.gzip
2
- (:require [clojure.java.io :as io])
2
+ (:require [clojure.java.io :as io]
3
+ clojure.reflect)
3
4
(:import (java.util.zip GZIPOutputStream)
4
5
(java.io InputStream
6
+ OutputStream
5
7
Closeable
6
8
File
7
9
PipedInputStream
8
10
PipedOutputStream)))
9
11
12
+ ; only available on JDK7
13
+ (def ^:private flushable-gzip?
14
+ (delay (->> (clojure.reflect/reflect GZIPOutputStream)
15
+ :members
16
+ (some (comp '#{[java.io.OutputStream boolean]} :parameter-types )))))
17
+
18
+ ; only proxying here so we can specialize io/copy (which ring uses to transfer
19
+ ; InputStream bodies to the servlet response) for reading from the result of
20
+ ; piped-gzipped-input-stream
21
+ (defn- piped-gzipped-input-stream*
22
+ []
23
+ (proxy [PipedInputStream] []))
24
+
25
+ ; exactly the same as do-copy for [InputStream OutputStream], but
26
+ ; flushes the output on every chunk; this allows gzipped content to start
27
+ ; flowing to clients ASAP (a reasonable change to ring IMO)
28
+ (defmethod @#'io /do-copy [(class (piped-gzipped-input-stream* )) OutputStream]
29
+ [^InputStream input ^OutputStream output opts]
30
+ (let [buffer (make-array Byte/TYPE (or (:buffer-size opts) 1024 ))]
31
+ (loop []
32
+ (let [size (.read input buffer)]
33
+ (when (pos? size)
34
+ (do (.write output buffer 0 size)
35
+ (.flush output)
36
+ (recur )))))))
37
+
10
38
(defn piped-gzipped-input-stream [in]
11
- (let [pipe-in (PipedInputStream. )
39
+ (let [pipe-in (piped-gzipped-input-stream* )
12
40
pipe-out (PipedOutputStream. pipe-in)]
13
- (future ; new thread to prevent blocking deadlock
14
- (with-open [out (GZIPOutputStream. pipe-out)]
41
+ ; separate thread to prevent blocking deadlock
42
+ (future
43
+ (with-open [out (if @flushable-gzip?
44
+ (GZIPOutputStream. pipe-out true )
45
+ (GZIPOutputStream. pipe-out))]
15
46
(if (seq? in)
16
- (doseq [string in] (io/copy (str string) out))
47
+ (doseq [string in]
48
+ (io/copy (str string) out)
49
+ (.flush out))
17
50
(io/copy in out)))
18
51
(when (instance? Closeable in)
19
- (.close in)))
52
+ (.close ^Closeable in)))
20
53
pipe-in))
21
54
22
55
(defn gzipped-response [resp]
34
67
(not (get-in resp [:headers " Content-Encoding" ]))
35
68
(or
36
69
(and (string? body) (> (count body) 200 ))
37
- (seq? body)
70
+ (and ( seq? body) @flushable-gzip? )
38
71
(instance? InputStream body)
39
72
(instance? File body)))
40
73
(let [accepts (get-in req [:headers " accept-encoding" ] " " )
43
76
(match 3 ))))
44
77
(gzipped-response resp)
45
78
resp))
46
- resp))))
79
+ resp))))
0 commit comments