Skip to content

Commit a94bc5f

Browse files
committed
Compare multipart requests
1 parent 273370c commit a94bc5f

File tree

3 files changed

+115
-9
lines changed

3 files changed

+115
-9
lines changed

src/aleph/http/multipart.clj

+1-1
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@
201201

202202
(defn decode-request
203203
"Takes a ring request and returns a manifold stream which yields
204-
parts of the mutlipart/form-data encoded body. In case the size of
204+
parts of the multipart/form-data encoded body. In case the size of
205205
a part content exceeds `:memory-limit` limit (16KB by default),
206206
corresponding payload would be written to a temp file. Check `:memory?`
207207
flag to know whether content might be read directly from `:content` or

test/aleph/http/clj_http/core_test.clj

+3-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@
106106
[:delete "/delete-with-body"]
107107
{:status 200 :body "delete-with-body"}
108108
[:post "/multipart"]
109-
{:status 200 :body (:body req)}
109+
{:status 200 :body (:body req)
110+
:headers {"x-original-content-type" (get-in req [:headers "content-type"] "not found")}}
110111
[:get "/get-with-body"]
111112
{:status 200 :body (:body req)}
112113
[:options "/options"]
@@ -335,6 +336,7 @@
335336
:mime-type "application/text"}]})
336337
resp-body (apply str (map #(try (char %) (catch Exception _ ""))
337338
(util/force-byte-array (:body resp))))]
339+
#_(println "clj-http resp-body:\n>>>>>>>>>>>\n" resp-body "\n>>>>>>>>>>\n")
338340
(is (= 200 (:status resp)))
339341
(is (re-find #"testFINDMEtest" resp-body))
340342
(is (re-find #"application/json" resp-body))

test/aleph/http/clj_http/util.clj

+111-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(ns aleph.http.clj-http.util
22
(:require
33
[aleph.http :as http]
4+
[aleph.http.core :as http.core]
45
[aleph.http.client-middleware :as aleph.mid]
56
[clj-commons.byte-streams :as bs]
67
[clj-http.core :as clj-http]
@@ -13,7 +14,8 @@
1314
(java.io ByteArrayInputStream
1415
ByteArrayOutputStream
1516
FilterInputStream
16-
InputStream)))
17+
InputStream)
18+
(java.util.regex Pattern)))
1719

1820
;; turn off default middleware for the core tests
1921
(def no-middleware-pool (http/connection-pool {:middleware identity}))
@@ -24,6 +26,7 @@
2426
:server-port 18080})
2527

2628
(def ignored-headers ["date" "connection" "server"])
29+
(def multipart-related-headers ["content-length" "x-original-content-type"])
2730

2831
(defn header-keys
2932
"Returns a set of headers of interest"
@@ -46,10 +49,24 @@
4649
aleph-common-headers (select-keys aleph-headers ks-intersection)]
4750
(is (= clj-http-common-headers aleph-common-headers)))))
4851

52+
(defn- tee-output-stream
53+
"Return the byte array contents of a stream, and a new, unconsumed stream"
54+
[^InputStream in]
55+
(let [baos (ByteArrayOutputStream.)]
56+
(.transferTo in baos) ; not avail until JDK 9
57+
58+
(let [in-bytes (.toByteArray baos)]
59+
{:bytes in-bytes
60+
:stream (proxy [FilterInputStream]
61+
[^InputStream (ByteArrayInputStream. in-bytes)]
62+
(close []
63+
(.close in)
64+
(proxy-super close)))})))
65+
4966
(defn bodies=
5067
"Are the two bodies equal? clj-http's client/request fn coerces to strings by default,
5168
while the core/request leaves the body an InputStream.
52-
Aleph, in keeping with it's stream-based nature, leaves as an InputStream by default.
69+
Aleph, in keeping with its stream-based nature, leaves it as an InputStream by default.
5370
5471
If an InputStream, returns a new ByteArrayInputStream based on the consumed original"
5572
[clj-http-body ^InputStream aleph-body]
@@ -85,6 +102,66 @@
85102
(is (= clj-http-body aleph-body))
86103
clj-http-body)))
87104

105+
(defn- parse-multipart-boundary
106+
[s]
107+
(->> s
108+
(re-find #"boundary=([^ ;]*)")
109+
(second)))
110+
111+
;;(defn- decode-multipart-body
112+
;; [req body]
113+
;; (let [req' (http.core/ring-request->netty-request req)
114+
;; factory (DefaultHttpDataFactory. (long 1e6))
115+
;; decoder (HttpPostRequestDecoder. factory req')
116+
;; baos (ByteArrayOutputStream.)]))
117+
118+
(defn multipart-resp=
119+
"Compares multipart responses from /multipart, which echoes the orig multipart bodies.
120+
121+
Splits based on boundaries, and compares the parts. Whole-byte comparison is impossible
122+
since the boundary strings are chosen randomly.
123+
124+
Does not compare part headers for now, since they differ in case and order, and clj-http
125+
adds Content-Length headers, which are uncommon, can cause problems, and may be completely
126+
unknown for streaming requests."
127+
[clj-http-resp aleph-resp]
128+
(let [clj-http-headers (:headers clj-http-resp)
129+
aleph-headers (:headers aleph-resp)
130+
clj-http-boundary (parse-multipart-boundary (get clj-http-headers "x-original-content-type"))
131+
aleph-boundary (parse-multipart-boundary (get aleph-headers "x-original-content-type"))
132+
{clj-http-bytes :bytes clj-http-stream :stream} (tee-output-stream (:body clj-http-resp))
133+
aleph-bytes (-> aleph-resp :body tee-output-stream :bytes)
134+
135+
;; unlikely to be a problem, but let's make the regex literal, just to be safe
136+
clj-http-boundary-regex (Pattern/compile clj-http-boundary (bit-or Pattern/LITERAL Pattern/MULTILINE))
137+
aleph-boundary-regex (Pattern/compile aleph-boundary (bit-or Pattern/LITERAL Pattern/MULTILINE))
138+
139+
clj-http-contents (-> ^bytes clj-http-bytes
140+
(String.)
141+
(str/split clj-http-boundary-regex))
142+
aleph-contents (-> ^bytes aleph-bytes
143+
(String.)
144+
(str/split aleph-boundary-regex))]
145+
#_(do
146+
(println "aleph bytes")
147+
(bs/print-bytes aleph-bytes)
148+
149+
(println "clj-http bytes")
150+
(bs/print-bytes clj-http-bytes))
151+
152+
(is (= (count clj-http-contents) (count aleph-contents))
153+
"Unequal number of parts found!")
154+
(doseq [[^String clj-http-part ^String aleph-part] (partition 2 (interleave clj-http-contents aleph-contents))]
155+
(let [[clj-http-part-headers clj-http-part-body] (str/split clj-http-part #"\r\n\r\n")
156+
[aleph-part-headers aleph-part-body] (str/split aleph-part #"\r\n\r\n")]
157+
#_ (println "headers:>>>>>>>>>>>>\n" clj-http-part-headers "\n>>>>>>>>>>>>>>>>\n" aleph-part-headers)
158+
#_ (println ">>>>>>>>>>\nbodies:\n" clj-http-part-body "\n>>>>>>>>>>>>>>>>\n" aleph-part-body)
159+
(is (or (and (nil? clj-http-part-body) (nil? aleph-part-body))
160+
(.equalsIgnoreCase clj-http-part-body aleph-part-body))
161+
(str "clj-part:\n>>>>>>>>>>\n" clj-http-part-body "\n>>>>>>>>>>\naleph-part:\n>>>>>>>>>>\n" aleph-part-body "\n>>>>>>>>>>\n"))))
162+
163+
clj-http-stream))
164+
88165

89166
(defn- defined-middleware
90167
"Returns a set of symbols beginning with `wrap-` in the ns"
@@ -173,11 +250,12 @@
173250
;;_ (print-middleware-list clj-http.client/*current-middleware*)
174251
aleph-ring-map (merge base-req req {:pool (aleph-test-conn-pool clj-http-middleware)})
175252
;;_ (prn aleph-ring-map)
253+
is-multipart (contains? clj-http-ring-map :multipart)
176254
clj-http-resp (clj-http-request clj-http-ring-map)
177255
aleph-resp @(http/request aleph-ring-map)]
178256
(is (= (:status clj-http-resp) (:status aleph-resp)))
179257

180-
(prn aleph-resp)
258+
181259

182260
#_(when (not= (:status clj-http-resp) (:status aleph-resp))
183261
(println "clj-http req:")
@@ -193,7 +271,33 @@
193271
(println "aleph resp:")
194272
(prn aleph-resp))
195273

196-
(is-headers= (:headers clj-http-resp) (:headers aleph-resp))
197-
(is (instance? InputStream (:body aleph-resp)))
198-
(let [new-clj-http-body (bodies= (:body clj-http-resp) (:body aleph-resp))]
199-
(assoc clj-http-resp :body new-clj-http-body)))))))
274+
(is (instance? InputStream (:body aleph-resp))) ; non-nil, for now...
275+
276+
(if is-multipart
277+
(do
278+
;;(println "multipart resps")
279+
;;(prn clj-http-resp)
280+
;;(prn aleph-resp)
281+
;;(println)
282+
283+
(do
284+
(println "clj-http req:")
285+
(prn clj-http-ring-map)
286+
(println)
287+
(println "clj-http resp:")
288+
(prn clj-http-resp)
289+
(println)
290+
(println)
291+
(println "aleph req:")
292+
(prn aleph-ring-map)
293+
(println)
294+
(println "aleph resp:")
295+
(prn aleph-resp))
296+
297+
(is-headers= (apply dissoc (:headers clj-http-resp) multipart-related-headers)
298+
(apply dissoc (:headers aleph-resp) multipart-related-headers))
299+
(assoc clj-http-resp :body (multipart-resp= clj-http-resp aleph-resp)))
300+
(do
301+
(is-headers= (:headers clj-http-resp) (:headers aleph-resp))
302+
(let [new-clj-http-body (bodies= (:body clj-http-resp) (:body aleph-resp) is-multipart)]
303+
(assoc clj-http-resp :body new-clj-http-body)))))))))

0 commit comments

Comments
 (0)