Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
203 lines (168 sloc) 8.7 KB
layout: doc
title: HTTP client
<h2 id="title">HTTP client</h2>
{% highlight clojure %}
(:require [org.httpkit.client :as http])
{% endhighlight %}
Like the Server, the client uses an event-driven, non-blocking I/O model. It's lightweight and efficient.
<li>Concurrency made easy by asynchronicity and promises</li>
<li>Keep-alive makes quite a difference in performance, and can be disabled with <code>{:keepalive -1}</code></li>
<li>Timeout per request</li>
<li>HTTPS supported, ~100k of RAM for issuing a HTTPS request. TCP connection and SSLEngine get reused if possible</li>
<li>Easy-to-use API, modeled after <a href="">clj-http</a></li>
<h3 id="async" class="anchor">asynchronous with promise, callback</h3>
{% highlight clojure %}
;fire and forget, returns immediately[1], returned promise is ignored
(http/get "")
(def options {:timeout 200 ; ms
:basic-auth ["user" "pass"]
:query-params {:param "value" :param2 ["value1" "value2"]}
:user-agent "User-Agent-string"
:headers {"X-Header" "Value"}})
(http/get "" options
(fn [{:keys [status headers body error]}] ;; asynchronous response handling
(if error
(println "Failed, exception is " error)
(println "Async HTTP GET: " status))))
; [1] may not always true, since DNS lookup maybe slow
{% endhighlight %}
<h3 class="anchor" id="sync"> synchronous with @promise</h3>
<p>Synchronous programming is easy to understand, just like clj-http:</p>
{% highlight clojure %}
;; synchronous
(let [{:keys [status headers body error] :as resp} @(http/get "")]
(if error
(println "Failed, exception: " error)
(println "HTTP GET success: " status)))
;; Form params
(let [options {:form-params {:name "http-kit" :features ["async" "client" "server"]}}
{:keys [status error]} @(http/post "" options)]
(if error
(println "Failed, exception is " error)
(println "Async HTTP POST: " status)))
{% endhighlight %}
<h3 class="anchor" id="combined">Combined, concurrent requests, handle results synchronously</h3>
<p>Send request concurrently, with half the waiting time</p>
{% highlight clojure %}
(let [resp1 (http/get "")
resp2 (http/get "")]
(println "Response 1's status: " (:status @resp1)) ; wait as necessary
(println "Response 2's status: " (:status @resp2)))
{% endhighlight %}
{% highlight clojure %}
(let [urls ["" ""
;; send the request concurrently (asynchronously)
futures (doall (map http/get urls))]
(doseq [resp futures]
;; wait for server response synchronously
(println (-> @resp :opt :url) " status: " (:status @resp))
{% endhighlight %}
<h3 class="anchor" id="reuse">Persistent connection</h3>
<p>HTTP persistent connection, also called HTTP keep-alive, or HTTP connection reuse, is the idea of using a single TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new connection for every single request/response pair</p>
<p>HTTPS persistent connection is also supported.</p>
<p>By default, http-kit keeps idle connections for 120s.
That can be configured by the <code>keepalive</code> option:</p>
{% highlight clojure %}
; keepalive for 30s
@(http/get "" {:keepalive 30000})
; will reuse the previous TCP connection
@(http/get "" {:keepalive 30000})
; disable keepalive for this request
@(http/get "" {:keepalive -1})
{% endhighlight %}
<h3 class="anchor" id="state">Pass state to asynchronous callback</h3>
<p>Sometimes, it's handy to pass some state to callback. You can do it this way:</p>
{% highlight clojure %}
(defn callback [{:keys [status headers body error opts]}]
;; opts contains :url :method :header + user defined key(s)
(let [{:keys [method start-time url]} opts]
(println method url "status" status "takes time"
(- (System/currentTimeMillis) start-time) "ms")))
;;; save state for callback, useful for async request
(let [opts {:start-time (System/currentTimeMillis)}]
(http/get "" opts callback))
{% endhighlight %}
<h3 class="anchor" id="coercion">Output coercion</h3>
{% highlight clojure %}
;; Return the body as a byte stream
(http/get "" {:as :stream}
(fn [{:keys [status headers body error opts]}]
;; body is a
;; Coerce as a byte-array
(http/get "" {:as :byte-array}
(fn [{:keys [status headers body error opts]}]
;; body is a byte[]
;; return the body as a string body
(http/get "" {:as :text}
(fn [{:keys [status headers body error opts]}]
;; body is a java.lang.String
;; Try to automatically coerce the output based on the content-type header, currently supports :text :stream, (with automatic charset detection)
(http/get "" {:as :auto})
{% endhighlight %}
<h3 class="anchor" id="nested-params">Nested params</h3>
httpkit has some supports for nested params, like <code>clj-http</code> does.
{% highlight clojure %}
{:query-params {:a {:b {:c 5} :e {:f 6}}}} => "a[e][f]=6&a[b][c]=5"
{% endhighlight %}
httpkit and clj-http do so by <code>encoding</code> the nested data structure, expect server understand the encoding, and do the proper decoding.
This is not robust. Recommanded usage is do the encoding and decoding explicitly, using your favorite encoding.
<p>Take JSON for example: </p>
{% highlight clojure %}
(require '[ :as json])
(http/post "http://your-server/api"
;; using json to encode the nested params before pass to httpkit
{:query-params {:a (json/write-str {:b {:c 5} :e {:f 6}})}})
;; on the server side, get the param, decode it using json/read-str.
;; json supports more data structure and is easier to understand and reason about.
;; It's also cross language, has minimum requirement for your server's implementation
{% endhighlight %}
<h3 class="anchor" id="options">Various options</h3>
{% highlight clojure %}
(http/request {:url ""
:method :get ; :post :put :head or other
:user-agent "User-Agent string"
:oauth-token "your-token"
:headers {"X-header" "value"
"X-Api-Version" "2"}
:query-params {"q" "foo, bar"} ;"Nested" query parameters are also supported
:form-params {"q" "foo, bar"} ; just like query-params, except sent in the body
:body (json/encode {"key" "value"}) ; use this for content-type json
:basic-auth ["user" "pass"]
:keepalive 3000 ; Keep the TCP connection for 3000ms
:timeout 1000 ; connection timeout and reading timeout 1000ms
:filter (http/max-body-filter (* 1024 100)) ; reject if body is more than 100k
:insecure? true ; Need to contact a server with an untrusted SSL cert?
;; File upload. :content can be a,, String
;; It read the whole content before send them to server:
;; should be used when the file is small, say, a few megabytes
:multipart [{:name "comment" :content "httpkit's project.clj"}
{:name "file" :content ( "project.clj") :filename "project.clj"}]
:max-redirects 10 ; Max redirects to follow
;; whether follow 301/302 redirects automatically, default to true
;; :trace-redirects will contain the chain of the redirections followed.
:follow-redirects false
{% endhighlight %}
<p><code>http/get</code> <code>http/post</code> etc, are build on top of <code>http/request</code>, so, these options apply to them too</p>
<h3 class="anchor" id="faking">Faking Responses in Tests</h3>
<p>Often in tests you want to prevent requests from being sent over HTTP by stubbing out calls to the client.
This can be done with <a href="">http-kit-fake</a>.
{% highlight clojure %}
(with-fake-http ["" "a fake response"]
(http/get {:url ""})) ; promise wrapping the faked response
{% endhighlight %}
<p>See the http-kit-fake <a href="">README</a> for a full set of options.</p>