├── .gitignore ├── Readme.md ├── project.clj ├── src └── clj_http │ ├── client.clj │ ├── core.clj │ └── util.clj └── test └── clj_http ├── client_test.clj └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | target 3 | lib 4 | *.dot 5 | 6 | # use glob syntax. 7 | syntax: glob 8 | creds.clj 9 | Manifest.txt 10 | pom.xml 11 | aws.clj 12 | *.ser 13 | *.class 14 | *.jar 15 | *~ 16 | *.bak 17 | *.off 18 | *.old 19 | .DS_Store 20 | *.#* 21 | *#* 22 | *.classpath 23 | *.project 24 | *.settings 25 | *.pyc 26 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | ### Note 2 | 3 | The canonical repository for this project is now [dakrone/clj-http](https://github.com/dakrone/clj-http). 4 | 5 | 6 | # `clj-http` 7 | 8 | A Clojure HTTP library wrapping the [Apache HttpComponents](http://hc.apache.org/) client. 9 | 10 | ## Usage 11 | 12 | The main HTTP client functionality is provided by the `clj-http.client` namespace: 13 | 14 | (require '[clj-http.client :as client]) 15 | 16 | The client supports simple `get`, `head`, `put`, `post`, and `delete` requests. Responses are returned as Ring-style response maps: 17 | 18 | (client/get "http://google.com") 19 | => {:status 200 20 | :headers {"date" "Sun, 01 Aug 2010 07:03:49 GMT" 21 | "cache-control" "private, max-age=0" 22 | "content-type" "text/html; charset=ISO-8859-1" 23 | ...} 24 | :body "..."} 25 | 26 | More example requests: 27 | 28 | (client/get "http://site.com/resources/id") 29 | 30 | (client/get "http://site.com/resources/3" {:accept :json}) 31 | 32 | (client/post "http://site.com/resources" {:body byte-array}) 33 | 34 | (client/post "http://site.com/resources" {:body "string"}) 35 | 36 | (client/get "http://site.com/protected" {:basic-auth ["user" "pass"]}) 37 | 38 | (client/get "http://site.com/search" {:query-params {"q" "foo, bar"}}) 39 | 40 | (client/get "http://site.com/favicon.ico" {:as :byte-array}) 41 | 42 | (client/post "http://site.com/api" 43 | {:basic-auth ["user" "pass"] 44 | :body "{\"json\": \"input\"}" 45 | :headers {"X-Api-Version" "2"} 46 | :content-type :json 47 | :accept :json}) 48 | 49 | A more general `response` function is also available, which is useful as a primitive for building higher-level interfaces: 50 | 51 | (defn api-action [method path & [opts]] 52 | (client/request 53 | (merge {:method method :url (str "http://site.com/" path)} opts))) 54 | 55 | The client will throw exceptions on, well, exceptional status codes: 56 | 57 | (client/get "http://site.com/broken") 58 | => Exception: 500 59 | 60 | The client will also follow redirects on the appropriate `30*` status codes. 61 | 62 | The client transparently accepts and decompresses the `gzip` and `deflate` content encodings. 63 | 64 | ## Installation 65 | 66 | `clj-http` is available as a Maven artifact from [Clojars](http://clojars.org/clj-http): 67 | 68 | :dependencies 69 | [[clj-http "0.1.3"] ...] 70 | 71 | ## Design 72 | 73 | The design of `clj-http` is inspired by the [Ring](http://github.com/mmcgrana/ring) protocol for Clojure HTTP server applications. 74 | 75 | The client in `clj-http.core` makes HTTP requests according to a given Ring request map and returns Ring response maps corresponding to the resulting HTTP response. The function `clj-http.client/request` uses Ring-style middleware to layer functionality over the core HTTP request/response implementation. Methods like `clj-http.client/get` are sugar over this `clj-http.client/request` function. 76 | 77 | ## Development 78 | 79 | To run the tests: 80 | 81 | $ lein deps 82 | $ lein run -m clj-http.run-server 83 | $ lein test 84 | 85 | ## License 86 | 87 | Released under the MIT License: 88 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-http "0.1.3" 2 | :description 3 | "A Clojure HTTP library wrapping the Apache HttpComponents client." 4 | :dependencies 5 | [[org.clojure/clojure "1.2.0"] 6 | [org.apache.httpcomponents/httpclient "4.0.3"] 7 | [commons-codec "1.4"] 8 | [commons-io "1.4"]] 9 | :dev-dependencies 10 | [[swank-clojure "1.2.0"] 11 | [ring/ring-jetty-adapter "0.3.5"] 12 | [ring/ring-devel "0.3.5"] 13 | [robert/hooke "1.1.0"]] 14 | :test-selectors {:default #(not (:integration %)) 15 | :integration :integration 16 | :all (constantly true)}) 17 | -------------------------------------------------------------------------------- /src/clj_http/client.clj: -------------------------------------------------------------------------------- 1 | (ns clj-http.client 2 | "Batteries-included HTTP client." 3 | (:import (java.net URL)) 4 | (:require [clojure.string :as str]) 5 | (:require [clj-http.core :as core]) 6 | (:require [clj-http.util :as util]) 7 | (:refer-clojure :exclude (get))) 8 | 9 | (defn update [m k f & args] 10 | (assoc m k (apply f (m k) args))) 11 | 12 | (defn if-pos [v] 13 | (if (and v (pos? v)) v)) 14 | 15 | (defn parse-url [url] 16 | (let [url-parsed (URL. url)] 17 | {:scheme (.getProtocol url-parsed) 18 | :server-name (.getHost url-parsed) 19 | :server-port (or (if-pos (.getPort url-parsed)) 20 | (if (= "https" (.getProtocol url-parsed)) 443 80)) 21 | :uri (.getPath url-parsed) 22 | :user-info (.getUserInfo url-parsed) 23 | :query-string (.getQuery url-parsed)})) 24 | 25 | 26 | (def unexceptional-status? 27 | #{200 201 202 203 204 205 206 207 300 301 302 303 307}) 28 | 29 | (defn wrap-exceptions [client] 30 | (fn [req] 31 | (let [{:keys [status] :as resp} (client req)] 32 | (if (or (not (clojure.core/get req :throw-exceptions true)) 33 | (unexceptional-status? status)) 34 | resp 35 | (throw (Exception. (str status))))))) 36 | 37 | 38 | (defn follow-redirect [client req resp] 39 | (let [url (get-in resp [:headers "location"])] 40 | (client (merge req (parse-url url))))) 41 | 42 | (defn wrap-redirects [client] 43 | (fn [{:keys [request-method] :as req}] 44 | (let [{:keys [status] :as resp} (client req)] 45 | (cond 46 | (and (#{301 302 307} status) (#{:get :head} request-method)) 47 | (follow-redirect client req resp) 48 | (and (= 303 status) (= :head request-method)) 49 | (follow-redirect client (assoc req :request-method :get) resp) 50 | :else 51 | resp)))) 52 | 53 | 54 | (defn wrap-decompression [client] 55 | (fn [req] 56 | (if (get-in req [:headers "Accept-Encoding"]) 57 | (client req) 58 | (let [req-c (update req :headers assoc "Accept-Encoding" "gzip, deflate") 59 | resp-c (client req)] 60 | (case (get-in resp-c [:headers "Content-Encoding"]) 61 | "gzip" 62 | (update resp-c :body util/gunzip) 63 | "deflate" 64 | (update resp-c :body util/inflate) 65 | resp-c))))) 66 | 67 | 68 | (defn wrap-output-coercion [client] 69 | (fn [{:keys [as] :as req}] 70 | (let [{:keys [body] :as resp} (client req)] 71 | (cond 72 | (or (nil? body) (= :byte-array as)) 73 | resp 74 | (nil? as) 75 | (assoc resp :body (String. #^"[B" body "UTF-8")))))) 76 | 77 | 78 | (defn wrap-input-coercion [client] 79 | (fn [{:keys [body] :as req}] 80 | (if (string? body) 81 | (client (-> req (assoc :body (util/utf8-bytes body) 82 | :character-encoding "UTF-8"))) 83 | (client req)))) 84 | 85 | 86 | (defn content-type-value [type] 87 | (if (keyword? type) 88 | (str "application/" (name type)) 89 | type)) 90 | 91 | (defn wrap-content-type [client] 92 | (fn [{:keys [content-type] :as req}] 93 | (if content-type 94 | (client (-> req (assoc :content-type 95 | (content-type-value content-type)))) 96 | (client req)))) 97 | 98 | 99 | (defn wrap-accept [client] 100 | (fn [{:keys [accept] :as req}] 101 | (if accept 102 | (client (-> req (dissoc :accept) 103 | (assoc-in [:headers "Accept"] 104 | (content-type-value accept)))) 105 | (client req)))) 106 | 107 | 108 | (defn accept-encoding-value [accept-encoding] 109 | (str/join ", " (map name accept-encoding))) 110 | 111 | (defn wrap-accept-encoding [client] 112 | (fn [{:keys [accept-encoding] :as req}] 113 | (if accept-encoding 114 | (client (-> req (dissoc :accept-encoding) 115 | (assoc-in [:headers "Accept-Encoding"] 116 | (accept-encoding-value accept-encoding)))) 117 | (client req)))) 118 | 119 | 120 | (defn generate-query-string [params] 121 | (str/join "&" 122 | (map (fn [[k v]] (str (util/url-encode (name k)) "=" 123 | (util/url-encode (str v)))) 124 | params))) 125 | 126 | (defn wrap-query-params [client] 127 | (fn [{:keys [query-params] :as req}] 128 | (if query-params 129 | (client (-> req (dissoc :query-params) 130 | (assoc :query-string 131 | (generate-query-string query-params)))) 132 | (client req)))) 133 | 134 | 135 | (defn basic-auth-value [user password] 136 | (str "Basic " 137 | (util/base64-encode (util/utf8-bytes (str user ":" password))))) 138 | 139 | (defn wrap-basic-auth [client] 140 | (fn [req] 141 | (if-let [[user password] (:basic-auth req)] 142 | (client (-> req (dissoc :basic-auth) 143 | (assoc-in [:headers "Authorization"] 144 | (basic-auth-value user password)))) 145 | (client req)))) 146 | 147 | (defn parse-user-info [user-info] 148 | (when user-info 149 | (str/split user-info #":"))) 150 | 151 | (defn wrap-user-info [client] 152 | (fn [req] 153 | (if-let [[user password] (parse-user-info (:user-info req))] 154 | (client (assoc req :basic-auth [user password])) 155 | (client req)))) 156 | 157 | (defn wrap-method [client] 158 | (fn [req] 159 | (if-let [m (:method req)] 160 | (client (-> req (dissoc :method) 161 | (assoc :request-method m))) 162 | (client req)))) 163 | 164 | (defn wrap-url [client] 165 | (fn [req] 166 | (if-let [url (:url req)] 167 | (client (-> req (dissoc :url) (merge (parse-url url)))) 168 | (client req)))) 169 | 170 | (defn wrap-request 171 | "Returns a battaries-included HTTP request function coresponding to the given 172 | core client. See client/client." 173 | [request] 174 | (-> request 175 | wrap-redirects 176 | wrap-exceptions 177 | wrap-decompression 178 | wrap-input-coercion 179 | wrap-output-coercion 180 | wrap-query-params 181 | wrap-basic-auth 182 | wrap-user-info 183 | wrap-accept 184 | wrap-accept-encoding 185 | wrap-content-type 186 | wrap-method 187 | wrap-url)) 188 | 189 | (def #^{:doc 190 | "Executes the HTTP request corresponding to the given map and returns the 191 | response map for corresponding to the resulting HTTP response. 192 | 193 | In addition to the standard Ring request keys, the following keys are also 194 | recognized: 195 | * :url 196 | * :method 197 | * :query-params 198 | * :basic-auth 199 | * :content-type 200 | * :accept 201 | * :accept-encoding 202 | * :as 203 | 204 | The following additional behaviors over also automatically enabled: 205 | * Exceptions are thrown for status codes other than 200-207, 300-303, or 307 206 | * Gzip and deflate responses are accepted and decompressed 207 | * Input and output bodies are coerced as required and indicated by the :as 208 | option."} 209 | request 210 | (wrap-request #'core/request)) 211 | 212 | (defn get 213 | "Like #'request, but sets the :method and :url as appropriate." 214 | [url & [req]] 215 | (request (merge req {:method :get :url url}))) 216 | 217 | (defn head 218 | "Like #'request, but sets the :method and :url as appropriate." 219 | [url & [req]] 220 | (request (merge req {:method :head :url url}))) 221 | 222 | (defn post 223 | "Like #'request, but sets the :method and :url as appropriate." 224 | [url & [req]] 225 | (request (merge req {:method :post :url url}))) 226 | 227 | (defn put 228 | "Like #'request, but sets the :method and :url as appropriate." 229 | [url & [req]] 230 | (request (merge req {:method :put :url url}))) 231 | 232 | (defn delete 233 | "Like #'request, but sets the :method and :url as appropriate." 234 | [url & [req]] 235 | (request (merge req {:method :delete :url url}))) 236 | -------------------------------------------------------------------------------- /src/clj_http/core.clj: -------------------------------------------------------------------------------- 1 | (ns clj-http.core 2 | "Core HTTP request/response implementation." 3 | (:import (org.apache.http HttpRequest HttpEntityEnclosingRequest HttpResponse Header)) 4 | (:import (org.apache.http.util EntityUtils)) 5 | (:import (org.apache.http.entity ByteArrayEntity)) 6 | (:import (org.apache.http.client.methods HttpGet HttpHead HttpPut HttpPost HttpDelete)) 7 | (:import (org.apache.http.client.params CookiePolicy ClientPNames)) 8 | (:import (org.apache.http.impl.client DefaultHttpClient))) 9 | 10 | (defn- parse-headers [#^HttpResponse http-resp] 11 | (into {} (map (fn [#^Header h] [(.toLowerCase (.getName h)) (.getValue h)]) 12 | (iterator-seq (.headerIterator http-resp))))) 13 | 14 | (defn request 15 | "Executes the HTTP request corresponding to the given Ring request map and 16 | returns the Ring response map corresponding to the resulting HTTP response. 17 | 18 | Note that where Ring uses InputStreams for the request and response bodies, 19 | the clj-http uses ByteArrays for the bodies." 20 | [{:keys [request-method scheme server-name server-port uri query-string 21 | headers content-type character-encoding body]}] 22 | (let [http-client (DefaultHttpClient.)] 23 | (try 24 | (-> http-client 25 | (.getParams) 26 | (.setParameter ClientPNames/COOKIE_POLICY CookiePolicy/BROWSER_COMPATIBILITY)) 27 | (let [http-url (str scheme "://" server-name 28 | (if server-port (str ":" server-port)) 29 | uri 30 | (if query-string (str "?" query-string))) 31 | #^HttpRequest 32 | http-req (case request-method 33 | :get (HttpGet. http-url) 34 | :head (HttpHead. http-url) 35 | :put (HttpPut. http-url) 36 | :post (HttpPost. http-url) 37 | :delete (HttpDelete. http-url))] 38 | (if (and content-type character-encoding) 39 | (.addHeader http-req "Content-Type" 40 | (str content-type "; charset=" character-encoding))) 41 | (if (and content-type (not character-encoding)) 42 | (.addHeader http-req "Content-Type" content-type)) 43 | (.addHeader http-req "Connection" "close") 44 | (doseq [[header-n header-v] headers] 45 | (.addHeader http-req header-n header-v)) 46 | (if body 47 | (let [http-body (ByteArrayEntity. body)] 48 | (.setEntity #^HttpEntityEnclosingRequest http-req http-body))) 49 | (let [http-resp (.execute http-client http-req) 50 | http-entity (.getEntity http-resp) 51 | resp {:status (.getStatusCode (.getStatusLine http-resp)) 52 | :headers (parse-headers http-resp) 53 | :body (if http-entity (EntityUtils/toByteArray http-entity))}] 54 | (.shutdown (.getConnectionManager http-client)) 55 | resp))))) 56 | -------------------------------------------------------------------------------- /src/clj_http/util.clj: -------------------------------------------------------------------------------- 1 | (ns clj-http.util 2 | "Helper functions for the HTTP client." 3 | (:import (java.net URLEncoder)) 4 | (:import (org.apache.commons.codec.binary Base64)) 5 | (:import (java.io ByteArrayInputStream ByteArrayOutputStream)) 6 | (:import (java.util.zip InflaterInputStream DeflaterInputStream 7 | GZIPInputStream GZIPOutputStream)) 8 | (:import (org.apache.commons.io IOUtils))) 9 | 10 | (defn utf8-bytes 11 | "Returns the UTF-8 bytes corresponding to the given string." 12 | [#^String s] 13 | (.getBytes s "UTF-8")) 14 | 15 | (defn utf8-string 16 | "Returns the String corresponding to the UTF-8 decoding of the given bytes." 17 | [#^"[B" b] 18 | (String. b "UTF-8")) 19 | 20 | (defn url-encode 21 | "Returns an UTF-8 URL encoded version of the given string." 22 | [unencoded] 23 | (URLEncoder/encode unencoded "UTF-8")) 24 | 25 | (defn base64-encode 26 | "Encode an array of bytes into a base64 encoded string." 27 | [unencoded] 28 | (utf8-string (Base64/encodeBase64 unencoded))) 29 | 30 | (defn gunzip 31 | "Returns a gunzip'd version of the given byte array." 32 | [b] 33 | (IOUtils/toByteArray (GZIPInputStream. (ByteArrayInputStream. b)))) 34 | 35 | (defn gzip 36 | "Returns a gzip'd version of the given byte array." 37 | [b] 38 | (let [baos (ByteArrayOutputStream.) 39 | gos (GZIPOutputStream. baos)] 40 | (IOUtils/copy (ByteArrayInputStream. b) gos) 41 | (.close gos) 42 | (.toByteArray baos))) 43 | 44 | (defn inflate 45 | "Returns a zlib inflate'd version of the given byte array." 46 | [b] 47 | (IOUtils/toByteArray (InflaterInputStream. (ByteArrayInputStream. b)))) 48 | 49 | (defn deflate 50 | "Returns a deflate'd version of the given byte array." 51 | [b] 52 | (IOUtils/toByteArray (DeflaterInputStream. (ByteArrayInputStream. b)))) 53 | -------------------------------------------------------------------------------- /test/clj_http/client_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-http.client-test 2 | (:use clojure.test) 3 | (:use [clj-http.core-test :only [run-server]] ) 4 | (:require [clj-http.client :as client]) 5 | (:require [clj-http.util :as util]) 6 | (:import (java.util Arrays))) 7 | 8 | (def base-req 9 | {:scheme "http" 10 | :server-name "localhost" 11 | :server-port 18080}) 12 | 13 | (deftest ^{:integration true} roundtrip 14 | (run-server) 15 | (let [resp (client/request (merge base-req {:uri "/get" :method :get}))] 16 | (is (= 200 (:status resp))) 17 | (is (= "close" (get-in resp [:headers "connection"]))) 18 | (is (= "get" (:body resp))))) 19 | 20 | 21 | (defn is-passed [middleware req] 22 | (let [client (middleware identity)] 23 | (is (= req (client req))))) 24 | 25 | (defn is-applied [middleware req-in req-out] 26 | (let [client (middleware identity)] 27 | (is (= req-out (client req-in))))) 28 | 29 | 30 | (deftest redirect-on-get 31 | (let [client (fn [req] 32 | (if (= "foo.com" (:server-name req)) 33 | {:status 302 34 | :headers {"location" "http://bar.com/bat"}} 35 | {:status 200 36 | :req req})) 37 | r-client (client/wrap-redirects client) 38 | resp (r-client {:server-name "foo.com" :request-method :get})] 39 | (is (= 200 (:status resp))) 40 | (is (= :get (:request-method (:req resp)))) 41 | (is (= "http" (:scheme (:req resp)))) 42 | (is (= "/bat" (:uri (:req resp)))))) 43 | 44 | (deftest redirect-to-get-on-head 45 | (let [client (fn [req] 46 | (if (= "foo.com" (:server-name req)) 47 | {:status 303 48 | :headers {"location" "http://bar.com/bat"}} 49 | {:status 200 50 | :req req})) 51 | r-client (client/wrap-redirects client) 52 | resp (r-client {:server-name "foo.com" :request-method :head})] 53 | (is (= 200 (:status resp))) 54 | (is (= :get (:request-method (:req resp)))) 55 | (is (= "http" (:scheme (:req resp)))) 56 | (is (= "/bat" (:uri (:req resp)))))) 57 | 58 | (deftest pass-on-non-redirect 59 | (let [client (fn [req] {:status 200 :body (:body req)}) 60 | r-client (client/wrap-redirects client) 61 | resp (r-client {:body "ok"})] 62 | (is (= 200 (:status resp))) 63 | (is (= "ok" (:body resp))))) 64 | 65 | 66 | (deftest throw-on-exceptional 67 | (let [client (fn [req] {:status 500}) 68 | e-client (client/wrap-exceptions client)] 69 | (is (thrown-with-msg? Exception #"500" 70 | (e-client {}))))) 71 | 72 | (deftest pass-on-non-exceptional 73 | (let [client (fn [req] {:status 200}) 74 | e-client (client/wrap-exceptions client) 75 | resp (e-client {})] 76 | (is (= 200 (:status resp))))) 77 | 78 | (deftest pass-on-exceptional-when-surpressed 79 | (let [client (fn [req] {:status 500}) 80 | e-client (client/wrap-exceptions client) 81 | resp (e-client {:throw-exceptions false})] 82 | (is (= 500 (:status resp))))) 83 | 84 | 85 | (deftest apply-on-compressed 86 | (let [client (fn [req] {:body (util/gzip (util/utf8-bytes "foofoofoo")) 87 | :headers {"Content-Encoding" "gzip"}}) 88 | c-client (client/wrap-decompression client) 89 | resp (c-client {})] 90 | (is (= "foofoofoo" (util/utf8-string (:body resp)))))) 91 | 92 | (deftest apply-on-deflated 93 | (let [client (fn [req] {:body (util/deflate (util/utf8-bytes "barbarbar")) 94 | :headers {"Content-Encoding" "deflate"}}) 95 | c-client (client/wrap-decompression client) 96 | resp (c-client {})] 97 | (is (= "barbarbar" (util/utf8-string (:body resp)))))) 98 | 99 | (deftest pass-on-non-compressed 100 | (let [c-client (client/wrap-decompression (fn [req] {:body "foo"})) 101 | resp (c-client {:uri "/foo"})] 102 | (is (= "foo" (:body resp))))) 103 | 104 | 105 | (deftest apply-on-accept 106 | (is-applied client/wrap-accept 107 | {:accept :json} 108 | {:headers {"Accept" "application/json"}})) 109 | 110 | (deftest pass-on-no-accept 111 | (is-passed client/wrap-accept 112 | {:uri "/foo"})) 113 | 114 | 115 | (deftest apply-on-accept-encoding 116 | (is-applied client/wrap-accept-encoding 117 | {:accept-encoding [:identity :gzip]} 118 | {:headers {"Accept-Encoding" "identity, gzip"}})) 119 | 120 | (deftest pass-on-no-accept-encoding 121 | (is-passed client/wrap-accept-encoding 122 | {:uri "/foo"})) 123 | 124 | 125 | (deftest apply-on-output-coercion 126 | (let [client (fn [req] {:body (util/utf8-bytes "foo")}) 127 | o-client (client/wrap-output-coercion client) 128 | resp (o-client {:uri "/foo"})] 129 | (is (= "foo" (:body resp))))) 130 | 131 | (deftest pass-on-no-output-coercion 132 | (let [client (fn [req] {:body nil}) 133 | o-client (client/wrap-output-coercion client) 134 | resp (o-client {:uri "/foo"})] 135 | (is (nil? (:body resp)))) 136 | (let [client (fn [req] {:body :thebytes}) 137 | o-client (client/wrap-output-coercion client) 138 | resp (o-client {:uri "/foo" :as :byte-array})] 139 | (is (= :thebytes (:body resp))))) 140 | 141 | 142 | (deftest apply-on-input-coercion 143 | (let [i-client (client/wrap-input-coercion identity) 144 | resp (i-client {:body "foo"})] 145 | (is (= "UTF-8" (:character-encoding resp))) 146 | (is (Arrays/equals (util/utf8-bytes "foo") (:body resp))))) 147 | 148 | (deftest pass-on-no-input-coercion 149 | (is-passed client/wrap-input-coercion 150 | {:body (util/utf8-bytes "foo")})) 151 | 152 | 153 | (deftest apply-on-content-type 154 | (is-applied client/wrap-content-type 155 | {:content-type :json} 156 | {:content-type "application/json"})) 157 | 158 | (deftest pass-on-no-content-type 159 | (is-passed client/wrap-content-type 160 | {:uri "/foo"})) 161 | 162 | 163 | (deftest apply-on-query-params 164 | (is-applied client/wrap-query-params 165 | {:query-params {"foo" "bar" "dir" "<<"}} 166 | {:query-string "foo=bar&dir=%3C%3C"})) 167 | 168 | (deftest pass-on-no-query-params 169 | (is-passed client/wrap-query-params 170 | {:uri "/foo"})) 171 | 172 | 173 | (deftest apply-on-basic-auth 174 | (is-applied client/wrap-basic-auth 175 | {:basic-auth ["Aladdin" "open sesame"]} 176 | {:headers {"Authorization" "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="}})) 177 | 178 | (deftest pass-on-no-basic-auth 179 | (is-passed client/wrap-basic-auth 180 | {:uri "/foo"})) 181 | 182 | 183 | (deftest apply-on-method 184 | (let [m-client (client/wrap-method identity) 185 | echo (m-client {:key :val :method :post})] 186 | (is (= :val (:key echo))) 187 | (is (= :post (:request-method echo))) 188 | (is (not (:method echo))))) 189 | 190 | (deftest pass-on-no-method 191 | (let [m-client (client/wrap-method identity) 192 | echo (m-client {:key :val})] 193 | (is (= :val (:key echo))) 194 | (is (not (:request-method echo))))) 195 | 196 | 197 | (deftest apply-on-url 198 | (let [u-client (client/wrap-url identity) 199 | resp (u-client {:url "http://google.com:8080/foo?bar=bat"})] 200 | (is (= "http" (:scheme resp))) 201 | (is (= "google.com" (:server-name resp))) 202 | (is (= 8080 (:server-port resp))) 203 | (is (= "/foo" (:uri resp))) 204 | (is (= "bar=bat" (:query-string resp))))) 205 | 206 | (deftest pass-on-no-url 207 | (let [u-client (client/wrap-url identity) 208 | resp (u-client {:uri "/foo"})] 209 | (is (= "/foo" (:uri resp))))) 210 | 211 | (deftest provide-default-port 212 | (is (= 80 (-> "http://example.com/" client/parse-url :server-port))) 213 | (is (= 8080 (-> "http://example.com:8080/" client/parse-url :server-port))) 214 | (is (= 443 (-> "https://example.com/" client/parse-url :server-port))) 215 | (is (= 8443 (-> "https://example.com:8443/" client/parse-url :server-port)))) 216 | -------------------------------------------------------------------------------- /test/clj_http/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns clj-http.core-test 2 | (:use clojure.test) 3 | (:require [clojure.pprint :as pp]) 4 | (:require [clj-http.core :as core]) 5 | (:require [clj-http.util :as util]) 6 | (:require [ring.adapter.jetty :as ring])) 7 | 8 | (defn handler [req] 9 | (pp/pprint req) 10 | (println) (println) 11 | (condp = [(:request-method req) (:uri req)] 12 | [:get "/get"] 13 | {:status 200 :body "get"} 14 | [:head "/head"] 15 | {:status 200} 16 | [:get "/content-type"] 17 | {:status 200 :body (:content-type req)} 18 | [:get "/header"] 19 | {:status 200 :body (get-in req [:headers "x-my-header"])} 20 | [:post "/post"] 21 | {:status 200 :body (slurp (:body req))} 22 | [:get "/error"] 23 | {:status 500 :body "o noes"})) 24 | 25 | (defn run-server 26 | [] 27 | (defonce server 28 | (future (ring/run-jetty handler {:port 18080})))) 29 | 30 | (def base-req 31 | {:scheme "http" 32 | :server-name "localhost" 33 | :server-port 18080}) 34 | 35 | (defn request [req] 36 | (core/request (merge base-req req))) 37 | 38 | (defn slurp-body [req] 39 | (slurp (:body req))) 40 | 41 | (deftest ^{:integration true} makes-get-request 42 | (run-server) 43 | (let [resp (request {:request-method :get :uri "/get"})] 44 | (is (= 200 (:status resp))) 45 | (is (= "get" (slurp-body resp))))) 46 | 47 | (deftest ^{:integration true} makes-head-request 48 | (run-server) 49 | (let [resp (request {:request-method :head :uri "/head"})] 50 | (is (= 200 (:status resp))) 51 | (is (nil? (:body resp))))) 52 | 53 | (deftest ^{:integration true} sets-content-type-with-charset 54 | (run-server) 55 | (let [resp (request {:request-method :get :uri "/content-type" 56 | :content-type "text/plain" :character-encoding "UTF-8"})] 57 | (is (= "text/plain; charset=UTF-8" (slurp-body resp))))) 58 | 59 | (deftest ^{:integration true} sets-content-type-without-charset 60 | (run-server) 61 | (let [resp (request {:request-method :get :uri "/content-type" 62 | :content-type "text/plain"})] 63 | (is (= "text/plain" (slurp-body resp))))) 64 | 65 | (deftest ^{:integration true} sets-arbitrary-headers 66 | (run-server) 67 | (let [resp (request {:request-method :get :uri "/header" 68 | :headers {"X-My-Header" "header-val"}})] 69 | (is (= "header-val" (slurp-body resp))))) 70 | 71 | (deftest ^{:integration true} sends-and-returns-byte-array-body 72 | (run-server) 73 | (let [resp (request {:request-method :post :uri "/post" 74 | :body (util/utf8-bytes "contents")})] 75 | (is (= 200 (:status resp))) 76 | (is (= "contents" (slurp-body resp))))) 77 | 78 | (deftest ^{:integration true} returns-arbitrary-headers 79 | (run-server) 80 | (let [resp (request {:request-method :get :uri "/get"})] 81 | (is (string? (get-in resp [:headers "date"]))))) 82 | 83 | (deftest ^{:integration true} returns-status-on-exceptional-responses 84 | (run-server) 85 | (let [resp (request {:request-method :get :uri "/error"})] 86 | (is (= 500 (:status resp))))) 87 | --------------------------------------------------------------------------------