Skip to content

Commit

Permalink
Merge with cddeb3e and add docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tuzi_li committed Oct 5, 2016
2 parents 2cce747 + aa8e2a7 commit 720dba3
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 42 deletions.
97 changes: 90 additions & 7 deletions README.org
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#+AUTHOR: Lee Hinman
#+STARTUP: align fold nodlcheck lognotestate showall
#+OPTIONS: H:4 num:nil toc:t \n:nil @:t ::t |:t ^:{} -:t f:t *:t
#+OPTIONS: skip:nil d:(HIDE) tags:not-in-toc
#+OPTIONS: skip:nil d:(HIDE) tags:not-in-toc auto-id:t
#+PROPERTY: header-args :results code :exports both :noweb yes
#+HTML_HEAD: <style type="text/css"> body {margin-right:15%; margin-left:15%;} </style>
#+LANGUAGE: en
Expand All @@ -23,6 +23,7 @@
- [[#put][PUT]]
- [[#post][POST]]
- [[#delete][DELETE]]
- [[#async][Async HTTP Request]]
- [[#coercions][Coercions]]
- [[#headers][Headers]]
- [[#meta-tag-headers][Meta Tag Headers]]
Expand Down Expand Up @@ -102,22 +103,19 @@ function.
With Leiningen/Boot:

#+BEGIN_SRC clojure
[clj-http "2.2.0"]
[clj-http "2.3.0"]
#+END_SRC

If you want to test out the latest version, a 3.x release is also available.

#+BEGIN_SRC clojure
[clj-http "3.2.0"]
[clj-http "3.3.0"]
#+END_SRC

Previous versions available as:
The previous major version is available as:

#+BEGIN_SRC clojure
[clj-http "2.0.1"]
[clj-http "1.1.2"]
[clj-http "1.1.1"]
[clj-http "1.0.1"]
#+END_SRC

clj-http supports clojure 1.6.0 and higher.
Expand Down Expand Up @@ -353,6 +351,26 @@ content encodings.

#+END_SRC

** Async HTTP Request
:PROPERTIES:
:CUSTOM_ID: h:0e3eb987-5b2b-4874-97ef-b834394d083d
:END:
The new async HTTP request API is a Ring-style async API.
All options for synchronous request can use in asynchronous requests.
start an async request is easy, for example:

#+BEGIN_SRC clojure
;; :async? in options map need to be true
(client/get "http://example.com"
{:async? true}
;; respond callback
(fn [response] (println "response is:" response))
;; raise callback
(fn [exception] (println "exception message is: " (.getMessage exception))))
#+END_SRC

All exceptions thrown during the request will be passed to the raise callback.

** Coercions
:PROPERTIES:
:CUSTOM_ID: h:8902cd95-e01e-4d9b-9dc8-5f5c8f04504b
Expand Down Expand Up @@ -471,6 +489,33 @@ This special treatment of headers is implemented in the
wrap-header-map middleware, which (like any middleware) can be
disabled by using with-middleware to specify different behavior.

** Query-string parameters
:PROPERTIES:
:CUSTOM_ID: h:dd49992c-a516-4af0-9735-4f4340773361
:END:

There are three different ways that query string parameters for array values can
be generated, depending on what the resulting query string should look like,
they are:

- A repeating parameter (default)
- Array style
- Indexed array style

Here is an example of the input and output for the ~:query_string~ parameter,
controlled by the ~:multi-param-style~ option:

#+BEGIN_SRC clojure
;; default style, with :multi-param-style unset
:a [1 2 3] => "a=1&a=2&a=3"
;; with :multi-param-style :array, a repeating param with array suffix
;; (PHP-style):
:a [1 2 3] => "a[]=1&a[]=2&a[]=3"
;; with :multi-param-style :indexed, a repeating param with array suffix and
;; index (Rails-style):
:a [1 2 3] => "a[0]=1&a[1]=2&a[2]=3"
#+END_SRC

** Meta Tag Headers
:PROPERTIES:
:CUSTOM_ID: h:01663a63-8bc8-45da-8a3d-341402f3f3fa
Expand Down Expand Up @@ -840,10 +885,46 @@ connections are being used:
(get "http://aoeu.com/999"))
#+END_SRC

For async request, you can use =with-async-connection-pool=

#+BEGIN_SRC clojure
(with-async-connection-pool {:timeout 5 :threads 4 :insecure? false :default-per-route 10}
(get "http://aoeu.com/1" {async? :true} resp1 exce1)
(post "http://aoeu.com/2" {async? :true} resp2 exce2)
(get "http://aoeu.com/3" {async? :true} resp3 exce3)
...
(get "http://aoeu.com/999" {async? :true} resp999 exce999))
#+END_SRC

This is MUCH faster than sequentially performing all requests, because a
persistent connection can be used instead creating a new connection for each
request.

If you want to start an async request in the =respond= callback of an async request and
reuse the pool context, just use =reuse-pool=.

#+BEGIN_SRC clojure
(with-async-connection-pool {:timeout 5 :threads 4 :insecure? false :default-per-route 10}
(get "http://aoeu.com/1" {async? :true} resp1 exce1)
(post "http://aoeu.com/2"
{async? :true}
(fn [resp] (get "http://aoeu.com/3"
(reuse-pool {async? :true} resp)
resp3 exce3))
exce2))
#+END_SRC

To implement the persistent connections of async requests, we add a middleware
named =wrap-async-pooling= to the default middleware list. This middleware's
behaviour depends on =*pooling-info*= binding or =:polling-info= in options map.
The =pooling-info= contains =:conn-mgr=, =:allocate= and =:release=.
=:conn-mgr= is the connection manager used in the pooling context, =:allocate=
is a function will be invoked when request start and =:release= will be invoked
when request finished.

=with-async-connection-pool= use the =wrap-async-pooling= to manage the
connection manager, you can also implement your manage strategy.

If you would prefer to handle managing the connection manager yourself, you can
create a connection manager yourself and specify it for each request:

Expand All @@ -863,6 +944,8 @@ create a connection manager yourself and specify it for each request:
See the docstring on =make-reusable-conn-manager= for options and default
values.

In current version, pooled async request CANNOT specify connection manager.

** Proxies
:PROPERTIES:
:CUSTOM_ID: h:49f9ca81-0bad-4cd8-87ac-c09a85fa5500
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject clj-http "3.2.1-SNAPSHOT"
(defproject clj-http "3.3.1-SNAPSHOT"
:description "A Clojure HTTP library wrapping the Apache HttpComponents client."
:url "https://github.com/dakrone/clj-http/"
:license {:name "The MIT License"
Expand Down
60 changes: 32 additions & 28 deletions src/clj_http/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -716,25 +716,31 @@
(second found))
"UTF-8"))

(defn generate-query-string-with-encoding [params encoding]
(defn- multi-param-suffix [index multi-param-style]
(case multi-param-style
:indexed (str "[" index "]")
:array "[]"
""))

(defn generate-query-string-with-encoding [params encoding multi-param-style]
(str/join "&"
(mapcat (fn [[k v]]
(if (sequential? v)
(map #(str (util/url-encode (name %1) encoding)
"="
(util/url-encode (str %2) encoding))
(repeat k) v)
(map-indexed #(str (util/url-encode (name k) encoding)
(multi-param-suffix %1 multi-param-style)
"="
(util/url-encode (str %2) encoding)) v)
[(str (util/url-encode (name k) encoding)
"="
(util/url-encode (str v) encoding))]))
params)))

(defn generate-query-string [params & [content-type]]
(defn generate-query-string [params & [content-type multi-param-style]]
(let [encoding (detect-charset content-type)]
(generate-query-string-with-encoding params encoding)))
(generate-query-string-with-encoding params encoding multi-param-style)))

(defn- query-params-request
[{:keys [query-params content-type]
[{:keys [query-params content-type multi-param-style]
:or {content-type :x-www-form-urlencoded}
:as req}]
(if query-params
Expand All @@ -746,7 +752,8 @@
new-query-string))
(generate-query-string
query-params
(content-type-value content-type))))
(content-type-value content-type)
multi-param-style)))
req))

(defn wrap-query-params
Expand Down Expand Up @@ -870,11 +877,13 @@
:json-opts json-opts})))
(json-encode form-params json-opts))

(defmethod coerce-form-params :default [{:keys [content-type form-params
(defmethod coerce-form-params :default [{:keys [content-type
multi-param-style
form-params
form-param-encoding]}]
(if form-param-encoding
(generate-query-string-with-encoding form-params form-param-encoding)
(generate-query-string form-params (content-type-value content-type))))
(generate-query-string-with-encoding form-params form-param-encoding multi-param-style)
(generate-query-string form-params (content-type-value content-type) multi-param-style)))

(defn- form-params-request
[{:keys [form-params content-type request-method]
Expand All @@ -896,25 +905,20 @@
([req respnd raise]
(client (form-params-request req) respnd raise))))

(defn- nest-kv
[kv]
(if (and (vector? kv)
(or (map? (second kv))
(vector? (second kv))))
(let [[fk m] kv]
(reduce-kv (fn [m sk v]
(assoc m
(str (name fk)
\[ (if (integer? sk) sk (name sk)) \])
v))
{}
m))
kv))

(defn- nest-params
[request param-key]
(if-let [params (request param-key)]
(assoc request param-key (prewalk nest-kv params))
(assoc request param-key (prewalk
#(if (and (vector? %) (map? (second %)))
(let [[fk m] %]
(reduce
(fn [m [sk v]]
(assoc m (str (name fk)
\[ (name sk) \]) v))
{}
m))
%)
params))
request))

(defn- nest-params-request
Expand Down
39 changes: 34 additions & 5 deletions test/clj_http/test/client_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1107,11 +1107,6 @@
(are [in out] (is-applied client/wrap-nested-params
{:query-params in :form-params in}
{:query-params out :form-params out})
{"x" ["0" "1"]} {"x[0]" "0" "x[1]" "1"}

{"x" [{"y" "a" "z" "b"} {"w" "c"}]}
{"x[0][y]" "a" "x[0][z]" "b" "x[1][w]" "c"}

{"foo" "bar"} {"foo" "bar"}
{"x" {"y" "z"}} {"x[y]" "z"}
{"a" {"b" {"c" "d"}}} {"a[b][c]" "d"}
Expand Down Expand Up @@ -1462,3 +1457,37 @@
(is (= 1 (:major protocol-version)))
(is (= 1 (:minor protocol-version)))
(is (= "OK" (:reason-phrase resp)))))

(deftest ^:integration multi-valued-query-params
(run-server)
(testing "default (repeating) multi-valued query params"
(let [resp (request {:uri "/query-string"
:method :get
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a=1&a=2&a=3") query-string)
(is (.contains query-string "b=x&b=y&b=z") query-string)))

(testing "multi-valued query params in indexed-style"
(let [resp (request {:uri "/query-string"
:method :get
:multi-param-style :indexed
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a[0]=1&a[1]=2&a[2]=3") query-string)
(is (.contains query-string "b[0]=x&b[1]=y&b[2]=z") query-string)))

(testing "multi-valued query params in array-style"
(let [resp (request {:uri "/query-string"
:method :get
:multi-param-style :array
:query-params {:a [1 2 3]
:b ["x" "y" "z"]}})
query-string (-> resp :body form-decode-str)]
(is (= 200 (:status resp)))
(is (.contains query-string "a[]=1&a[]=2&a[]=3") query-string)
(is (.contains query-string "b[]=x&b[]=y&b[]=z") query-string))))
4 changes: 3 additions & 1 deletion test/clj_http/test/core_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@
[:propfind "/propfind"]
{:status 200 :body "propfind"}
[:propfind "/propfind-with-body"]
{:status 200 :body (:body req)}))
{:status 200 :body (:body req)}
[:get "/query-string"]
{:status 200 :body (:query-string req)}))

(defn run-server
[]
Expand Down

0 comments on commit 720dba3

Please sign in to comment.