HTTP client

(:require [org.httpkit.client :as http])
  

Like the Server, the client uses an event-driven, non-blocking I/O model. It's lightweight and efficient.

  • Concurrent made easy by asynchronous and promise
  • Keep-alived, quite a difference in performance, {:keepalive -1} to disable
  • Timeout per request
  • HTTPS supported, ~100k of RAM for issuing a HTTPS request. TCP connection, SSLEngine get reused if possible
  • Easy to use API, modeled after clj-http

asynchronous with promise, callback

;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 handle response
            (if error
              (println "Failed, exception is " error)
              (println "Async HTTP GET: " status))))
 ; [1] may not always true, since DNS lookup maybe slow
  

synchronous with @promise

Synchronous programming is easy to understand, just like clj-http:

;; 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)))
  

Combined, concurrent requests, handle results synchronously

Sent request concurrently, half the waiting time

(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)))
(let [urls ["http://server.com/api/1" "http://server.com/api/2"
            "http://server.com/api/3"]
      futures (doall (map http/get urls))]
  (doseq [f futures]
    ;; wait for server response concurrently
    (println (-> @f :opt :url) " status: " (:status @f))
    ))

Persistent connection

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

HTTPS persistent connection is also supported.

By default, http-kit keeps idle connection for 120s. That can be configured by the keepalive option:

 ; 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})

Pass state to asynchronous callback

Sometimes, it's handy to pass some state to callback. You can do it this way:

(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))
  

Output coercion

;; 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})