|
1 | 1 | (ns aleph.http.clj-http.util
|
2 | 2 | (:require
|
3 | 3 | [aleph.http :as http]
|
| 4 | + [aleph.http.core :as http.core] |
4 | 5 | [aleph.http.client-middleware :as aleph.mid]
|
5 | 6 | [clj-commons.byte-streams :as bs]
|
6 | 7 | [clj-http.core :as clj-http]
|
|
13 | 14 | (java.io ByteArrayInputStream
|
14 | 15 | ByteArrayOutputStream
|
15 | 16 | FilterInputStream
|
16 |
| - InputStream))) |
| 17 | + InputStream) |
| 18 | + (java.util.regex Pattern))) |
17 | 19 |
|
18 | 20 | ;; turn off default middleware for the core tests
|
19 | 21 | (def no-middleware-pool (http/connection-pool {:middleware identity}))
|
|
24 | 26 | :server-port 18080})
|
25 | 27 |
|
26 | 28 | (def ignored-headers ["date" "connection" "server"])
|
| 29 | +(def multipart-related-headers ["content-length" "x-original-content-type"]) |
27 | 30 |
|
28 | 31 | (defn header-keys
|
29 | 32 | "Returns a set of headers of interest"
|
|
46 | 49 | aleph-common-headers (select-keys aleph-headers ks-intersection)]
|
47 | 50 | (is (= clj-http-common-headers aleph-common-headers)))))
|
48 | 51 |
|
| 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 | + |
49 | 66 | (defn bodies=
|
50 | 67 | "Are the two bodies equal? clj-http's client/request fn coerces to strings by default,
|
51 | 68 | 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. |
53 | 70 |
|
54 | 71 | If an InputStream, returns a new ByteArrayInputStream based on the consumed original"
|
55 | 72 | [clj-http-body ^InputStream aleph-body]
|
|
85 | 102 | (is (= clj-http-body aleph-body))
|
86 | 103 | clj-http-body)))
|
87 | 104 |
|
| 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 | + |
88 | 165 |
|
89 | 166 | (defn- defined-middleware
|
90 | 167 | "Returns a set of symbols beginning with `wrap-` in the ns"
|
|
173 | 250 | ;;_ (print-middleware-list clj-http.client/*current-middleware*)
|
174 | 251 | aleph-ring-map (merge base-req req {:pool (aleph-test-conn-pool clj-http-middleware)})
|
175 | 252 | ;;_ (prn aleph-ring-map)
|
| 253 | + is-multipart (contains? clj-http-ring-map :multipart) |
176 | 254 | clj-http-resp (clj-http-request clj-http-ring-map)
|
177 | 255 | aleph-resp @(http/request aleph-ring-map)]
|
178 | 256 | (is (= (:status clj-http-resp) (:status aleph-resp)))
|
179 | 257 |
|
180 |
| - (prn aleph-resp) |
| 258 | + |
181 | 259 |
|
182 | 260 | #_(when (not= (:status clj-http-resp) (:status aleph-resp))
|
183 | 261 | (println "clj-http req:")
|
|
193 | 271 | (println "aleph resp:")
|
194 | 272 | (prn aleph-resp))
|
195 | 273 |
|
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