Skip to content

Commit 3e8cc91

Browse files
committed
Add wrap-content-length middleware
1 parent d2cfdd3 commit 3e8cc91

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
(ns ring.middleware.content-length
2+
(:require [ring.util.response :as resp]))
3+
4+
(defprotocol SizableResponseBody
5+
(body-size-in-bytes [body response]
6+
"Return the number of bytes that an object will require when it is
7+
serialized as a response body. This number will be placed in the
8+
Content-Length header of the response by the wrap-content-length
9+
middleware. If the number of bytes cannot be ascertained, nil is
10+
returned."))
11+
12+
;; Extending primitive arrays prior to Clojure 1.12 requires using the low-level
13+
;; extend function.
14+
(extend (Class/forName "[B")
15+
SizableResponseBody
16+
{:body-size-in-bytes
17+
(fn [bs _] (alength bs))})
18+
19+
(extend-protocol SizableResponseBody
20+
String
21+
(body-size-in-bytes [s response]
22+
(when-let [charset (resp/get-charset response)]
23+
(alength (.getBytes s charset))))
24+
java.io.File
25+
(body-size-in-bytes [f _]
26+
(.length f))
27+
Object
28+
(body-size-in-bytes [_ _] nil)
29+
nil
30+
(body-size-in-bytes [_ _] 0))
31+
32+
(defn content-length-response
33+
"Adds a Content-Length header to the response. See: wrap-content-length."
34+
{:added "1.15"}
35+
[response]
36+
(if (resp/get-header response "content-length")
37+
response
38+
(if-let [size (body-size-in-bytes (:body response) response)]
39+
(-> response (resp/header "Content-Length" (str size)))
40+
response)))
41+
42+
(defn wrap-content-length
43+
"Middleware that adds a Content-Length header to the response, if the
44+
response body satisfies the ContentLength protocol."
45+
{:added "1.15"}
46+
[handler]
47+
(fn
48+
([request]
49+
(content-length-response (handler request)))
50+
([request respond raise]
51+
(handler request
52+
(comp respond content-length-response)
53+
raise))))
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
(ns ring.middleware.test.content-length
2+
(:require [clojure.test :refer [deftest is testing]]
3+
[ring.middleware.content-length :refer [content-length-response
4+
wrap-content-length]]))
5+
6+
(deftest test-content-length-response
7+
(testing "strings"
8+
(is (= "5" (-> (content-length-response
9+
{:status 200
10+
:headers {"Content-Type" "text/plain; charset=UTF-8"}
11+
:body "hello"})
12+
(get-in [:headers "Content-Length"])))))
13+
(testing "strings without a defined charset"
14+
(is (nil? (-> (content-length-response
15+
{:status 200
16+
:headers {"Content-Type" "text/plain"}
17+
:body "hello"})
18+
(get-in [:headers "Content-Length"])))))
19+
(testing "byte arrays"
20+
(is (= "11" (-> (content-length-response
21+
{:status 200
22+
:headers {}
23+
:body (.getBytes "hello world" "UTF-8")})
24+
(get-in [:headers "Content-Length"])))))
25+
(testing "files"
26+
(is (= "6" (-> (content-length-response
27+
{:status 200
28+
:headers {}
29+
:body (java.io.File. "test/ring/assets/plain.txt")})
30+
(get-in [:headers "Content-Length"])))))
31+
(testing "nil"
32+
(is (= "0" (-> (content-length-response
33+
{:status 200, :headers {}, :body nil})
34+
(get-in [:headers "Content-Length"])))))
35+
(testing "other data"
36+
(is (nil? (-> (content-length-response
37+
{:status 200
38+
:headers {"Content-Type" "text/plain; charset=UTF-8"}
39+
:body (list "hello" "world")})
40+
(get-in [:headers "Content-Length"])))))
41+
(testing "manual content-length overrides middleware"
42+
(is (= "10" (-> (content-length-response
43+
{:status 200
44+
:headers {"Content-Length" "10"}
45+
:body (.getBytes "hello world" "UTF-8")})
46+
(get-in [:headers "Content-Length"]))))))
47+
48+
(deftest test-wrap-content-length
49+
(testing "synchronous handlers"
50+
(let [handler (wrap-content-length
51+
(constantly
52+
{:status 200
53+
:headers {"Content-Type" "text/plain; charset=UTF-8"}
54+
:body "hello"}))]
55+
(is (= "5" (-> (handler {:request-method :get, :uri "/"})
56+
(get-in [:headers "Content-Length"]))))))
57+
(testing "asynchronous handlers"
58+
(let [response (promise)
59+
error (promise)
60+
request {:request-method :get, :url "/"}
61+
handler (wrap-content-length
62+
(fn [_ respond _]
63+
(respond
64+
{:status 200
65+
:headers {"Content-Type" "text/plain; charset=UTF-8"}
66+
:body "hello"})))]
67+
(handler request response error)
68+
(is (not (realized? error)))
69+
(is (realized? response))
70+
(is (= "5" (get-in @response [:headers "Content-Length"]))))))

0 commit comments

Comments
 (0)