-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathclient.html
202 lines (168 loc) · 8.7 KB
/
client.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
---
layout: doc
title: HTTP client
---
<h2 id="title">HTTP client</h2>
{% highlight clojure %}
(:require [org.httpkit.client :as http])
{% endhighlight %}
<p>
Like the Server, the client uses an event-driven, non-blocking I/O model. It's lightweight and efficient.
</p>
<ul>
<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="https://github.com/dakrone/clj-http">clj-http</a></li>
</ul>
<h3 id="async" class="anchor">asynchronous with promise, callback</h3>
{% highlight clojure %}
;fire and forget, returns immediately[1], returned promise is ignored
(http/get "http://host.com/path")
(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 "http://host.com/path" 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 "http://host.com/path")]
(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 "http://host.com/path1" 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 "http://http-kit.org/")
resp2 (http/get "http://clojure.org/")]
(println "Response 1's status: " (:status @resp1)) ; wait as necessary
(println "Response 2's status: " (:status @resp2)))
{% endhighlight %}
{% highlight clojure %}
(let [urls ["http://server.com/api/1" "http://server.com/api/2"
"http://server.com/api/3"]
;; send the request concurrently (asynchronously)
futures (doall (map http/get urls))]
(doseq [resp futures]
;; wait for server response synchronously
(println (-> @resp :opts :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 "http://http-kit.org" {:keepalive 30000})
; will reuse the previous TCP connection
@(http/get "http://http-kit.org" {:keepalive 30000})
; disable keepalive for this request
@(http/get "http://http-kit.org" {: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 "http://http-kit.org" opts callback))
{% endhighlight %}
<h3 class="anchor" id="coercion">Output coercion</h3>
{% highlight clojure %}
;; Return the body as a byte stream
(http/get "http://site.com/favicon.ico" {:as :stream}
(fn [{:keys [status headers body error opts]}]
;; body is a java.io.InputStream
))
;; Coerce as a byte-array
(http/get "http://site.com/favicon.ico" {:as :byte-array}
(fn [{:keys [status headers body error opts]}]
;; body is a byte[]
))
;; return the body as a string body
(http/get "http://site.com/string.txt" {: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 "http://site.com/string.txt" {:as :auto})
{% endhighlight %}
<h3 class="anchor" id="nested-params">Nested params</h3>
<p>
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>
<p>Take JSON for example: </p>
{% highlight clojure %}
(require '[clojure.data.json :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 "http://http-kit.org/"
: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 java.io.File, java.io.InputStream, 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 (clojure.java.io/file "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="https://github.com/d11wtq/http-kit-fake">http-kit-fake</a>.
</p>
{% highlight clojure %}
(with-fake-http ["http://http-kit.org/" "a fake response"]
(http/get {:url "http://http-kit.org/"})) ; promise wrapping the faked response
{% endhighlight %}
<p>See the http-kit-fake <a href="https://github.com/d11wtq/http-kit-fake">README</a> for a full set of options.</p>