pax_global_header 0000666 0000000 0000000 00000000064 14070752310 0014511 g ustar 00root root 0000000 0000000 52 comment=44ce155754dbb375837dd1729b97e55c96046f9d
clj-http-3.12.3/ 0000775 0000000 0000000 00000000000 14070752310 0013324 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/.github/ 0000775 0000000 0000000 00000000000 14070752310 0014664 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/.github/workflows/ 0000775 0000000 0000000 00000000000 14070752310 0016721 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/.github/workflows/clojure.yml 0000664 0000000 0000000 00000001703 14070752310 0021110 0 ustar 00root root 0000000 0000000 name: Clojure CI
on:
push:
branches: [ 3.x ]
pull_request:
branches: [ 3.x ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: ["8", "11", "14"]
clojure: ["1.6", "1.7", "1.8", "1.9", "1.10"]
name: Java ${{ matrix.java }} Clojure ${{ matrix.clojure }}
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: ~/.m2/repository
key: ${{ runner.os }}-lein-${{ hashFiles('**/project.clj') }}
restore-keys: |
${{ runner.os }}-lein-
- name: Setup java
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- name: Install dependencies
run: lein deps
- name: Run tests
run: lein with-profile dev,${{matrix.clojure}} test :all
- name: Check Reflection Warnings
run: '! lein with-profile dev,${{matrix.clojure}} check 2>&1 | egrep "Reflection warning|Performance warning"'
clj-http-3.12.3/.gitignore 0000664 0000000 0000000 00000000555 14070752310 0015321 0 ustar 00root root 0000000 0000000 # leiningen .gitignore defaults
/target
/classes
/checkouts
pom.xml
pom.xml.asc
*.jar
*.class
/.lein-*
/.nrepl-port
# custom from here on out
build
lib
*.dot
# use glob syntax.
syntax: glob
creds.clj
Manifest.txt
aws.clj
*.ser
*~
*.bak
*.off
*.old
.DS_Store
*.#*
*#*
*.classpath
*.project
*.settings
*.pyc
docs/*
doc
http.log
# Intellij Idea
/*.iml
/.idea
log/
clj-http-3.12.3/CONTRIBUTING.md 0000664 0000000 0000000 00000001222 14070752310 0015552 0 ustar 00root root 0000000 0000000 # Contributing Guidelines
First, thanks for the contributing! Hopefully you find it fairly painless, but
in the interest of explanation, here are some things you might be interested in
when contributing code:
- Please run the tests locally if you submit a change, you can use `lein all
test :all` to ensure that they pass locally
- If you're able, adding tests with a PR is fantastic! If not, no worries, I can
add those later
- Don't hesitate to ask if you have questions, use `@dakrone` or you can email
me (if it's something you can't talk about publically) at `lee [at]
writequit.org`
That's it, thanks for using and contributing to clj-http!
clj-http-3.12.3/LICENSE 0000664 0000000 0000000 00000002070 14070752310 0014330 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2013 M. Lee Hinman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
clj-http-3.12.3/README.org 0000664 0000000 0000000 00000176554 14070752310 0015014 0 ustar 00root root 0000000 0000000 #+TITLE: clj-http documentation
#+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 auto-id:t
#+PROPERTY: header-args :results code :exports both :noweb yes
#+HTML_HEAD:
#+LANGUAGE: en
[[https://clojars.org/clj-http][file:https://img.shields.io/clojars/v/clj-http.svg]] [[https://github.com/dakrone/clj-http/actions?query=workflow%3A%22Clojure+CI%22][file:https://github.com/dakrone/clj-http/workflows/Clojure%20CI/badge.svg]] [[https://gitter.im/clj-http/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge][file:https://badges.gitter.im/clj-http/Lobby.svg]]
* Table of Contents :TOC_3:
:PROPERTIES:
:CUSTOM_ID: h-aaf075ea-2f0e-4a45-871a-0f89c838fb4b
:END:
- [[#branches][Branches]]
- [[#introduction][Introduction]]
- [[#overview][Overview]]
- [[#philosophy][Philosophy]]
- [[#installation][Installation]]
- [[#quickstart][Quickstart]]
- [[#head][HEAD]]
- [[#get][GET]]
- [[#put][PUT]]
- [[#post][POST]]
- [[#delete][DELETE]]
- [[#async-http-request][Async HTTP Request]]
- [[#cancelling-requests][Cancelling Requests]]
- [[#coercions][Coercions]]
- [[#input-coercion][Input coercion]]
- [[#output-coercion][Output coercion]]
- [[#headers][Headers]]
- [[#query-string-parameters][Query-string parameters]]
- [[#meta-tag-headers][Meta Tag Headers]]
- [[#link-headers][Link Headers]]
- [[#redirects][Redirects]]
- [[#how-to-create-a-custom-redirectstrategy][How to create a custom RedirectStrategy]]
- [[#cookies][Cookies]]
- [[#cookiestores][Cookiestores]]
- [[#keystores-trust-stores][Keystores, Trust-stores]]
- [[#exceptions][Exceptions]]
- [[#decompression][Decompression]]
- [[#debugging][Debugging]]
- [[#logging][Logging]]
- [[#caching][Caching]]
- [[#authentication][Authentication]]
- [[#basic-auth][Basic Auth]]
- [[#digest-auth][Digest Auth]]
- [[#ntlm-auth][NTLM Auth]]
- [[#oauth2][oAuth2]]
- [[#advanced-usage][Advanced Usage]]
- [[#raw-request][Raw Request]]
- [[#boolean-options][Boolean options]]
- [[#persistent-connections][Persistent Connections]]
- [[#re-using-httpclient-between-requests][Re-using =HttpClient= between requests]]
- [[#proxies][Proxies]]
- [[#custom-middleware][Custom Middleware]]
- [[#modifying-apache-specific-features-of-the-httpclientbuilder-and-httpasyncclientbuilder][Modifying Apache-specific features of the =HttpClientBuilder= and =HttpAsyncClientBuilder=]]
- [[#incrementally-json-parsing][Incrementally JSON Parsing]]
- [[#dns-resolution][DNS Resolution]]
- [[#development][Development]]
- [[#faking-responses][Faking Responses]]
- [[#optional-dependencies][Optional Dependencies]]
- [[#clj-http-lite][clj-http-lite]]
- [[#troubleshooting][Troubleshooting]]
- [[#verifyerror-class-orgcodehausjacksonsmilesmileparser-overrides-final-method-getbinaryvalue][VerifyError class org.codehaus.jackson.smile.SmileParser overrides final method getBinaryValue...]]
- [[#nohttpresponseexception--due-to-stale-connections][NoHttpResponseException ... due to stale connections**]]
- [[#tests][Tests]]
- [[#testimonials][Testimonials]]
- [[#other-libraries-providing-middleware][Other Libraries Providing Middleware]]
- [[#license][License]]
* Branches
:PROPERTIES:
:CUSTOM_ID: h-e390585c-cbd8-4e94-b36b-4e9c27c16720
:END:
There are branches for the major version numbers:
- 2.x (no longer maintained except for security issues)
- 3.x (current stable releases and the main Github branch)
- master (which is 4.x, unreleased, based on version 5 of the apache http client)
* Introduction
:PROPERTIES:
:CUSTOM_ID: h-d893078a-b20b-4086-9272-3d9c28c86846
:END:
** Overview
:PROPERTIES:
:CUSTOM_ID: h-d8b17d06-124e-44fd-9c86-0399f39b0254
:END:
clj-http is an HTTP library wrapping the [[http://hc.apache.org/][Apache HttpComponents]] client. This
library has taken over from mmcgrana's clj-http.
** Philosophy
:PROPERTIES:
:CUSTOM_ID: h-aa21d07d-333b-4ff2-93a9-ffdca31d8949
:END:
The design of =clj-http= is inspired by the [[https://github.com/ring-clojure/ring][Ring]] protocol for Clojure HTTP
server applications.
The client in =clj-http.core= makes HTTP requests according to a given Ring
request map and returns [[https://github.com/ring-clojure/ring/blob/master/SPEC][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.
* Installation
:PROPERTIES:
:CUSTOM_ID: h-ddfce0e2-6797-4774-add5-d5cf5bfaaa17
:END:
=clj-http= is available as a Maven artifact from [[http://clojars.org/clj-http][Clojars]].
With Leiningen/Boot:
#+BEGIN_SRC clojure
[clj-http "3.12.3"]
#+END_SRC
If you need an older version, a 2.x release is also available.
#+BEGIN_SRC clojure
[clj-http "2.3.0"]
#+END_SRC
clj-http 3.x supports clojure 1.6.0 and higher.
clj-http 4.x will support clojure 1.7.0 and higher.
* Quickstart
:PROPERTIES:
:CUSTOM_ID: h-65f0132e-1f96-4711-a84e-973817f37dd3
:END:
The main HTTP client functionality is provided by the =clj-http.client= namespace.
First, require it in the REPL:
#+BEGIN_SRC clojure
(require '[clj-http.client :as client])
#+END_SRC
Or in your application:
#+BEGIN_SRC clojure
(ns my-app.core
(:require [clj-http.client :as client]))
#+END_SRC
The client supports simple =get=, =head=, =put=, =post=, =delete=, =copy=,
=move=, =patch=, and =options= requests. Response are returned as [[https://github.com/ring-clojure/ring/blob/master/SPEC][Ring-style
response maps]]:
** HEAD
:PROPERTIES:
:CUSTOM_ID: h-79d1bb5f-c695-46a6-af4e-a64ca599c978
:END:
#+BEGIN_SRC clojure
(client/head "http://example.com/resource")
(client/head "http://example.com/resource" {:accept :json})
#+END_SRC
** GET
:PROPERTIES:
:CUSTOM_ID: h-89c164fb-85c2-4953-a8c4-a50867adf42a
:END:
Example requests:
#+BEGIN_SRC clojure
(client/get "http://example.com/resources/id")
;; Setting options
(client/get "http://example.com/resources/3" {:accept :json})
(client/get "http://example.com/resources/3" {:accept :json :query-params {"q" "foo, bar"}})
;; Specifying headers as either a string or collection:
(client/get "http://example.com"
{:headers {"foo" ["bar" "baz"], "eggplant" "quux"}})
;; Using either string or keyword header names:
(client/get "http://example.com"
{:headers {:foo ["bar" "baz"], :eggplant "quux"}})
;; Completely ignore cookies:
(client/post "http://example.com" {:cookie-policy :none})
;; There are also multiple ways to handle cookies
(client/post "http://example.com" {:cookie-policy :default})
(client/post "http://example.com" {:cookie-policy :netscape})
(client/post "http://example.com" {:cookie-policy :standard})
(client/post "http://example.com" {:cookie-policy :standard-strict})
;; Cookies can be completely configurable with a custom spec by adding a
;; function to return a cookie spec for parsing the cookie. For example, if you
;; wanted to configure a spec provider to have a certain compatibility level:
(client/post "http://example.com"
{:cookie-spec
(fn [http-context]
(println "generating a new cookie spec")
(.create
(org.apache.http.impl.cookie.RFC6265CookieSpecProvider.
org.apache.http.impl.cookie.RFC6265CookieSpecProvider$CompatibilityLevel/IE_MEDIUM_SECURITY
(PublicSuffixMatcherLoader/getDefault))
http-context))})
;; Or a version with relaxed compatibility
(client/post "http://example.com"
{:cookie-spec
(fn [http-context]
(println "generating a new cookie spec")
(.create
(org.apache.http.impl.cookie.RFC6265CookieSpecProvider.
org.apache.http.impl.cookie.RFC6265CookieSpecProvider$CompatibilityLevel/RELAXED
(PublicSuffixMatcherLoader/getDefault))
http-context))})
;; Sometimes you want to do your own validation or something, which you can do
;; by proxying the CookieSpecBase. Note that this doesn't actually return the
;; cookies, because clj-http does its own cookie parsing. If you want to store
;; the cookies from these methods you'll need to use a cookie store or put it in
;; some datastructure yourself.
(client/post "http://example.com"
{:cookie-spec
(fn [http-context]
(proxy [org.apache.http.impl.cookie.CookieSpecBase] []
;; Version and version header
(getVersion [] 0)
(getVersionHeader [] nil)
;; parse headers into cookie objects
(parse [header cookie-origin] (java.util.ArrayList.))
;; Validate a cookie, throwing MalformedCookieException if the
;; cookies isn't valid
(validate [cookie cookie-origin]
(println "validating:" cookie))
;; Determine if a cookie matches the target location
(match [cookie cookie-origin] true)
;; Format a list of cookies into a list of headers
(formatCookies [cookies] (java.util.ArrayList.))))})
;; If you have created your own registry for cookie policies, you can provide
;; :cookie-policy-registry to use it. See
;; clj-http.core/create-custom-cookie-policy-registry for an example of a custom
;; registry
(client/post "http://example.com"
{:cookie-policy-registry my-custom-policy-registry
:cookie-policy "my-policy"})
;; Need to contact a server with an untrusted SSL cert?
(client/get "https://alioth.debian.org" {:insecure? true})
;; If you don't want to follow-redirects automatically:
(client/get "http://example.com/redirects-somewhere" {:redirect-strategy :none})
;; Only follow a certain number of redirects:
(client/get "http://example.com/redirects-somewhere" {:max-redirects 5})
;; Avoid throwing exceptions if redirected too many times:
(client/get "http://example.com/redirects-somewhere" {:max-redirects 5 :redirect-strategy :graceful})
;; Throw an exception if the get takes too long. Timeouts in milliseconds.
(client/get "http://example.com/redirects-somewhere" {:socket-timeout 1000 :connection-timeout 1000})
;; Query parameters
(client/get "http://example.com/search" {:query-params {"q" "foo, bar"}})
;; "Nested" query parameters
;; (this yields a query string of `a[e][f]=6&a[b][c]=5`)
(client/get "http://example.com/search" {:query-params {:a {:b {:c 5} :e {:f 6}}}})
;; Provide cookies — uses same schema as :cookies returned in responses
;; (see the cookie store option for easy cross-request maintenance of cookies)
(client/get "http://example.com"
{:cookies {"ring-session" {:discard true, :path "/", :value "", :version 0}}})
;; Tell clj-http not to decode cookies from the response header
(client/get "http://example.com" {:decode-cookies false})
;; Support for IPv6!
(client/get "http://[2001:62f5:9006:e472:cabd:c8ff:fee3:8ddf]")
;; Super advanced, your own http-client-context and request-config
(client/get "http://example.com/get"
{:http-client-context my-http-client-context
:http-request-config my-request-config})
#+END_SRC
The client will also follow redirects on the appropriate =30*= status codes.
The client transparently accepts and decompresses the =gzip= and =deflate=
content encodings.
=:trace-redirects= will contain the chain of the redirections followed.
** PUT
:PROPERTIES:
:CUSTOM_ID: h-1582cd6e-a6e8-49c8-96e3-28eee6128c31
:END:
#+BEGIN_SRC clojure
(client/put "http://example.com/api" {:body "my PUT body"})
#+END_SRC
** POST
:PROPERTIES:
:CUSTOM_ID: h-32c8ca7a-0ef2-41b8-8158-20b0e2945e5d
:END:
#+BEGIN_SRC clojure
;; Various options:
(client/post "http://example.com/api"
{:basic-auth ["user" "pass"]
:body "{\"json\": \"input\"}"
:headers {"X-Api-Version" "2"}
:content-type :json
:socket-timeout 1000 ;; in milliseconds
:connection-timeout 1000 ;; in milliseconds
:accept :json})
;; Send form params as a urlencoded body (POST or PUT)
(client/post "http://example.com" {:form-params {:foo "bar"}})
;; Send form params as a json encoded body (POST or PUT)
(client/post "http://example.com" {:form-params {:foo "bar"} :content-type :json})
;; Send form params as a json encoded body (POST or PUT) with options
(client/post "http://example.com" {:form-params {:foo "bar"}
:content-type :json
:json-opts {:date-format "yyyy-MM-dd"}})
;; You can also specify the encoding of form parameters
(client/post "http://example.com" {:form-params {:foo "bar"}
:form-param-encoding "ISO-8859-1"})
;; Send form params as a Transit encoded JSON body (POST or PUT) with options
(client/post "http://example.com" {:form-params {:foo "bar"}
:content-type :transit+json
:transit-opts
{:encode {:handlers {}}
:decode {:handlers {}}}})
;; Send form params as a Transit encoded MessagePack body (POST or PUT) with options
(client/post "http://example.com" {:form-params {:foo "bar"}
:content-type :transit+msgpack
:transit-opts
{:encode {:handlers {}}
:decode {:handlers {}}}})
;; Multipart form uploads/posts
;; takes a vector of maps, to preserve the order of entities, :name
;; will be used as the part name unless :part-name is specified
(client/post "http://example.org" {:multipart [{:name "title" :content "My Awesome Picture"}
{:name "Content/type" :content "image/jpeg"}
{:name "foo.txt" :part-name "eggplant" :content "Eggplants"}
{:name "file" :content (clojure.java.io/file "pic.jpg")}]
;; You can also optionally pass a :mime-subtype
:mime-subtype "foo"})
;; Multipart :content values can be one of the following:
;; String, InputStream, File, a byte-array, or an instance of org.apache.http.entity.mime.content.ContentBody
;; Some Multipart bodies can also support more keys (like :encoding
;; and :mime-type), check src/clj-http/multipart.clj to see all flags
;; Apache's http client automatically retries on IOExceptions, if you
;; would like to handle these retries yourself, you can specify a
;; :retry-handler. Return true to retry, false to stop trying:
(client/post "http://example.org" {:multipart [["title" "Foo"]
["Content/type" "text/plain"]
["file" (clojure.java.io/file "/tmp/missing-file")]]
:retry-handler (fn [ex try-count http-context]
(println "Got:" ex)
(if (> try-count 4) false true))})
;; to handle a file with non-ascii filename, try :multipart-charset "UTF-8" and :multipart-mode BROWSER_COMPATIBLE
;; see also: https://stackoverflow.com/questions/3393445/international-characters-in-filename-in-mutipart-formdata
(import (org.apache.http.entity.mime HttpMultipartMode))
(client/post "http://example.org" {:multipart [{:content (clojure.java.io/file "日本語.txt")}]
:multipart-mode HttpMultipartMode/BROWSER_COMPATIBLE
:multipart-charset "UTF-8"} )
#+END_SRC
A word about flattening nested =:query-params= and =:form-params= maps. There are essentially three
different ways to handle flattening them:
- =:ignore-nested-query-string= :: Do not handle nested query parameters specially, treat them as
the exact text they come in as. Defaults to *false*.
- =:flatten-nested-form-params= :: Flatten nested (map within a map) =:form-params= before encoding
it as the body. Defaults to *false*, meaning form params are encoded only
=x-www-form-urlencoded=.
- =:flatten-nested-keys= :: An advanced way of specifying which keys having nested maps should be
flattened. A middleware function checks the previous two options
(=:ignore-nested-query-string= and =:flatten-nested-form-params=) and modifies this to be the
list that will be flattened.
** DELETE
:PROPERTIES:
:CUSTOM_ID: h-c7165d6b-232a-439d-9390-8c05e6ef1e6f
:END:
#+BEGIN_SRC clojure
(client/delete "http://example.com/resource")
#+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.
*** Cancelling Requests
:PROPERTIES:
:CUSTOM_ID: cancelling-requests
:END:
Calls to the http methods with =:async true= return an Apache [[https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/concurrent/BasicFuture.html][BasicFuture]] that you can call =.get=
or =.cancel= on. See the Javadocs for =BasicFuture= [[https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/concurrent/BasicFuture.html][here]]. For instance:
#+BEGIN_SRC clojure
(import '(java.util.concurrent TimeoutException TimeUnit))
(let [future (client/get "http://example.com/slow-url"
{:async true :oncancel #(println "request was cancelled")}
#(println :got %) #(println :err %))]
(try
(.get future 1 TimeUnit/SECONDS)
(catch TimeoutException e
;; Cancel the request, it's taken too long
(.cancel future true))))
#+END_SRC
** Coercions
:PROPERTIES:
:CUSTOM_ID: h-8902cd95-e01e-4d9b-9dc8-5f5c8f04504b
:END:
clj-http allows coercing the body of the request either before it is sent (input coercion), or after
it's received (output coercion) from the server.
*** Input coercion
:PROPERTIES:
:CUSTOM_ID: h-bed01743-2209-473d-ae86-bd187f059e0c
:END:
#+BEGIN_SRC clojure
;; body as a byte-array
(client/post "http://example.com/resources" {:body my-byte-array})
;; body as a string
(client/post "http://example.com/resources" {:body "string"})
;; :body-encoding is optional and defaults to "UTF-8"
(client/post "http://example.com/resources"
{:body "string" :body-encoding "UTF-8"})
;; body as a file
(client/post "http://example.com/resources"
{:body (clojure.java.io/file "/tmp/foo") :body-encoding "UTF-8"})
;; :length is optional for passing in an InputStream; if not
;; supplied it will default to -1 to signal to HttpClient to use
;; chunked encoding
(client/post "http://example.com/resources"
{:body (clojure.java.io/input-stream "/tmp/foo")})
(client/post "http://example.com/resources"
{:body (clojure.java.io/input-stream "/tmp/foo") :length 1000})
#+END_SRC
*** Output coercion
:PROPERTIES:
:CUSTOM_ID: h-0c8966a6-f220-4f1e-a79e-a520fb313f9e
:END:
#+BEGIN_SRC clojure
;; The default output is a string body
(client/get "http://example.com/foo.txt")
;; Coerce as a byte-array
(client/get "http://example.com/favicon.ico" {:as :byte-array})
;; Coerce as something other than UTF-8 string
(client/get "http://example.com/string.txt" {:as "UTF-16"})
;; Coerce as json
(client/get "http://example.com/foo.json" {:as :json})
(client/get "http://example.com/foo.json" {:as :json-string-keys})
;; Coerce as Transit encoded JSON or MessagePack
(client/get "http://example.com/foo" {:as :transit+json})
(client/get "http://example.com/foo" {:as :transit+msgpack})
;; Coerce as a clojure datastructure
(client/get "http://example.com/foo.clj" {:as :clojure})
;; Coerce as x-www-form-urlencoded
(client/post "http://example.com/foo" {:as :x-www-form-urlencoded})
;; Try to automatically coerce the output based on the content-type
;; header (this is currently a BETA feature!). Currently supports
;; text, json and clojure (with automatic charset detection)
;; clojure coercion requires "application/clojure" or
;; "application/edn" in the content-type header
(client/get "http://example.com/foo.json" {:as :auto})
;; Return the body as a stream
(client/get "http://example.com/bigrequest.html" {:as :stream})
;; Note that the connection to the server will NOT be closed until the
;; stream has been read
;; Return the body as a java.io.BufferedReader
(client/get "http://example.com/bigrequest.html" {:as :reader})
;; As above, the connection will remain open until the stream has been
;; read. The reader will attempt to respect the server-specified charset,
;; if any, defaulting to UTF-8.
#+END_SRC
Output coercion with =:as :json=, =:as :json-string-keys= or =:as :x-www-form-urlencoded=, will only work with an optional dependency, see [[#optional-dependencies][Optional Dependencies]].
By default, JSON coercion is only applied when the response's status
is considered "unexceptional". If the =:unexceptional-status= option
is provided, then its value is a function which specifies what status
codes are unexceptional. =:unexceptional-status= defaults to
=clj-http.client/unexceptional-status?=.
If you would like to change under what conditions coercion is applied,
you can send the =:coerce= option, which can be set to:
#+BEGIN_SRC clojure
:always ;; always json decode the body
:unexceptional ;; json decode when an HTTP response is considered unexceptional
:exceptional ;; json decode when an HTTP response is considered exceptional
#+END_SRC
The =:coerce= setting defaults to =:unexceptional=.
** Headers
:PROPERTIES:
:CUSTOM_ID: h-ef64574f-f9dc-4356-95b7-d55cc6737b44
:END:
clj-http's treatment of headers is a little more permissive than the [[https://github.com/ring-clojure/ring/blob/master/SPEC][ring spec]]
specifies.
Rather than forcing all request headers to be lowercase strings,
clj-http allows strings or keywords of any case. Keywords will be
transformed into their canonical representation, so the :content-md5
header will be sent to the server as "Content-MD5", for instance.
String keys in request headers, however, will be sent to the server
with their casing unchanged.
Response headers can be read as keywords or strings of any case. If
the server responds with a "Date" header, you could access the value
of that header as :date, "date", "Date", etc.
If for some reason you require access to the original header name that
the server specified, it is available by invoking (keys ...) on the
header map.
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 four 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
- Comma separated style
Here is an example of the input and output for the ~:query-params~ 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"
;; with :multi-param-style :comma-separated, a param with comma-separated values
:a [1 2 3] => "a=1,2,3"
#+END_SRC
** Meta Tag Headers
:PROPERTIES:
:CUSTOM_ID: h-01663a63-8bc8-45da-8a3d-341402f3f3fa
:END:
HTML 4.01 allows using the tag ~~ and HTML 5 allows
using the tag ~~ to specify a header that should be
treated as an HTTP response header. By default, clj-http will ignore the body of
the response (other than the regular output coercion), but if you need clj-http
to parse the headers out of the body, you can use the =:decode-body-headers=
option:
#+BEGIN_SRC clojure
;; without decoding body headers (defaults to off):
(:headers (client/get "http://www.yomiuri.co.jp/"))
=> {"server" "Apache",
"content-encoding" "gzip",
"content-type" "text/html",
"date" "Tue, 09 Oct 2012 18:02:41 GMT",
"cache-control" "max-age=0, no-cache",
"expires" "Tue, 09 Oct 2012 18:02:41 GMT",
"etag" "\"1dfb-2686-4cba2686fb8b1\"",
"pragma" "no-cache",
"connection" "close"}
;; with decoding body headers, notice the content-type,
;; content-style-type and content-script-type headers:
(:headers (client/get "http://www.yomiuri.co.jp/" {:decode-body-headers true}))
=> {"server" "Apache",
"content-encoding" "gzip",
"content-script-type" "text/javascript",
"content-style-type" "text/css",
"content-type" "text/html; charset=Shift_JIS",
"date" "Tue, 09 Oct 2012 18:02:59 GMT",
"cache-control" "max-age=0, no-cache",
"expires" "Tue, 09 Oct 2012 18:02:59 GMT",
"etag" "\"1dfb-2686-4cba2686fb8b1\"",
"pragma" "no-cache",
"connection" "close"}
#+END_SRC
This can be used to have clj-http correctly interpret the body's charset by
using:
#+BEGIN_SRC clojure
(client/get "http://www.yomiuri.co.jp/" {:decode-body-headers true :as :auto})
=> ;; correctly formatted :body (Shift_JIS charset instead of UTF-8)
#+END_SRC
Note that this feature is currently beta and uses [[https://github.com/weavejester/crouton][Crouton]] to parse the body of
the request. If you do not want to use this feature, you can include Crouton in
addition to clj-http as a dependency like so:
#+BEGIN_SRC clojure
(defproject foo "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.3.0"]
[clj-http "0.6.0"]
[crouton "1.0.0"]])
#+END_SRC
Note also that HEAD requests will not return a body, in which case this setting will have no effect.
clj-http will automatically disable the =:decode-body-headers= option.
** Link Headers
:PROPERTIES:
:CUSTOM_ID: h-f7464c54-4928-474f-9132-08e6b6f3c19d
:END:
clj-http parses any [[http://tools.ietf.org/html/rfc5988][link headers]] returned in the response, and adds them to the
=:links= key on the response map. This is particularly useful for paging RESTful
APIs:
#+BEGIN_SRC clojure
(:links (client/get "https://api.github.com/gists"))
=> {:next {:href "https://api.github.com/gists?page=2"}
:last {:href "https://api.github.com/gists?page=22884"}}
#+END_SRC
** Redirects
:PROPERTIES:
:CUSTOM_ID: h-71c966ae-f764-4bd7-801c-0f3c8413c502
:END:
clj-http conforms its behaviour regarding automatic redirects to the [[https://tools.ietf.org/html/rfc2616#section-10.3][RFC]].
It means that redirects on status =301=, =302=, =307= and =308= are not redirected on
methods other than =GET= and =HEAD=. If you want a behaviour closer to what most
browser have, you can set =:redirect-strategy= to =:lax= in your request to have
automatic redirection work on all methods by transforming the method of the
request to =GET=.
Redirect Options:
- =:trace-redirects= :: If true, clj-http will enhance the response object with a
list of redirected URLs with key: =:trace-redirects=.
- =:redirect-strategy= :: Sets the redirect strategy for clj-http. Accepts the following:
- =:none= - Perform no redirects
- =:default= - See https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/DefaultRedirectStrategy.html
- =:lax= - See https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/LaxRedirectStrategy.html
- =:graceful= - Similar to =:default=, but does not throw exceptions when max redirects is reached. This is the redirects behaviour in 2.x
- =nil= - When nil, assumes =:default=
You may also pass in an instance of RedirectStrategy (in the =:redirect-strategy= key) if you want a
behavior that's not implemented.
Additionally, clj-http will attempt to validate that a redirect host is not invalid, you can disable
this by setting =:validate-redirects false= in the request (the default is true)
NOTE: The options =:force-redirects= and =:follow-redirects= (present in clj-http 2.x are no longer
used). You can use =:graceful= to mostly emulate the old redirect behaviour.
*** How to create a custom RedirectStrategy
:PROPERTIES:
:CUSTOM_ID: h:a3b8b124-411f-4c4c-ac4b-777624e76bf1
:END:
As mentioned earlier, it's possible to pass a custom instance of RedirectStrategy. The snippet below shows how to create a custom =RedirectStrategy= by wrapping the default strategy.
#+begin_src clojure
(def default-strategy org.apache.http.impl.client.DefaultRedirectStrategy/INSTANCE)
(def logging-redirect-strategy
(reify org.apache.http.client.RedirectStrategy
(getRedirect [this request response context]
(println "attempting redirect...")
(.getRedirect default-strategy request response context))
(isRedirected [this request response context]
(println "checking isRedirected")
(.isRedirected default-strategy request response context))))
(client/get "https://httpbin.org/absolute-redirect/3" {:redirect-strategy logging-redirect-strategy})
;; this will output the following:
;;
;; checking isRedirected
;; attempting redirect...
;; checking isRedirected
;; attempting redirect...
;; checking isRedirected
;; attempting redirect...
;; checking isRedirected
#+end_src
** Cookies
:PROPERTIES:
:CUSTOM_ID: h-3bb89b16-4be3-455e-98ec-c5ca5830ddb9
:END:
*** Cookiestores
:PROPERTIES:
:CUSTOM_ID: h-1d86fe30-f690-4c2a-9a1c-231669f4591a
:END:
clj-http can simplify the maintenance of cookies across requests if it is
provided with a _cookie store_.
#+BEGIN_SRC clojure
(binding [clj-http.core/*cookie-store* (clj-http.cookies/cookie-store)]
(client/post "http://example.com/login" {:form-params {:username "..."
:password "..."}})
(client/get "http://example.com/secured-page")
...)
#+END_SRC
(The =clj-http.cookies/cookie-store= function returns a new empty instance of a
default implementation of =org.apache.http.client.CookieStore=.)
This will allow cookies to only be _written_ to the cookie store. Cookies from
the cookie-store will not automatically be sent with future requests.
If you would like cookies from the cookie-store to automatically be sent with
each request, specify the cookie-store with the =:cookie-store= option:
#+BEGIN_SRC clojure
(let [my-cs (clj-http.cookies/cookie-store)]
(client/post "http://example.com/login" {:form-params {:username "..."
:password "..."}
:cookie-store my-cs})
(client/post "http://example.com/update" {:body my-data
:cookie-store my-cs}))
#+END_SRC
You can also use the =get-cookies= function to retrieve the cookies
from a cookie store:
#+BEGIN_SRC clojure
(def cs (clj-http.cookies/cookie-store))
(client/get "http://google.com" {:cookie-store cs})
(clojure.pprint/pprint (clj-http.cookies/get-cookies cs))
{"NID"
{:domain ".google.com",
:expires #,
:path "/",
:value
"58=c387....",
:version 0},
"PREF"
{:domain ".google.com",
:expires #,
:path "/",
:value
"ID=3ba...:FF=0:TM=133...:LM=133...:S=_iRM...",
:version 0}}
#+END_SRC
*** Keystores, Trust-stores
:PROPERTIES:
:CUSTOM_ID: h-7968467a-1441-4a73-9307-9a7a5fd8e733
:END:
You can also specify your own keystore/trust-store to be used:
#+BEGIN_SRC clojure
(client/get "https://example.com" {:keystore "/path/to/keystore.ks"
:keystore-type "jks" ; default: jks
:keystore-pass "secretpass"
:trust-store "/path/to/trust-store.ks"
:trust-store-type "jks" ; default jks
:trust-store-pass "trustpass"})
#+END_SRC
The =:keystore/:trust-store= values may be either paths to keystore
files or =KeyStore= instances.
** Exceptions
:PROPERTIES:
:CUSTOM_ID: h-ed9e04f1-1c7b-4c2e-9259-94d2a3e65a89
:END:
The client will throw exceptions on, well, exceptional status codes, meaning all
HTTP responses other than =#{200 201 202 203 204 205 206 207 300 301 302 303 304
307}=. clj-http will throw a [[http://github.com/scgilardi/slingshot][Slingshot]] Stone that can be caught by a regular
=(catch Exception e ...)= or in Slingshot's =try+= block:
#+BEGIN_SRC clojure
(client/get "http://example.com/broken")
=> ExceptionInfo clj-http: status 404 clj-http.client/wrap-exceptions/fn--583 (client.clj:41)
;; Or, if you would like the Exception message to contain the entire response:
(client/get "http://example.com/broken" {:throw-entire-message? true})
=> ExceptionInfo clj-http: status 404 {:status 404,
:headers {"server" "nginx/1.0.4",
"x-runtime" "12ms",
"content-encoding" "gzip",
"content-type" "text/html; charset=utf-8",
"date" "Mon, 17 Oct 2011 23:15 :36 GMT",
"cache-control" "no-cache",
"status" "404 Not Found",
"transfer-encoding" "chunked",
"connection" "close"},
:body "...body here..."}
clj-http.client/wrap-exceptions/fn--584 (client.clj:42
;; You can also ignore HTTP-status-code exceptions and handle them yourself:
(client/get "http://example.com/broken" {:throw-exceptions false})
;; Or ignore an unknown host (methods return 'nil' if this is set to
;; true and the host does not exist:
(client/get "http://example.invalid" {:ignore-unknown-host? true})
;; Or customize the http statuses that will not throw:
(client/get "http://example.com/broken" {:unexceptional-status #(<= 200 % 299)})
#+END_SRC
(spacing added by me to be human readable)
How to use with Slingshot:
#+BEGIN_SRC clojure
; Response map is thrown as exception obj.
; We filter out by status codes
(try+
(client/get "http://example.com/broken")
(catch [:status 403] {:keys [request-time headers body]}
(log/warn "403" request-time headers))
(catch [:status 404] {:keys [request-time headers body]}
(log/warn "NOT Found 404" request-time headers body))
(catch Object _
(log/error (:throwable &throw-context) "unexpected error")
(throw+)))
#+END_SRC
** Decompression
:PROPERTIES:
:CUSTOM_ID: h-f780c96c-90be-4d83-9b53-227a9e5942ab
:END:
By default, clj-http will add the ={"Accept-Encoding" "gzip, deflate"}= header
to requests, and automatically decompress the resulting gzip or deflate stream
if the =Content-Encoding= header is found on the response. If this is undesired,
the ={:decompress-body false}= option can be specified:
#+BEGIN_SRC clojure
;; Auto-decompression used: (google requires a user-agent to send gzip data)
(def h {"User-Agent" "Mozilla/5.0 (Windows NT 6.1;) Gecko/20100101 Firefox/13.0.1"})
(def resp (client/get "http://google.com" {:headers h}))
(:orig-content-encoding resp)
=> "gzip" ;; <= google sent response gzipped
;; and without decompression:
(def resp2 (client/get "http://google.com" {:headers h :decompress-body false})
(:orig-content-encoding resp2)
=> nil
#+END_SRC
If clj-http decompresses something, the "content-encoding" header is removed
from the headers (because the encoding is no longer true). This allows clj-http
to be used as a pass-through proxy with ring. The original content-encoding is
available as =:orig-content-encoding= in the response map if auto-decompression
is enabled.
** Debugging
:PROPERTIES:
:CUSTOM_ID: debugging
:END:
There are four debugging methods you can use:
#+BEGIN_SRC clojure
;; print request info to *out*:
(client/get "http://example.org" {:debug true})
;; print request info to *out*, including request body:
(client/post "http://example.org" {:debug true :debug-body true :body "..."})
;; save the request that was sent in a :request key in the response:
(client/get "http://example.org" {:save-request? true})
;; save the request that was sent in a :request key in the response,
;; including the body content:
(client/get "http://example.org" {:save-request? true :debug-body true})
;; add an HttpResponseInterceptor to the request. This callback
;; is called for each redirects with the following args:
;; ^HttpResponse resp, HttpContext^ ctx
;; this allows low level debugging + access to socket.
;; see http://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/HttpResponseInterceptor.html
(client/get "http://example.org" {:response-interceptor (fn [resp ctx] (println ctx))})
#+END_SRC
*** Logging
:PROPERTIES:
:CUSTOM_ID: h-0d505652-d453-48a2-a868-46aef2b8af66
:END:
Finally, if you want to access the logging that the Apache client does internally, you can set up
your dependencies to add the [[https://logging.apache.org/log4j/2.x/][log4j2]] libraries and configure the logging for clj-http. In order to do
this, you'll need to add
#+BEGIN_SRC clojure
[org.apache.logging.log4j/log4j-api "2.11.0"]
[org.apache.logging.log4j/log4j-core "2.11.0"]
[org.apache.logging.log4j/log4j-1.2-api "2.11.0"]
#+END_SRC
To your =project.clj= and have a usable log4j2.properties. I have provided one in
=resources/log4j2.properties=. Make sure to set:
#+BEGIN_SRC fundamental
rootLogger.level = debug
#+END_SRC
If you want to see debug information (or "trace" for trace logging). When you perform a request you
should see something akin to this in the logs:
#+BEGIN_SRC fundamental
[2018-03-20T20:36:34,635][DEBUG][o.a.h.c.p.RequestAddCookies] CookieSpec selected: default
[2018-03-20T20:36:34,635][DEBUG][o.a.h.c.p.RequestAuthCache] Auth cache not set in the context
[2018-03-20T20:36:34,635][DEBUG][o.a.h.i.c.BasicHttpClientConnectionManager] Get connection for route {s}->https://example.com:443
[2018-03-20T20:36:34,636][DEBUG][o.a.h.i.c.DefaultManagedHttpClientConnection] http-outgoing-1: set socket timeout to 0
[2018-03-20T20:36:34,636][DEBUG][o.a.h.i.e.MainClientExec ] Opening connection {s}->https://example.com:443
[2018-03-20T20:36:34,644][DEBUG][o.a.h.i.c.DefaultHttpClientConnectionOperator] Connecting to example.com/10.0.0.1:443
[2018-03-20T20:36:34,644][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] Connecting socket to example.com/10.0.0.1:443 with timeout 0
[2018-03-20T20:36:34,692][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] Enabled protocols: [TLSv1, TLSv1.1, TLSv1.2]
[2018-03-20T20:36:34,693][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, ... etc ...]
[2018-03-20T20:36:34,693][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] Starting handshake
[2018-03-20T20:36:34,841][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] Secure session established
[2018-03-20T20:36:34,842][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] negotiated protocol: TLSv1.2
[2018-03-20T20:36:34,842][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] negotiated cipher suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
[2018-03-20T20:36:34,843][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] peer principal: CN=example.com
[2018-03-20T20:36:34,843][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] peer alternative names: [example.com, www.example.com]
[2018-03-20T20:36:34,843][DEBUG][o.a.h.c.s.SSLConnectionSocketFactory] issuer principal: CN=Let's Encrypt Authority X3, O=Let's Encrypt, C=US
[2018-03-20T20:36:34,844][DEBUG][o.a.h.i.c.DefaultHttpClientConnectionOperator] Connection established 192.168.0.29:36792<->10.0.0.1:443
[2018-03-20T20:36:34,844][DEBUG][o.a.h.i.e.MainClientExec ] Executing request POST /post HTTP/1.1
[2018-03-20T20:36:34,844][DEBUG][o.a.h.i.e.MainClientExec ] Target auth state: UNCHALLENGED
[2018-03-20T20:36:34,844][DEBUG][o.a.h.i.e.MainClientExec ] Proxy auth state: UNCHALLENGED
[2018-03-20T20:36:34,845][DEBUG][o.a.h.headers ] http-outgoing-1 >> POST /post HTTP/1.1
[2018-03-20T20:36:34,845][DEBUG][o.a.h.headers ] http-outgoing-1 >> Connection: close
[2018-03-20T20:36:34,845][DEBUG][o.a.h.headers ] http-outgoing-1 >> accept-encoding: gzip, deflate
[2018-03-20T20:36:34,845][DEBUG][o.a.h.headers ] http-outgoing-1 >> Content-Length: 14
[2018-03-20T20:36:34,845][DEBUG][o.a.h.headers ] http-outgoing-1 >> Content-Type: text/plain; charset=UTF-8
[2018-03-20T20:36:34,846][DEBUG][o.a.h.headers ] http-outgoing-1 >> Host: example.com
[2018-03-20T20:36:34,846][DEBUG][o.a.h.headers ] http-outgoing-1 >> User-Agent: Apache-HttpClient/4.5.5 (Java/9.0.1)
[2018-03-20T20:36:34,846][DEBUG][o.a.h.wire ] http-outgoing-1 >> "POST /post HTTP/1.1[\r][\n]"
[2018-03-20T20:36:34,846][DEBUG][o.a.h.wire ] http-outgoing-1 >> "Connection: close[\r][\n]"
[2018-03-20T20:36:34,846][DEBUG][o.a.h.wire ] http-outgoing-1 >> "accept-encoding: gzip, deflate[\r][\n]"
[2018-03-20T20:36:34,847][DEBUG][o.a.h.wire ] http-outgoing-1 >> "Content-Length: 14[\r][\n]"
[2018-03-20T20:36:34,847][DEBUG][o.a.h.wire ] http-outgoing-1 >> "Content-Type: text/plain; charset=UTF-8[\r][\n]"
[2018-03-20T20:36:34,847][DEBUG][o.a.h.wire ] http-outgoing-1 >> "Host: example.com[\r][\n]"
etc etc it will go on forever and be very verbose
#+END_SRC
This provides both the data sent and received on the wire for debugging purposes.
I've also provided an example for changing the log level from clojure in
=examples/logging-apache-requests.clj=.
* Caching
:PROPERTIES:
:CUSTOM_ID: h-2c4ee611-ca22-432e-9c33-18040566661e
:END:
clj-http supports Apache's caching client, essentially it "provides an HTTP/1.1-compliant caching
layer to be used with HttpClient--the Java equivalent of a browser cache." (see [[https://hc.apache.org/httpcomponents-client-ga/tutorial/html/caching.html][the explanation in
the apache docs]]). In order to use the cache, a reusable connection manager *and* http-client must be
used.
An example of basic usage with the default options:
#+BEGIN_SRC clojure
(let [cm (conn/make-reusable-conn-manager {})
client (:http-client (http/get "http://example.com"
{:connection-manager cm :cache true}))]
(http/get "http://example.com"
{:connection-manager cm :http-client client :cache true})
(http/get "http://example.com"
{:connection-manager cm :http-client client :cache true})
(http/get "http://example.com"
{:connection-manager cm :http-client client :cache true}))
#+END_SRC
You can build your own cache config by providing either a map of caching configuration options, or
by providing a =CacheConfig= object, as seen below:
#+BEGIN_SRC clojure
(let [cm (conn/make-reusable-conn-manager {})
cache-config (core/build-cache-config
{:cache-config {:max-object-size 4096}})
client (:http-client (http/get "http://example.com"
{:connection-manager cm :cache true}))]
(http/get "http://example.com"
;; Use the default cache config settings
{:connection-manager cm :http-client client :cache true})
(http/get "http://example.com"
{:connection-manager cm :http-client client :cache true
;; Provide cache configuration options as a map
:cache-config {:max-object-size 9152
:max-cache-entries 100}})
(http/get "http://example.com"
{:connection-manager cm :http-client client :cache true
;; Provide the cache configuration as a CacheConfig object
:cache-config cache-config}))
#+END_SRC
In the response, clj-http provides the =:cached= key to indicate whether the response was cached,
missed, etc:
- nil :: Caching was not used for this request
- =:CACHE_HIT= :: A response was generated from the cache with no requests sent upstream.
- =:CACHE_MISS= :: The response came from an upstream server.
- =:CACHE_MODULE_RESPONSE= :: The response was generated directly by the caching module.
- =:VALIDATED= :: The response was generated from the cache after validating the entry with the origin server.
* Authentication
:PROPERTIES:
:CUSTOM_ID: h-87f38469-36b4-44c6-ae74-0d8f5e80c2ed
:END:
** Basic Auth
:PROPERTIES:
:CUSTOM_ID: h-d3ea348f-88ed-4193-bb16-d8d5accdc2aa
:END:
#+BEGIN_SRC clojure
(client/get "http://example.com/protected" {:basic-auth ["user" "pass"]})
(client/get "http://example.com/protected" {:basic-auth "user:pass"})
#+END_SRC
** Digest Auth
:PROPERTIES:
:CUSTOM_ID: h-d1904589-e71e-43db-8b93-0f94ccecaabe
:END:
#+BEGIN_SRC clojure
(client/get "http://example.com/protected" {:digest-auth ["user" "pass"]})
#+END_SRC
** NTLM Auth
:PROPERTIES:
:CUSTOM_ID: h-AE80FFDC-2016-4883-9512-2BE16640339D
:END:
#+BEGIN_SRC clojure
(client/get "http://example.com/protected" {:ntlm-auth ["user" "pass" "host" "domain"]})
#+END_SRC
** oAuth2
:PROPERTIES:
:CUSTOM_ID: h-dd077440-a1de-437e-b34e-5d6d0d1da4bd
:END:
#+BEGIN_SRC clojure
(client/get "http://example.com/protected" {:oauth-token "secret-token"})
#+END_SRC
* Advanced Usage
:PROPERTIES:
:CUSTOM_ID: h-d52ca837-a575-402f-81fe-53241d85f2db
:END:
** Raw Request
:PROPERTIES:
:CUSTOM_ID: h-0d2eadbf-c1ad-4514-a932-9d173582a790
:END:
A more general =request= function is also available, which is useful as a
primitive for building higher-level interfaces:
#+BEGIN_SRC clojure
(defn api-action [method path & [opts]]
(client/request
(merge {:method method :url (str "http://example.com/" path)} opts)))
#+END_SRC
*** Boolean options
:PROPERTIES:
:CUSTOM_ID: h-a37c718c-43bb-43ce-936a-21ef65147295
:END:
Since 0.9.0, all boolean options can be expressed as either ={:debug true}= or
={:debug? true}=, with or without the question mark.
** Persistent Connections
:PROPERTIES:
:CUSTOM_ID: h-4e9f116d-c293-4a0c-8e11-435c440bfe97
:END:
clj-http can use persistent connections to speed up connections if multiple
connections are being used:
#+BEGIN_SRC clojure
(with-connection-pool {:timeout 5 :threads 4 :insecure? false :default-per-route 10}
(get "http://example.org/1")
(post "http://example.org/2")
(get "http://example.org/3")
...
(get "http://example.org/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://example.org/1" {:async? true} resp1 exce1)
(post "http://example.org/2" {:async? true} resp2 exce2)
(get "http://example.org/3" {:async? true} resp3 exce3)
...
(get "http://example.org/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://example.org/1" {:async? true} resp1 exce1)
(post "http://example.org/2"
{:async? true}
(fn [resp] (get "http://example.org/3"
(reuse-pool {:async? true} resp)
resp3 exce3))
exce2))
#+END_SRC
There are many advanced options available when creating asynchronous connection pools that can be
configured by passing an =:io-config= map in the connection manager parameters. It supports:
- =:connect-timeout=
- =:interest-op-queued=
- =:io-thread-count=
- =:rcv-buf-size=
- =:select-interval=
- =:shutdown-grace-period=
- =:snd-buf-size=
- =:so-keep-alive=
- =:so-linger=
- =:so-timeout=
- =:tcp-no-delay=
See the docstring on =with-async-connection-pool= for more information about these options.
If you would prefer to handle managing the connection manager yourself, you can
create a connection manager and specify it for each request:
#+BEGIN_SRC clojure
(def cm (clj-http.conn-mgr/make-reusable-conn-manager {:timeout 2 :threads 3}))
(def cm2 (clj-http.conn-mgr/make-reusable-conn-manager {:timeout 10 :threads 1}))
(get "http://example.org/1" {:connection-manager cm2})
(post "http://example.org/2" {:connection-manager cm})
(get "http://example.org/3" {:connection-manager cm2})
;; Don't forget to shut it down when you're done!
(clj-http.conn-mgr/shutdown-manager cm)
(clj-http.conn-mgr/shutdown-manager cm2)
#+END_SRC
See the docstring on =make-reusable-conn-manager= for options and default
values.
In the current version, pooled async request CANNOT specify connection manager.
** Re-using =HttpClient= between requests
:PROPERTIES:
:CUSTOM_ID: h-b79b07fb-d024-49a2-a7f7-53863d1b8d6d
:END:
In some cases, you may want to re-use the same =HttpClient= object between requests, either so you
don't have to build it every time, or because you make some configuration change to the request.
clj-http will return the built HTTP client in =:http-client= which you can then specify in
subsequent requests (with =:http-client=). Note that in order to reuse the client a connection
manager must be used.
#+BEGIN_SRC clojure
;; Re-use the HttpClient clj-http builds for you:
(let [cm (conn/make-reusable-conn-manager {})
resp (client/get "http://example.com" {:connection-manager cm})
hclient (:http-client resp)]
(client/get "http://example.com/1"
{:connection-manager cm :http-client hclient})
(client/get "http://example.com/2"
{:connection-manager cm :http-client hclient})
(client/get "http://example.com/3"
{:connection-manager cm :http-client hclient}))
;; You can also build your own, using clj-http's helper or manually building it:
(let [cm (conn/make-reusable-conn-manager {})
hclient (core/build-http-client {} false cm)]
(client/get "http://example.com/1"
{:connection-manager cm :http-client hclient})
(client/get "http://example.com/2"
{:connection-manager cm :http-client hclient})
(client/get "http://example.com/3"
{:connection-manager cm :http-client hclient}))
;; Async http clients may also be created and re-used:
(let [acm (conn/make-reuseable-async-conn-manager {})
ahclient (core/build-async-http-client {} acm)]
(client/get "http://example.com/1"
{:connection-manager cm :http-client ahclient}
handle-response handle-failure)
(client/get "http://example.com/2"
{:connection-manager cm :http-client ahclient}
handle-response handle-failure)
(client/get "http://example.com/3"
{:connection-manager cm :http-client ahclient}
handle-response handle-failure))
#+END_SRC
** Proxies
:PROPERTIES:
:CUSTOM_ID: h-49f9ca81-0bad-4cd8-87ac-c09a85fa5500
:END:
A proxy can be specified by setting the Java properties: =.proxyHost=
and =.proxyPort= where == is the client scheme used (normally
'http' or 'https'). =http.nonProxyHosts= allows you to specify a pattern for
hostnames which do not require proxy routing - this is shared for all schemes.
Additionally, per-request proxies can be specified with the =proxy-host= and
=proxy-port= options (this overrides =http.nonProxyHosts= too):
#+BEGIN_SRC clojure
(client/get "http://example.com" {:proxy-host "127.0.0.1" :proxy-port 8118})
#+END_SRC
You can also specify the =proxy-ignore-hosts= parameter with a list of
hosts where the proxy should be ignored. By default this list is
=#{"localhost" "127.0.0.1"}=.
A SOCKS proxy can be used by creating a proxied connection manager with
=clj-http.conn-mgr/make-socks-proxied-conn-manager=. Then using that connection
manager in the request.
For example if you wanted to connect to a local socks proxy on port =8081= you
would:
#+BEGIN_SRC clojure
(ns foo.bar
(:require [clj-http.client :as client]
[clj-http.conn-mgr :as conn-mgr]))
(client/get "https://google.com"
{:connection-manager
(conn-mgr/make-socks-proxied-conn-manager "localhost" 8081)})
#+END_SRC
If your SOCKS connection requires a keystore / trust-store, you can specify that too:
#+BEGIN_SRC clojure
(ns foo.bar
(:require [clj-http.client :as client]
[clj-http.conn-mgr :as conn-mgr]))
(client/get "https://google.com"
{:connection-manager
(conn-mgr/make-socks-proxied-conn-manager "localhost" 8081
{:keystore "/path/to/keystore.ks"
:keystore-type "jks" ; default: jks
:keystore-pass "secretpass"
:trust-store "/path/to/trust-store.ks"
:trust-store-type "jks" ; default jks
:trust-store-pass "trustpass"})})
#+END_SRC
You can also store the proxied connection manager and reuse it later.
** Custom Middleware
:PROPERTIES:
:CUSTOM_ID: h-c51cba6c-5c1b-4941-93c3-f769bb533562
:END:
Sometime it is desirable to run a request with some middleware enabled and some
left out, the =with-middleware= method provides this functionality:
#+BEGIN_SRC clojure
(with-middleware [#'clj-http.client/wrap-method
#'clj-http.client/wrap-url
#'clj-http.client/wrap-exceptions]
(get "http://example.com")
(post "http://example.com/foo" {:body (.getBytes "foo")}))
#+END_SRC
To see available middleware, check the =clj-http.client/default-middleware= var,
which is a vector of the default middleware that clj-http uses.
=clj-http.client/*current-middleware*= is bound to the current list of
middleware during request time.
** Modifying Apache-specific features of the =HttpClientBuilder= and =HttpAsyncClientBuilder=
:PROPERTIES:
:CUSTOM_ID: h:844f078c-531e-445e-b7ce-76092bcc9928
:END:
While clj-http tries to provide the features needed, there are times when it does not provide access
to a parameter that you need. In these cases, you can use a couple of advanced parameters to provide
arbitrary configuration functions to be run on the =HttpClientBuilder= by specifying
=:http-builder-fns= and =:async-http-builder-fns=.
Each of these variables is a sequence of functions of two arguments, the http builder
(=HttpClientBuilder= for =:http-builder-fns= and =HttpAsyncClientBuilder= for
=:async-http-builder-fns=) and the request map.
#+BEGIN_SRC clojure
;; A function that takes a builder and disables Apache's cookie management
(defun my-cookie-disabler [^HttpClientBuilder builder
request]
(when (:disable-cookies request)
(.disableCookieManagement builder)))
;; The functions to modify the builder are passed in
(http/post "http://www.example.org" {:http-builder-fns [my-cookie-disabler]
:disable-cookies true})
#+END_SRC
The functions are run in the order they are passed in (inside a =doseq=).
By specifying =:http-client-builder=, your own instance of
=HttpClientBuilder= will be used. A supplied =HttpClientBuilder= which
sets the connection manager, redirect strategy, retry handler, route
planner, cache, or cookie spec registry may find these overridden by
clj-http's =:connection-manager=, =:redirect-strategy=,
=:retry-handler=, =:cache=, or =:cookie-policy-registry= or
=:cookie-spec=, respectively.
** Incrementally JSON Parsing
:PROPERTIES:
:CUSTOM_ID: h:b01c16e8-7179-468e-8890-316939ec0e38
:END:
[[https://github.com/dakrone/cheshire][cheshire]] supports incrementally parsing JSON using lazy sequences. This approach can useful for
processing large top-level JSON arrays because it doesn't require upfront work consuming the entire stream.
#+begin_src clojure
(require '[cheshire.core :as json])
(defn print-all-pokemon-names [pokemons]
(for [pokemon pokemons]
(println (get-in pokemon [:name :english]))))
(let [url "https://raw.githubusercontent.com/fanzeyi/pokemon.json/master/pokedex.json"
response (get url {:as :reader})]
(with-open [reader (:body response)] ; closes the underlying connection when we're done
(let [pokemons (json/parse-stream reader true)]
; You must perform all reads from the stream inside `with-open`,
; any , any lazy
(doall (print-all-pokemon-names pokemons)))))
#+end_src
Keep in mind that the =reader= object wraps a HTTP connection. The user needs to be aware of two
things:
1. The user should close the reader after processing the stream, otherwise the underlying HTTP
Connection may leak and create subtle bugs. Clojure's [[https://clojuredocs.org/clojure.core/with-open][with-open]] is useful here.
2. You should realize any lazy sequences before closing the connection. Use [[https://clojuredocs.org/clojure.core/doall][doall]] or [[https://clojure.org/reference/transducers][transducers]] to
prevent bugs from lazy IO. See [[https://stuartsierra.com/2015/08/25/clojure-donts-lazy-effects][Clojure Don'ts: Lazy Effects]].
In previous versions of =clj-http= (<= 3.10.0), =clj-http= defaulted to lazily parsing JSON, but this
was slow and also confused users who didn't expect laziness.
** DNS Resolution
Users may add their own DNS resolver function to override the default DNS Resolver. This is useful in situations where you are unable to change the name to IP Address mapping. It is analogous to the =--resolve= flag present in =curl=. This example uses =org.apache.http.impl.conn.InMemoryDnsResolver= to resolve =example.com= to IP Address =127.0.0.1=.
#+BEGIN_SRC clojure
(client/get "https://example.com" {:dns-resolver (doto (InMemoryDnsResolver.)
(.add "example.com" (into-array[(InetAddress/getByAddress (byte-array [127 0 0 1]))])))})
#+END_SRC
This option is supported for all of the connection managers.
The =dns-resolver= can be any instance of =DnsResolver=. Here is an example of a custom implementation that attempts to look up the hostname in the supplied map and falls back to the default SystemDnsResolver if not found. Note how IPV6 addresses are specified.
#+BEGIN_SRC clojure
(defn custom-dns-resolver
[host-map]
(let [system-dns-resolver (org.apache.http.impl.conn.SystemDefaultDnsResolver.)]
(reify
org.apache.http.conn.DnsResolver
(^"[Ljava.net.InetAddress;" resolve [this ^String host]
(if-let [address (get host-map host)]
(into-array [(java.net.InetAddress/getByAddress host (byte-array address))])
(.resolve system-dns-resolver host))))))
(client/get "https://example.com" {:dns-resolver (custom-dns-resolver {"example.com" [127 0 0 1]
"www.google.com" [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]})})
#+END_SRC
* Development
:PROPERTIES:
:CUSTOM_ID: h-65bbf017-2e8b-4c43-824b-24b89cc27a70
:END:
Please send a pull request or open an issue if you have any problems. See =CONTRIBUTING.md= for more
information.
** Faking Responses
:PROPERTIES:
:CUSTOM_ID: h-c3d9c7e0-cc3f-47bf-91e3-b12567b08eb6
:END:
If you need to fake clj-http responses (for things like testing and such), check
out the [[https://github.com/myfreeweb/clj-http-fake][clj-http-fake]] library.
** Optional Dependencies
:PROPERTIES:
:CUSTOM_ID: h-f1fbdad3-cf40-41e0-8ae0-8716419be228
:END:
In 2.0.0+ clj-http's optional dependencies at excluded by default, in order to
use the features you will need to add them to your =project.clj= file.
clj-http currently has four optional dependencies, =cheshire=, =crouton=,
=tools.reader= and =ring/ring-codec=. Any number of them may be included by
adding them with the clj-http dependency in your project.clj:
#+BEGIN_SRC clojure
;; optional dependencies
[cheshire] ;; for :as :json
[crouton] ;; for :decode-body-headers
[org.clojure/tools.reader] ;; for :as :clojure
[ring/ring-codec] ;; for :as :x-www-form-urlencoded
#+END_SRC
Prior to 2.0.0, you can /exclude/ the dependencies and clj-http will work
without them.
** clj-http-lite
:PROPERTIES:
:CUSTOM_ID: h-ba6b263b-74a5-40f3-afc1-b0d785554c2b
:END:
Like clj-http but need something more lightweight without as many external
dependencies? Check out [[https://github.com/hiredman/clj-http-lite][clj-http-lite]] for a project that can be used as a
drop-in replacement for clj-http.
** Troubleshooting
:PROPERTIES:
:CUSTOM_ID: h-c543201e-a0e5-4e84-8eb2-6bf3e21a3140
:END:
*** VerifyError class org.codehaus.jackson.smile.SmileParser overrides final method getBinaryValue...
:PROPERTIES:
:CUSTOM_ID: h-c3a8ebc3-a247-4327-8b71-0097d1380873
:END:
This is actually caused by your project attempting to use [[https://github.com/mmcgrana/clj-json/][clj-json]] and [[https://github.com/dakrone/cheshire][cheshire]]
in the same classloader. You can fix the issue by either not using clj-json (and
thus choosing cheshire), or specifying an exclusion for clj-http in your project
like this:
#+BEGIN_SRC clojure
(defproject foo "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.3.0"]
[clj-http "0.3.4" :exclusions [cheshire]]])
#+END_SRC
Note that if you exclude cheshire, json decoding of response bodies
and json encoding of form-params cannot happen, you are responsible
for your own encoding/decoding.
As of clj-http 0.3.5, you should no longer see this, as Cheshire 3.1.0
and clj-json can now live together without causing problems.
*** NoHttpResponseException ... due to stale connections**
:PROPERTIES:
:CUSTOM_ID: h-9d7cf050-ed5b-4d23-8b02-97a9b9c94737
:END:
Persistent connections kept alive by the connection manager become stale: the
target server shuts down the connection on its end without HttpClient being able
to react to that event, while the connection is being idle, thus rendering the
connection half-closed or 'stale'.
This can be solved by using (with-connection-pool) as described in the
'Using Persistent Connection' section above.
* Tests
:PROPERTIES:
:CUSTOM_ID: h-a52feb3d-d966-4287-a07e-ad7aa7918fd5
:END:
To run the tests:
#+BEGIN_SRC
$ lein deps
$ lein test
Run all tests (including integration):
$ lein test :all
Run tests against all clojure versions
$ lein all test
$ lein all test :all
#+END_SRC
* Testimonials
:PROPERTIES:
:CUSTOM_ID: h-3044d1f7-6772-43c2-9ded-8c71c7f9ada2
:END:
With over [[https://clojars.org/clj-http][three million]] downloads, clj-http is a widely used, battle-tested clojure library. It is
also included in other libraries (like database clients) as a low-level http wrapper.
Libraries using clj-http:
- [[https://github.com/mattrepl/clj-oauth][clj-oauth]]
- [[https://github.com/clojurewerkz/elastisch][elasticsearch]]
- [[https://github.com/olauzon/capacitor][influxdb]]
Libraries inspired by clj-http:
- [[https://github.com/mpenet/jet][jet]]
- [[https://github.com/hiredman/clj-http-lite][clj-http-lite]]
* Other Libraries Providing Middleware
:PROPERTIES:
:CUSTOM_ID: other-middleware
:END:
- [[https://github.com/sharetribe/aws-sig4][aws-sig4]] :: a pure clojure implementation of AWS v4 signature request signing as middleware
(feel free to open a PR or issue if you'd like to add middleware here)
* License
:PROPERTIES:
:CUSTOM_ID: h-2de3db75-7a1b-42b8-ad3b-6ef27fc2a5ea
:END:
Released under the MIT License:
# Local Variables:
# fill-column: 100
# End:
clj-http-3.12.3/changelog.org 0000664 0000000 0000000 00000037244 14070752310 0015776 0 ustar 00root root 0000000 0000000 #+TITLE: clj-http changelog
#+AUTHOR: Lee Hinman
#+STARTUP: fold nodlcheck lognotestate hideall
#+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
#+PROPERTY: header-args :results code :exports both :noweb yes
#+HTML_HEAD:
#+LANGUAGE: en
* Changelog
List of user-visible changes that have gone into each release
** 3.12.4 (unreleased)
** 3.12.3
- Allow http-client re-use in async situation (#599)
https://github.com/dakrone/clj-http/pull/599
** 3.12.2
- Upgrade Dependencies (#598)
https://github.com/dakrone/clj-http/pull/598
** 3.12.1
- Bugfix for :normalize-uri (#584)
https://github.com/dakrone/clj-http/pull/584
** 3.12.0
- Create SSLContext consistently for all connection managers (#575)
https://github.com/dakrone/clj-http/pull/575
- Adds RequestConfig Option :normalize-uri (#583)
https://github.com/dakrone/clj-http/pull/583
** 3.11.0
- Adds workaround for Async Multipart uploads greater than 25 kb (#574)
https://github.com/dakrone/clj-http/pull/574
- Adds an additional style for multi-param-style added (#562)
https://github.com/dakrone/clj-http/pull/562
- Close transit input stream after reading response (#565)
https://github.com/dakrone/clj-http/pull/565
- Bump patch versions of apache httpcomponents to latest. (#569)
https://github.com/dakrone/clj-http/pull/569
- Fixed decode-json-body (#568)
https://github.com/dakrone/clj-http/pull/568
- Handle quoted parameter values in content type (#573)
https://github.com/dakrone/clj-http/pull/573
** 3.10.3
- Improve error message when using incompatible version of cheshire
https://github.com/dakrone/clj-http/pull/558
- Properly handle "308 Permanent Redirect" status code
https://github.com/dakrone/clj-http/pull/554
** 3.10.2
- Fix performance regressions from #528
https://github.com/dakrone/clj-http/pull/546
- Adds support for custom DNS Resolvers
https://github.com/dakrone/clj-http/pull/545
- Buffer :debug output to improve readability
https://github.com/dakrone/clj-http/pull/544
- Improve compatbility with GraalVM
https://github.com/dakrone/clj-http/pull/543
- Bug fix: Check first byte before wrapping response stream with gunzip
https://github.com/dakrone/clj-http/pull/549
** 3.10.1
- JSON parsing is always strict. See [[file:README.org::*Incrementally%20JSON%20Parsing][README#Incrementally JSON Parsing]]. This is
a *breaking change* and users *must* upgrade to cheshire >= 5.9.0.
https://github.com/dakrone/clj-http/pull/507
** 3.10.0
- Add trust-manager and key-managers support to the client
https://github.com/dakrone/clj-http/pull/469
- Improving consistency of connection option names
https://github.com/dakrone/clj-http/pull/483
https://github.com/dakrone/clj-http/issues/477
- Ensure Socket Timeout is set for BasicHttpClientConnectionManager
https://github.com/dakrone/clj-http/pull/463
- Reduce body allocation and copying
https://github.com/dakrone/clj-http/pull/475
** 3.9.1
- Fix body parsing when first byte value is 255
https://github.com/dakrone/clj-http/pull/449
- Add custom =:unexceptional-status= option
https://github.com/dakrone/clj-http/pull/451
** 3.9.0
- Add support for reusable http clients, returning the client in =:http-client= and allowing one to
be specified (with the same setting) - https://github.com/dakrone/clj-http/issues/441
- Cancelling the =Future= returned from an async http request now also aborts the HttpRequest object
- Async connection managers no longer put the connection manager in an illegal ACTIVE state [[https://github.com/dakrone/clj-http/issues/443][#443]]
- Added the =:cookie-spec= and =:cookie-policy-registry= options for specifying a custom cookie spec
for parsing cookies. Since clj-http doesn't rely on Apache's cookies handling, this is for
advanced users who wish to add their own cookie validation, or use Apache's handling instead of
clj-http's. It also allows a user who wants to registry a custom spec to reuse the spec without
creating it for every request. Semi-related to https://github.com/dakrone/clj-http/issues/444
- Added support for caching HTTP responses from a server. This can dramatically speed up requests to
the same URL. Filling and invalidating the cache is handled by Apache's httpclient-cache project,
with configuration exposed under the =:cache= and =:cache-config= parameters in the option map.
https://github.com/dakrone/clj-http/issues/445
** 3.8.0
- Reintroduce the =:save-request= and =:debug-body= options
- +Wrap nested querystring params before form params, fixing
https://github.com/dakrone/clj-http/issues/427+ Reverted, see further below
- Merged https://github.com/dakrone/clj-http/pull/426 to allow an empty SSLGenericSocketFactory
context
- Merged https://github.com/dakrone/clj-http/pull/424 to add :mime-subtype request parameter to
override mime subtype
- create-multipart-entity with three arguments arity lets the selection of =HttpMultipartMode=
- new request key :http-multipart-mode which is HttpMultipartMode/STRICT by default
- Added =:ignore-nested-query-string=, =:flatten-nested-form-params=, and =:flatten-nested-keys=
options for finer-grained control over which nested parts of the request are flattened. Fixes
https://github.com/dakrone/clj-http/issues/427
- Added =:http-builder-fns= and =:async-http-builder-fns= to support arbitrary customizations to the
=HttpClientBuilder= and =HttpAsyncClientBuilder=
- Fixed an issue where redirects to a bad location could cause the async client to hang -
https://github.com/dakrone/clj-http/pull/435
- =client/parse-url= now includes the original URL in the =:url= key
- =core/get-cookie-policy= is now a multimethod. This allows users to customize the return of their
own cookie validation method.
- Empty responses with coercion no longer throw exceptions when processing empty gzipped response
streams. Fixes https://github.com/dakrone/clj-http/issues/257
** 3.7.0
This list contains all the changes since 3.0.0.
Added:
- HttpRequestInterceptor support 155bd23
- protocol-version and reason-phrase f430517
- support for async HTTP requests (like Ring) 44d10ec
- support for different multi-param encoding (:repeating, :array, :indexed) cddeb3e
- Add unparse function aec7dd1
- Added :redirect-strategy :graceful
- Allow RequestConfig and HttpClientContext to be injected feb3c48
Removed:
- :save-request
Changed:
- re-written middleware using apache http client 4.5
- Fix retry-handler to be added in correct place a2c31f5
- POST Mutipart: Use charset "UTF-8" instead of "ASCII" as default charset to support internationalization 983508f
** 2.0.0
- merged https://github.com/dakrone/clj-http/pull/274 to update Potemkin so it
supports Clojure 1.7.0 correctly
- merged https://github.com/dakrone/clj-http/pull/264 to add support for
coercion of urlencoded data
- make ALL optional dependencies opt-in, rather than opt-out
** 1.1.2
- bumped dependencies for transit-clj and tools.reader
- merge https://github.com/dakrone/clj-http/pull/263 to only decode body headers
when the content-type is either missing or starts with "text"
** 1.1.1
- merge https://github.com/dakrone/clj-http/pull/262 to prevent
NullPointerException when decoding body headers with HEAD requests
- merge https://github.com/dakrone/clj-http/pull/261 to decode user info from
URL if provided
- merge https://github.com/dakrone/clj-http/pull/260 to upgrade tools.reader
for better cljs compatibility
- add =304= (not modified) to the list of unexceptional responses, see #259
** 1.1.0
- merged https://github.com/dakrone/clj-http/pull/255 to add support for Windows
NTLM authentication
- Add the `with-additional-middleware` macro
- Add the ability to specify form-param-encoding for encoding form parameters
- merged https://github.com/dakrone/clj-http/pull/248 to removed deprecated
cookie APIs from cookie.clj
- merged https://github.com/dakrone/clj-http/pull/245 to do some cleanups and
small import fixes
- merged https://github.com/dakrone/clj-http/pull/240 to implement
meta/with-meta for the header map
- merged https://github.com/dakrone/clj-http/pull/242 fixing a connection leak
when http-entity is null
- bumped all dependencies to latest versions
- merged https://github.com/dakrone/clj-http/pull/235 to fix wrap-nested-params
- merged https://github.com/dakrone/clj-http/pull/236 to clean up multipart
constructors and reflection
- merged https://github.com/dakrone/clj-http/pull/234 to allow scheme
customization in default connection
** 1.0.1
- merged https://github.com/dakrone/clj-http/pull/232 to fix =empty= on
header-map
- fix :json-strict-string-keys
- exclude clojure.core/update from client ns
- added =:decode-cookies= option to allow skipping cookie header decode (if the
server sends incorrectly formatted cookies for some reason)
** 1.0.0
- merged https://github.com/dakrone/clj-http/pull/215 to add transit support
- drop support for clojure 1.4.0, start testing 1.7.0
- merged https://github.com/dakrone/clj-http/pull/213 to allow passing in an
already existing keystore, not just a path
- merged https://github.com/dakrone/clj-http/pull/211 to detect charset encoding
for url-encode
** 0.9.2
- merged https://github.com/dakrone/clj-http/pull/206 to handle null passwords
for keystores
- merged https://github.com/dakrone/clj-http/pull/201 to make :auto content type
parsing dispatch pluggable
- Bump crouton and tools.reader dependencies
- Merged https://github.com/dakrone/clj-http/pull/199 to add support for form
parameters in the PATCH method
- Bump dependencies and fix tests for 1.6.0 compatibility
** 0.9.1
- automatically coerce header values to strings
- fix issue where :ignore-unknown-host wasn't using the =opt= function correctly
** 0.9.0
- Bumped httpcore to 4.3.2
- Merged https://github.com/dakrone/clj-http/pull/190 to support file multiparts
with content, mime-type and name
- Unify all boolean operators so {:debug true} and {:debug? true} are treated
the same
- Fix :trace-redirects being [nil] when :uri is used
- Merged https://github.com/dakrone/clj-http/pull/184 containing a bevy of
changes:
- initial header-map implementation, allowing headers to be used case
insensitively
- drop support for clojure 1.2 and 1.3
- add support for clojure 1.6
- change all :use statements to :require statements
- use better docstring support for defs
- remove sleep calls in tests
- make Jetty quieter while running tests
- newer type hinting syntax
** 0.7.9
- Make :decode-body-headers more reliable by using a byte array instead of
slurp.
- Merged https://github.com/dakrone/clj-http/pull/181 to fix some tests
- Merged https://github.com/dakrone/clj-http/pull/178 to eliminate test
reflection
- Merged https://github.com/dakrone/clj-http/pull/177 to update apache HTTP deps
- Merged https://github.com/dakrone/clj-http/pull/175 to add {:as :json-strict}
for output coercion
- Added {:as :json-strict-string-keys} output coercion
- bump dependencies to their latest
- Merged https://github.com/dakrone/clj-http/pull/172 to update .gitignore file
and clean up whitespace for new clojure-mode
- Merged https://github.com/dakrone/clj-http/pull/171 to support SOCKS proxies
* Work log
** 2015-07-24
- branched master to create 2.x
- start major rewrite on master branch for non-deprecated Apache usage
** Released 2.0.0
** 2015-07-18
- merged https://github.com/dakrone/clj-http/pull/274 to update Potemkin so it
supports Clojure 1.7.0 correctly
** 2015-05-23
- merged https://github.com/dakrone/clj-http/pull/264 to add support for
coercion of urlencoded data
- make ALL optional dependencies opt-in, rather than opt-out
** Released 1.1.2
** 2015-05-06
- bumped dependencies for transit-clj and tools.reader
** 2015-04-24
- merge https://github.com/dakrone/clj-http/pull/263 to only decode body headers
when the content-type is either missing or starts with "text"
** Released 1.1.1
** 2015-04-22
- merge https://github.com/dakrone/clj-http/pull/262 to prevent
NullPointerException when decoding body headers with HEAD requests
** 2015-04-20
- merge https://github.com/dakrone/clj-http/pull/261 to decode user info from
URL if provided
** 2015-04-14
- merge https://github.com/dakrone/clj-http/pull/260 to upgrade tools.reader
for better cljs compatibility
** 2015-04-05
- add =304= (not modified) to the list of unexceptional responses, see #259
** Released 1.1.0
** 2015-03-03
- merged https://github.com/dakrone/clj-http/pull/255 to add support for Windows
NTLM authentication
** 2015-02-08
- Add the `with-additional-middleware` macro
- Add the ability to specify form-param-encoding for encoding form parameters
** 2015-01-19
- merged https://github.com/dakrone/clj-http/pull/248 to removed deprecated
cookie APIs from cookie.clj
- merged https://github.com/dakrone/clj-http/pull/245 to do some cleanups and
small import fixes
** 2015-01-15
- merged https://github.com/dakrone/clj-http/pull/240 to implement
meta/with-meta for the header map
- merged https://github.com/dakrone/clj-http/pull/242 fixing a connection leak
when http-entity is null
- bumped all dependencies to latest versions
** 2014-12-13
- merged https://github.com/dakrone/clj-http/pull/235 to fix wrap-nested-params
** 2014-12-12
- merged https://github.com/dakrone/clj-http/pull/236 to clean up multipart
constructors and reflection
** 2014-12-02
- merged https://github.com/dakrone/clj-http/pull/234 to allow scheme
customization in default connection
** Released 1.0.1
** 2014-10-28
- merged https://github.com/dakrone/clj-http/pull/232 to fix =empty= on
header-map
** 2014-10-17
- fix :json-strict-string-keys
** 2014-09-08
- exclude clojure.core/update from client ns
** 2014-08-15
- added =:decode-cookies= option to allow skipping cookie header decode (if the
server sends incorrectly formatted cookies for some reason)
** Released 1.0.0
** 2014-08-11
- merged https://github.com/dakrone/clj-http/pull/215 to add transit support
- drop support for clojure 1.4.0, start testing 1.7.0
** 2014-08-07
- merged https://github.com/dakrone/clj-http/pull/213 to allow passing in an
already existing keystore, not just a path
** 2014-07-27
- merged https://github.com/dakrone/clj-http/pull/211 to detect charset encoding
for url-encode
** Released 0.9.2
** 2014-05-27
- merged https://github.com/dakrone/clj-http/pull/206 to handle null passwords
for keystores
** 2014-05-14
- merged https://github.com/dakrone/clj-http/pull/201 to make :auto content type
parsing dispatch pluggable
** 2014-04-21
- Bump crouton and tools.reader dependencies
** 2014-04-09
- Merged https://github.com/dakrone/clj-http/pull/199 to add support for form
parameters in the PATCH method
** 2014-03-26
- Bump dependencies and fix tests for 1.6.0 compatibility
** Released 0.9.1
** 2014-03-15
- automatically coerce header values to strings
** 2014-03-05
- fix issue where :ignore-unknown-host wasn't using the =opt= function correctly
** Released 0.9.0
** 2014-02-25
- Bumped httpcore to 4.3.2
** 2014-02-19
- Merged https://github.com/dakrone/clj-http/pull/190 to support file multiparts
with content, mime-type and name
** 2014-02-16
- Unify all boolean operators so {:debug true} and {:debug? true} are treated
the same
** 2014-02-09
- Fix :trace-redirects being [nil] when :uri is used
** 2014-02-06
- Merged https://github.com/dakrone/clj-http/pull/184 containing a bevy of
changes:
- initial header-map implementation, allowing headers to be used case
insensitively
- drop support for clojure 1.2 and 1.3
- add support for clojure 1.6
- change all :use statements to :require statements
- use better docstring support for defs
- remove sleep calls in tests
- make Jetty quieter while running tests
- newer type hinting syntax
** Released 0.7.9
** 2014-02-01
- Make :decode-body-headers more reliable by using a byte array instead of
slurp.
clj-http-3.12.3/changelog.org_archive 0000664 0000000 0000000 00000057576 14070752310 0017511 0 ustar 00root root 0000000 0000000
Archived entries from file /Users/hinmanm/src/clj/clj-http/changelog.org
* Archived Tasks
** 0.7.8
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- Added the `proxy-ignore-hosts` option to allow specifying a list
of hosts where a proxy should be ignored
- merged https://github.com/dakrone/clj-http/pull/166 to fix some
small whitespace and reflection stuff
- Close the body of a response in wrap-redirects since all bodies
are streams now. Otherwise, the body is kept open indefinitely.
** 0.7.7
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/162 to pass
through json opts for form-param encoding
- bumped dependencies
- fix #159 - issue with :decode-body-headers introduced with
streaming bodies
- merged https://github.com/dakrone/clj-http/pull/156 to add
`:raw-headers` option to return an additional
untouched :raw-headers map
- merged https://github.com/dakrone/clj-http/pull/154 to handle
query-params not clobbering query-params in the URL string
- bump main deps
- merged https://github.com/dakrone/clj-http/pull/151 to prevent
shutting down a reusable connection manager when an error occurs
** 0.7.6
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- add logging config for local testing only
- remove "content-encoding" header if the body is automatically
decompressed to allow for pass-through. If header is removed,
assoc :orig-content-encoding to response map.
- merged https://github.com/dakrone/clj-http/pull/149 to fix
closing the stream when coerced to byte array
- merged https://github.com/dakrone/clj-http/pull/146 to correctly
reference parameter names
** 0.7.5
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- Only redirect if a "location" header is actually, present, avoiding an
NPE in the event it's missing. (fixes #145)
** 0.7.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/143 for fixing some
weirdness around body streams and inflation
- streams everywhere, all bodies coming out of core.clj are now streams, so
{:as :stream} truly streams the response, keeping it out of memory
- remove some more reflection
** 0.7.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- correctly close single client connection manager if {:as :stream} is used, fixes #142
- merged https://github.com/dakrone/clj-http/pull/138 to preserve
http method for 307 redirect
- merged in parse-url parameters into follow-redirect so request
map is not inconsistent
- bumped http* deps to 4.2.5
- fixed cookie compact-map not to remove falsey values, only nil
ones
- merged https://github.com/dakrone/clj-http/pull/135 to fix
discard always defaulting to true
- add *current-middleware* to see available middleware during a
with-middleware request (for nesting)
** 0.7.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/127 to allow
custom cookie policies
- allow specifying :length for mulitpart inputstream bodies to
avoid chunked transfer encoding
- bumped cheshire to 5.1.1
- merged https://github.com/dakrone/clj-http/pull/133 to remove
some reflection
** 0.7.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- clarify :throw-exceptions in documentation
- define default-middleware for use in wrap-request, remove bad
all-middleware method
- merged https://github.com/dakrone/clj-http/pull/130 to encode
query-params
- merged https://github.com/dakrone/clj-http/pull/124 to handle
URL-encoding invalid characters in the URI
- bump cheshire to 5.1.0
- Switch from deprecated SingleClientConnManager to BasicClientConnectionManager
- merged https://github.com/dakrone/clj-http/pull/126 to bump
httpcore version
- bump dependencies to latest versions
** 0.7.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/122 for
using *data-readers* when using tools.reader to parse EDN
- fix an issue with 1.3 where *data-readers* is not available
- merged https://github.com/dakrone/clj-http/pull/121 to fix
auto-coercion with json
- support application/edn as an auto-coercion type
- add tools.reader as an optional dependency, edn/read will be
used if available, otherwise read-string with *read-eval* bound
to false is used. See https://github.com/dakrone/clj-http/pull/120
- Bump clojure to 1.5.1
** 0.6.5
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- allow json coercion for exception cases based on :coerce setting,
can be either :always, :exceptional or :unexceptional
- Update clojure to 1.5
- Move SingleClientConnManager shutdown into finally block
- bind *read-eval* to false when reading for {:as :clojure}
- bump cheshire to 5.0.2
** 0.6.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/113 to update
the connection pooling code
- refactor pooled connection managers to allow specifying
the :connection-manager option
- merged https://github.com/dakrone/clj-http/pull/112 to allow
json coercion on error responses when :as :auto is used
- allow redirects when :url is not set in the request
- merged https://github.com/dakrone/clj-http/pull/110 to handle the
case when the server-side uses deflate incorrectly
- added `with-middleware` to allow running requests with a custom
middleware list
- added `all-middleware` var listing all the wrap-* middleware that
clj-http knows of
- clj-http.client/request is now marked as dynamic for rebinding
** 0.6.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- Remove wrap-cookie-store middleware, CookieStore headers are
automatically added by Apache
- set the SINGLE_COOKIE_HEADER value to true to ensure Apache sends
only one "Cookie:" header
- Do not add CookieStore or Cookie header if there are no cookies
in the cookie jar
** 0.6.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/106 to remove
query params for redirection.
- whitespace fixes; fix test that wasn't working correctly
** 0.6.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- bump httpcore to 4.2.3
- Fix an issue (#105) related to the "Content-Length" header being
automatically added to GET requests
** 0.6.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
(bumped to 0.6.0 since Cheshire has changed major versions)
- Update Cheshire to 5.0.1
- Add type hint for getting headers from body (michaelklishin)
** 0.5.8
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- add buffering for HttpEntity, with ability to turn off if needed,
fixes lein issue with repeatable requests
** 0.5.7
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- create a custom X509HostnameVerifier for the :insecure? option
- explicitly require httpcore instead of leaving it to a transitive dep
- update httpcomponents to 4.2.2
- implement HTML5 charset header reading from body
** 0.5.6
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- bump Crouton to 0.1.1 for faster speeds
- add feature to decode body headers, merging them into response
headers if they are present
- merged https://github.com/dakrone/clj-http/pull/98 to add
optional :default-per-route to with-connection-pool
** 0.5.5
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- bump cheshire to fix json encoding bug
** 0.5.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/95 to add support
for setting aribtrary client params to the http client
- Merged https://github.com/dakrone/clj-http/pull/94 to remove some
reflection
- update cheshire dep, make clojure a dev-dependency
- allow overriding the multipart part name with :part-name
** 0.5.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/91 to add support
for :digest-auth
- added request timing middleware to add :request-time key for
request timing
- add wrap-cookie-store to send cookie-store cookies with a request
automatically
- merged https://github.com/dakrone/clj-http/pull/90 to standardize
on lower-case headers for HTTP requests
** 0.5.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/88 to add chunked encoding
support (=:length= no longer required along with input stream =:body=)
** 0.5.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- fix clojure 1.3's exception wrapping for some exceptions
- merged https://github.com/dakrone/clj-http/pull/87 to allow using
http.nonProxyHosts
- mark json-encode and json-decode dynamic, so they could be
rebound if desired
- update httpclient and httpmime to 4.2.1
- update commons-codec to 1.6
- update common-io to 2.4
- change body decompression to be optional, if desired
- make the :content-type and :character-encoding options part of
middleware, not the core request
- document all the middleware
- merged https://github.com/dakrone/clj-http/pull/85 to allow
low-level callback for debugging
** 0.5.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- rewrite multipart body entity creation to use different map
format, allowing :mime-type and :encoding keys in some cases
** 0.4.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- bump cheshire to 4.0.1 and slingshot to 0.10.3
- fix an issue where cookies were encoded and should not be
- merged https://github.com/dakrone/clj-http/pull/80 to allow
specifying the keystore type
- merged https://github.com/dakrone/clj-http/pull/79 to allow
pluggable output coercion (multimethod)
** 0.4.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- support custom x509 keystore/trust-stores
** 0.4.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- fixed an issue where multiple link headers would cause an
exception to be thrown
** 0.4.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- added :debug-body that adds plaintext body information to
the :debug output
- fix json encoded form params with nested maps
- fix attempted json coercion when a bad status is received
- merged https://github.com/dakrone/clj-http/pull/69 to add support
for :oauth-token authentication
- merged https://github.com/dakrone/clj-http/pull/70 to save the
apache Http object when :save-request? is true
- merged https://github.com/dakrone/clj-http/pull/68 to support
additional options/delete/copy/move HTTP methods
- add support for the :patch method type
** 0.4.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/66 to add support
for 'Link' header
- added ability to specify your own retry-handler for IOExceptions
if desired
- bumped httpclient and httpmime to 4.1.3
- bump to released version of clojure (1.4)
- added documentation about ipv6 requests
- fixed https://github.com/dakrone/clj-http/issues/57 by have
wrap-redirects redirect according to the RFC and adding
the :force-redirects option to be more browser-like
- merged https://github.com/dakrone/clj-http/pull/61 to add support
for nested param maps
** 0.3.6
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- fixed an issue where urls like http://user:pass@foo.com didn't
work correctly for basic-auth
- added support for cookie stores
- added utility methods to retrieve cookies as a map from the
cookie store
- set the default maximum number of redirects to 20
** 0.3.5
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- same as 0.3.4, but with a newer cheshire that doesn't interfere
with clj-json
** 0.3.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- improved commit from pull/55 to make the predicate more generalized to
any kind of entity request
- make Cheshire an optional dependency, only for {:as :json} and
json form-params
- merged https://github.com/dakrone/clj-http/pull/55 to fix HEAD
requests with body contents
- merged https://github.com/dakrone/clj-http/pull/53 to add status
functions into the clj-http.client namespace
- added the ability to specify {:as :clojure} to get back a clojure
datastructure, or {:as :auto} with content-type=application/clojure
- merged https://github.com/dakrone/clj-http/pull/52 to support
json-encoded form params
- added a test for json-encoded form params as request body
** 0.3.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/51 to
allow :form-params on PUT requests
- bump Cheshire and slingshot deps
- add the :throw-entire-message? option to include resp in
Exception message
- throw an IllegalArgumentException instead of a regulor Exception
on nil urls
- add ability to redirect to relative paths (ngrunwald)
** 0.3.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/48 to fix :stream
bodies (to make sure they are not coerced on output)
- merged https://github.com/dakrone/clj-http/pull/49 to check for
nil URLs when using client functions
- switch from assertions to exceptions for nil URLs
- merged https://github.com/dakrone/clj-http/pull/46 to
add :trace-redirects to the response map
- merged https://github.com/dakrone/clj-http/pull/47 to allow GET
requests with a :body set
- merged https://github.com/dakrone/clj-http/pull/44 to add ability
to specify maximum number of redirects
- add tests for max-redirects
- merged https://github.com/dakrone/clj-http/pull/42 to allow
strings or keywords for :scheme in requests
- added test for different :schemes
** 0.3.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/40 to allow
per-request proxy settings
- remove a few more reflections
- added ablity to return the body as a stream with {:as :stream}
- general code cleanup
** 0.3.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- add ability to ignore unknown host if desired ({:ignore-unknown-host? true})
- use much better Enitity's for the body, depending on type
- bump all dependencies
- test re-org to make better sense (and allow C-c t in emacs)
- merged https://github.com/dakrone/clj-http/pull/36 to fix
url-encoding of multiple query params using the same key
- merged https://github.com/dakrone/clj-http/pull/34 to fix
decoding cookies that don't follow RFC spec
- Add better coercion, adding {:as :json}, {:as :json-string-keys}
and {:as :auto}
** 0.2.7
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- merged https://github.com/dakrone/clj-http/pull/31 to remove more
reflection warnings
- some whitespace changes
- merged https://github.com/dakrone/clj-http/pull/30 to remove more
reflection warnings
- removed swank from dev deps
- bump 1.4 to alpha3 in multi deps
** 0.2.6
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- don't use :server-port unless required (fixes problem with some
web servers)
- smaller error message on exceptions (thrown object is still the same)
- added the :save-request? option to return the request object in
a :request key in the response map
- multiple headers with the same name are now preserved when they
have differing cases
** 0.2.5
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- multipart form uploads
- bump slingshot to 0.9.0
** 0.2.4
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- Got a functioning reusable connection method,
(with-connection-pool ...)
- upgrade slingshot to 0.8.0
- upgrade commons-io to 2.1
- merged https://github.com/dakrone/clj-http/pull/20 to
allow :basic-auth as a string
** 0.2.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- added :insecure? flag
- fix AOT by requiring clojure.pprint
- wrap-redirects now handles recursive redirects
** 0.2.2
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- wrap-exceptions now uses Slingshot to throw a much more useful
exception when there was a problem with the request
- fixed an issue when malformed server responses could NPE the
decompression middleware
- added a :debug flag to pretty-print the request map and object
to stdout before performing the request to aid in debugging
** 0.2.1
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- decode cookies from response into :cookies (thanks r0man)
- redone redirects, they can now be toggled with {:follow-redirects
false} in the request
- decompression of responses has been fixed (thanks senior)
- accept Content-Encoding or content-encoding from responses
(thanks senior)
- added ability to specify sending a url-encoded :body of form
params using {:form-params {:key value}} (thanks senior)
** 0.2.0
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- updated dependencies to be the latest versions
- added ability to use system proxy for connections (thanks jou4)
- added ability to specify socket and connection timeouts in
request (thanks zkim)
** 0.1.3
:PROPERTIES:
:ARCHIVE_TIME: 2014-03-05 Wed 16:32
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Changelog
:ARCHIVE_CATEGORY: changelog
:END:
- see: https://github.com/mmcgrana/clj-http
** Released 0.7.8
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
** 2014-01-03
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
- bump dependencies to their latest
- Merged https://github.com/dakrone/clj-http/pull/172 to update .gitignore file
and clean up whitespace for new clojure-mode
- Merged https://github.com/dakrone/clj-http/pull/171 to support SOCKS proxies
** 2014-01-15
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
- Merged https://github.com/dakrone/clj-http/pull/175 to add {:as :json-strict}
for output coercion
- Added {:as :json-strict-string-keys} output coercion
** 2014-01-21
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
- Merged https://github.com/dakrone/clj-http/pull/177 to update apache HTTP deps
** 2014-01-27
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
- Merged https://github.com/dakrone/clj-http/pull/178 to eliminate test
reflection
** 2014-01-28
:PROPERTIES:
:ARCHIVE_TIME: 2015-04-22 Wed 23:13
:ARCHIVE_FILE: ~/src/clj/clj-http/changelog.org
:ARCHIVE_OLPATH: Work log
:ARCHIVE_CATEGORY: changelog
:END:
- Merged https://github.com/dakrone/clj-http/pull/181 to fix some tests
clj-http-3.12.3/examples/ 0000775 0000000 0000000 00000000000 14070752310 0015142 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/examples/body_coercion.clj 0000664 0000000 0000000 00000002156 14070752310 0020456 0 ustar 00root root 0000000 0000000 (ns clj-http.examples.body-coercion
(:require [clj-http.client :as http]
[camel-snake-kebab.core :refer [->kebab-case-keyword]]))
;; register your own body coercers by participating in the coerce-response-body multimethod
;; dispatch to it by using {:as :json-kebab-keys} as an argument to http client calls
;; this example uses camel-snake-kebab to turn a camel-cased JSON API into
;; idiomatic kebab-cased keywords in clojure data structures and is much
;; faster than applying via postwalk or similar
(defmethod http/coerce-response-body :json-kebab-keys [req resp]
(http/coerce-json-body req resp (memoize ->kebab-case-keyword) false))
;; example of use; note that in the response, the first field is called userId
;;
;; (:body (http/get "http://jsonplaceholder.typicode.com/posts/1" {:as :json-kebab-keys}))
;; =>
;; {:user-id 1,
;; :id 1,
;; :title "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
;; :body "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
;; }
clj-http-3.12.3/examples/caching_middleware.clj 0000664 0000000 0000000 00000005412 14070752310 0021427 0 ustar 00root root 0000000 0000000 (ns clj-http.examples.caching-middleware
"Example middleware that caches successful GET requests using core.cache."
(:require
[clj-http.client :as http]
[clojure.core.cache :as cache])
(:import
(java.nio.charset StandardCharsets)))
(def http-cache (atom (cache/ttl-cache-factory {} :ttl (* 60 60 1000))))
(defn slurp-bytes
"Read all bytes from the stream.
Use for example when the bytes will be in demand after stream has been closed."
[stream]
(.getBytes (slurp stream) StandardCharsets/UTF_8))
(defn- cached-response
"Look up the response in the cache using URL as the cache key.
If the cache has the response, return the cached value.
If the cache does not have the response, invoke the remaining middleware functions
to perform the request and receive the response.
If the response is successful (2xx) and is a GET, store the response in the cache.
Return the response."
([client req]
(let [cache-key (str (:server-name req) (:uri req) "?" (:query-string req))]
(if (cache/has? @http-cache cache-key)
(do
(println "CACHE HIT")
(reset! http-cache (cache/hit @http-cache cache-key)) ; update cache stats
(cache/lookup @http-cache cache-key)) ; return cached value
; do not invoke further middleware
(do
(println "CACHE MISS")
(let [resp (update (client req) :body slurp-bytes)] ; middleware chain invoked
(when (and (http/success? resp) (= (:request-method req) :get))
(reset! http-cache (cache/miss @http-cache cache-key resp)) ; update cache value
resp)))))))
(defn wrap-caching-middleware
"Middleware are functions that add functionality to handlers.
The argument client is a handler.
This wrapper function adds response caching to the client."
[client]
(fn
([req]
(cached-response client req))))
(defn example
"Add the caching middleware and perform a GET request using the URI argument.
Subsequent invocations of this function using an identical URI argument
before the Time To Live expires can be expected to hit the cache."
[& uri]
(-> (time (http/with-additional-middleware [#'wrap-caching-middleware]
(http/get (or uri "https://api.github.com")
{
;; :debug true
;; :debug-body true
;; :throw-entire-message? true
})))
(select-keys ,,, [:status :reason-phrase :headers])))
;; Try this out:
;;
;; user> (use '[clj-http.examples.caching-middleware :as mw])
;; nil
;; user> (mw/example)
;; CACHE MISS
;; "Elapsed time: 1910.027361 msecs"
;; {:status 200, :reason-phrase "OK"}
;; user> (mw/example)
;; CACHE HIT
;; "Elapsed time: 0.83484 msecs"
;; {:status 200, :reason-phrase "OK"}
;; user>
clj-http-3.12.3/examples/kubernetes_pod.clj 0000664 0000000 0000000 00000002225 14070752310 0020646 0 ustar 00root root 0000000 0000000 (:ns clj-http.examples.kubernetes-pod
"This is an example of calling the Kubernetes API from inside a pod. K8s uses a
custom CA so that you can authenticate the API server, and provides a token per pod
so that each pod can authenticate itself with the APi server.
If you are still having 401/403 errors, look carefully at the message, if it includes
a ServiceAccount name, this part worked, and your problem is likely at the Role/RoleBinding level."
(:require [clj-http.client :as http]
[less.awful.ssl :refer [trust-store]]))
;; Note that this is not a working example, you'll need to figure out your K8s API path.
(let [k8s-trust-store (trust-store (clojure.java.io/file "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"))
bearer-token (format "Bearer %s" (slurp "/var/run/secrets/kubernetes.io/serviceaccount/token"))
kube-api-host (System/getenv "KUBERNETES_SERVICE_HOST")
kube-api-port (System/getenv "KUBERNETES_SERVICE_PORT")]
(http/get
(format "https://%s:%s/apis/" kube-api-host kube-api-port)
{:trust-store k8s-trust-store
:headers {:authorization bearer-token}}))
clj-http-3.12.3/examples/logging-apache-requests.clj 0000664 0000000 0000000 00000002146 14070752310 0022355 0 ustar 00root root 0000000 0000000 (ns clj-http.examples.logging-apache-requests
"This is an example of configuring Apache's log4j2 logging from Clojure, so
that the http client logging can be seen"
(:require [clj-http.client :as http])
(:import (org.apache.logging.log4j Level
LogManager)))
;; This is a helper function to change the log level for log4j2. If you use a
;; different logging framework (and subsequently a different bridge for log4j
;; then you'll need to substitute your own logging configuration
(defn change-log-level! [logger-name level]
(let [ctx (LogManager/getContext false)
config (.getConfiguration ctx)
logger-config (.getLoggerConfig config logger-name)]
(.setLevel logger-config level)
(.updateLoggers ctx)))
;; Here is an example of using it to change the root logger to "DEBUG" and the
;; back to "INFO" after a request has been completed
(defn post-page-with-debug []
(change-log-level! LogManager/ROOT_LOGGER_NAME Level/DEBUG)
(http/post "https://httpbin.org/post" {:body "this is a test"})
(change-log-level! LogManager/ROOT_LOGGER_NAME Level/INFO))
clj-http-3.12.3/examples/progress_download.clj 0000664 0000000 0000000 00000006264 14070752310 0021377 0 ustar 00root root 0000000 0000000 (ns clj-http.examples.progress-download
(:require [clj-http.client :as http]
[clojure.java.io :refer [output-stream]])
(:import (org.apache.commons.io.input CountingInputStream)))
(defn print-progress-bar
"Render a simple progress bar given the progress and total. If the total is zero
the progress will run as indeterminated."
([progress total] (print-progress-bar progress total {}))
([progress total {:keys [bar-width]
:or {bar-width 50}}]
(if (pos? total)
(let [pct (/ progress total)
render-bar (fn []
(let [bars (Math/floor (* pct bar-width))
pad (- bar-width bars)]
(str (clojure.string/join (repeat bars "="))
(clojure.string/join (repeat pad " ")))))]
(print (str "[" (render-bar) "] "
(int (* pct 100)) "% "
progress "/" total)))
(let [render-bar (fn [] (clojure.string/join (repeat bar-width "-")))]
(print (str "[" (render-bar) "] "
progress "/?"))))))
(defn insert-at [v idx val]
"Addes value into a vector at an specific index."
(-> (subvec v 0 idx)
(conj val)
(into (subvec v idx))))
(defn insert-after [v needle val]
"Finds an item into a vector and adds val just after it.
If needle is not found, the input vector will be returned."
(let [index (.indexOf v needle)]
(if (neg? index)
v
(insert-at v (inc index) val))))
(defn wrap-downloaded-bytes-counter
"Middleware that provides an CountingInputStream wrapping the stream output"
[client]
(fn [req]
(let [resp (client req)
counter (CountingInputStream. (:body resp))]
(merge resp {:body counter
:downloaded-bytes-counter counter}))))
(defn download-with-progress [url target]
(http/with-middleware
(-> http/default-middleware
(insert-after http/wrap-redirects wrap-downloaded-bytes-counter)
(conj http/wrap-lower-case-headers))
(let [request (http/get url {:as :stream})
length (Integer. (get-in request [:headers "content-length"] 0))
buffer-size (* 1024 10)]
(println)
(with-open [input (:body request)
output (output-stream target)]
(let [buffer (make-array Byte/TYPE buffer-size)
counter (:downloaded-bytes-counter request)]
(loop []
(let [size (.read input buffer)]
(when (pos? size)
(.write output buffer 0 size)
(print "\r")
(print-progress-bar (.getByteCount counter) length)
(recur))))))
(println))))
;; Example of progress bar output (sample steps)
;;
;; [=== ] 7% 2094930/26572400
;; [============================== ] 60% 16062930/26572400
;; [========================================= ] 83% 22290930/26572400
;; [==================================================] 100% 26572400/26572400
;;
;; In case the content-length is unknown, the bar will be displayed as:
;;
;; [--------------------------------------------------] 4211440/?
clj-http-3.12.3/project.clj 0000664 0000000 0000000 00000005526 14070752310 0015474 0 ustar 00root root 0000000 0000000 (defproject clj-http "3.12.3"
:description "A Clojure HTTP library wrapping the Apache HttpComponents client."
:url "https://github.com/dakrone/clj-http/"
:license {:name "The MIT License"
:url "http://opensource.org/licenses/mit-license.php"
:distribution :repo}
:global-vars {*warn-on-reflection* false}
:min-lein-version "2.0.0"
:exclusions [org.clojure/clojure]
:dependencies [[org.apache.httpcomponents/httpcore "4.4.14"]
[org.apache.httpcomponents/httpclient "4.5.13"]
[org.apache.httpcomponents/httpclient-cache "4.5.13"]
[org.apache.httpcomponents/httpasyncclient "4.1.4"]
[org.apache.httpcomponents/httpmime "4.5.13"]
[commons-codec "1.15"]
[commons-io "2.8.0"]
[slingshot "0.12.2"]
[potemkin "0.4.5"]]
:resource-paths ["resources"]
:profiles {:dev {:dependencies [;; optional deps
[cheshire "5.10.0"]
[crouton "0.1.2" :exclusions [[org.jsoup/jsoup]]]
[org.jsoup/jsoup "1.13.1"]
[org.clojure/tools.reader "1.3.5"]
[com.cognitect/transit-clj "1.0.324"]
[ring/ring-codec "1.1.3"]
;; other (testing) deps
[org.clojure/clojure "1.10.3"]
[org.clojure/tools.logging "1.1.0"]
[ring/ring-jetty-adapter "1.9.3"]
[ring/ring-devel "1.9.3"]
;; caching example deps
[org.clojure/core.cache "1.0.207"]
;; logging
[org.apache.logging.log4j/log4j-api "2.14.1"]
[org.apache.logging.log4j/log4j-core "2.14.1"]
[org.apache.logging.log4j/log4j-1.2-api "2.14.1"]]
:plugins [[lein-ancient "0.7.0"]
[jonase/eastwood "0.2.5"]
[lein-kibit "0.1.5"]
[lein-nvd "0.5.2"]]}
:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]}
:1.7 {:dependencies [[org.clojure/clojure "1.7.0"]]}
:1.8 {:dependencies [[org.clojure/clojure "1.8.0"]]}
:1.9 {:dependencies [[org.clojure/clojure "1.9.0"]]}
:1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]}}
:aliases {"all" ["with-profile" "dev,1.6:dev,1.7:dev,1.8:dev,1.9:dev,1.10:dev"]}
:plugins [[codox "0.6.4"]]
:test-selectors {:default #(not (:integration %))
:integration :integration
:all (constantly true)})
clj-http-3.12.3/resources/ 0000775 0000000 0000000 00000000000 14070752310 0015336 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/resources/example-log4j2.properties 0000664 0000000 0000000 00000003000 14070752310 0022177 0 ustar 00root root 0000000 0000000 ###
# While no means required, this is an example log4j2.properties that you can use
# for debugging clj-http (mostly the apache http client side). See the readme or
# examples directory for how to use it.
# Change this to "debug" to get debugging information
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
rootLogger.appenderRef.rolling.ref = fileLogger
# Give directory path where log files should get stored
property.basePath = ./log/
status = error
# ConsoleAppender will print logs on console
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
# Specify the pattern of the logs
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
# RollingFileAppender will print logs in file which can be rotated based on time
# or size
appender.rolling.type = RollingFile
appender.rolling.name = fileLogger
appender.rolling.fileName=${basePath}/clj-http.log
appender.rolling.filePattern=${basePath}clj-http_%d{yyyyMMdd}.log.gz
appender.rolling.layout.type = PatternLayout
appender.rolling.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
appender.rolling.policies.type = Policies
# Mention package name here in place of example. Classes in this package or
# subpackages will use ConsoleAppender and RollingFileAppender for logging
logger.example.name = example
logger.example.level = debug
logger.example.additivity = false
logger.example.appenderRef.rolling.ref = fileLogger
logger.example.appenderRef.console.ref = console
clj-http-3.12.3/src/ 0000775 0000000 0000000 00000000000 14070752310 0014113 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/src/clj_http/ 0000775 0000000 0000000 00000000000 14070752310 0015722 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/src/clj_http/client.clj 0000664 0000000 0000000 00000132021 14070752310 0017671 0 ustar 00root root 0000000 0000000 (ns clj-http.client
"Batteries-included HTTP client."
(:refer-clojure :exclude [get update])
(:require [clj-http.conn-mgr :as conn]
[clj-http.cookies :refer [wrap-cookies]]
[clj-http.core :as core]
[clj-http.headers :refer [wrap-header-map]]
[clj-http.links :refer [wrap-links]]
[clj-http.util :as util :refer [opt]]
[clojure.java.io :as io]
[clojure.stacktrace :refer [root-cause]]
[clojure.string :as str]
[clojure.walk :refer [keywordize-keys prewalk]]
[slingshot.slingshot :refer [throw+]])
(:import [java.io BufferedReader ByteArrayInputStream ByteArrayOutputStream EOFException File InputStream]
[java.net UnknownHostException URL]
[org.apache.http.entity BufferedHttpEntity ByteArrayEntity FileEntity InputStreamEntity StringEntity]
org.apache.http.impl.conn.PoolingHttpClientConnectionManager
org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager))
;; Cheshire is an optional dependency, so we check for it at compile time.
(def json-enabled?
(try
(require 'cheshire.core)
true
(catch Throwable _ false)))
;; Crouton is an optional dependency, so we check for it at compile time.
(def crouton-enabled?
(try
(require 'crouton.html)
true
(catch Throwable _ false)))
;; tools.reader is an optional dependency, so check at compile time.
(def edn-enabled?
(try
(require 'clojure.tools.reader.edn)
true
(catch Throwable _ false)))
;; Transit is an optional dependency, so check at compile time.
(def transit-enabled?
(try
(require 'cognitect.transit)
true
(catch Throwable _ false)))
;; ring-codec is an optional dependency, so we check for it at compile time.
(def ring-codec-enabled?
(try
(require 'ring.util.codec)
true
(catch Throwable _ false)))
(defn ^:dynamic parse-edn
"Resolve and apply tool.reader's EDN parsing."
[& args]
{:pre [edn-enabled?]}
(apply (ns-resolve (symbol "clojure.tools.reader.edn")
(symbol "read-string"))
{:readers @(or (resolve '*data-readers*) (atom {}))} args))
(defn ^:dynamic parse-html
"Resolve and apply crouton's HTML parsing."
[& args]
{:pre [crouton-enabled?]}
(apply (ns-resolve (symbol "crouton.html") (symbol "parse")) args))
(defn- transit-opts-by-type
"Return the Transit options by type."
[opts type class-name]
{:pre [transit-enabled?]}
(cond
(empty? opts)
opts
(contains? opts type)
(clojure.core/get opts type)
:else
(let [class (Class/forName class-name)]
(println "Deprecated use of :transit-opts found.")
(update-in opts [:handlers]
(fn [handlers]
(->> handlers
(filter #(instance? class (second %)))
(into {})))))))
(defn- transit-read-opts
"Return the Transit read options."
[opts]
{:pre [transit-enabled?]}
(transit-opts-by-type opts :decode "com.cognitect.transit.ReadHandler"))
(defn- transit-write-opts
"Return the Transit write options."
[opts]
{:pre [transit-enabled?]}
(transit-opts-by-type opts :encode "com.cognitect.transit.WriteHandler"))
(defn ^:dynamic parse-transit
"Resolve and apply Transit's JSON/MessagePack decoding."
[^InputStream in type & [opts]]
{:pre [transit-enabled?]}
(when (pos? (.available in))
(let [reader (ns-resolve 'cognitect.transit 'reader)
read (ns-resolve 'cognitect.transit 'read)]
(read (reader in type (transit-read-opts opts))))))
(defn ^:dynamic transit-encode
"Resolve and apply Transit's JSON/MessagePack encoding."
[out type & [opts]]
{:pre [transit-enabled?]}
(let [output (ByteArrayOutputStream.)
writer (ns-resolve 'cognitect.transit 'writer)
write (ns-resolve 'cognitect.transit 'write)]
(write (writer output type (transit-write-opts opts)) out)
(.toByteArray output)))
(defn ^:dynamic json-encode
"Resolve and apply cheshire's json encoding dynamically."
[& args]
{:pre [json-enabled?]}
(apply (ns-resolve (symbol "cheshire.core") (symbol "encode")) args))
(defn ^:dynamic json-decode
"Resolve and apply cheshire's json decoding dynamically."
[& args]
{:pre [json-enabled?]}
(if-let [json-decode-fn (ns-resolve (symbol "cheshire.core") (symbol "parse-stream-strict"))]
(apply json-decode-fn args)
(throw
(IllegalStateException. "Missing #'cheshire.core/parse-stream-strict. Ensure the version of `cheshire` is >= 5.9.0"))))
(defn ^:dynamic form-decode
"Resolve and apply ring-codec's form decoding dynamically."
[& args]
{:pre [ring-codec-enabled?]}
(apply (ns-resolve (symbol "ring.util.codec") (symbol "form-decode")) args))
(defn update [m k f & args]
(assoc m k (apply f (m k) args)))
(defn when-pos [v]
(when (and v (pos? v)) v))
(defn dissoc-in
"Dissociates an entry from a nested associative structure returning a new
nested structure. keys is a sequence of keys. Any empty maps that result
will not be present in the new structure."
[m [k & ks :as keys]]
(if ks
(if-let [nextmap (clojure.core/get m k)]
(let [newmap (dissoc-in nextmap ks)]
(if (seq newmap)
(assoc m k newmap)
(dissoc m k)))
m)
(dissoc m k)))
(defn url-encode-illegal-characters
"Takes a raw url path or query and url-encodes any illegal characters.
Minimizes ambiguity by encoding space to %20."
[path-or-query]
(when path-or-query
(-> path-or-query
(str/replace " " "%20")
(str/replace #"[^a-zA-Z0-9\.\-\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@\/\%\?]"
util/url-encode))))
(defn parse-url
"Parse a URL string into a map of interesting parts."
[url]
(let [url-parsed (URL. url)]
{:scheme (keyword (.getProtocol url-parsed))
:server-name (.getHost url-parsed)
:server-port (when-pos (.getPort url-parsed))
:url url
:uri (url-encode-illegal-characters (.getPath url-parsed))
:user-info (if-let [user-info (.getUserInfo url-parsed)]
(util/url-decode user-info))
:query-string (url-encode-illegal-characters (.getQuery url-parsed))}))
(defn unparse-url
"Takes a map of url-parts and generates a string representation.
WARNING: does not do any sort of encoding! Don't use this for strict RFC
following!"
[{:keys [scheme server-name server-port uri user-info query-string]}]
(str (name scheme) "://"
(if (seq user-info)
(str user-info "@" server-name)
server-name)
(when server-port
(str ":" server-port))
uri
(when (seq query-string)
(str "?" query-string))))
;; Statuses for which clj-http will not throw an exception
(def unexceptional-status?
#{200 201 202 203 204 205 206 207 300 301 302 303 304 307 308})
(defn unexceptional-status-for-request?
[req status]
((or (:unexceptional-status req) unexceptional-status?)
status))
;; helper methods to determine realm of a response
(defn success?
[{:keys [status]}]
(<= 200 status 299))
(defn missing?
[{:keys [status]}]
(= status 404))
(defn conflict?
[{:keys [status]}]
(= status 409))
(defn redirect?
[{:keys [status]}]
(<= 300 status 399))
(defn client-error?
[{:keys [status]}]
(<= 400 status 499))
(defn server-error?
[{:keys [status]}]
(<= 500 status 599))
(defn- exceptions-response
[req {:keys [status] :as resp}]
(if (unexceptional-status-for-request? req status)
resp
(if (false? (opt req :throw-exceptions))
resp
(let [data (assoc resp :type ::unexceptional-status)]
(if (opt req :throw-entire-message)
(throw+ data "clj-http: status %d %s" (:status %) resp)
(throw+ data "clj-http: status %s" (:status %)))))))
(defn wrap-exceptions
"Middleware that throws a slingshot exception if the response is not a
regular response. If :throw-entire-message? is set to true, the entire
response is used as the message, instead of just the status number."
[client]
(fn
([req]
(exceptions-response req (client req)))
([req response raise]
(client req
(fn [resp]
(response (exceptions-response req resp)))
raise))))
(declare wrap-redirects)
(declare reuse-pool)
(defn- follow-redirect-request
[req redirect trace-redirects resp]
(-> req
(merge (parse-url redirect))
(dissoc :query-params)
(assoc :url redirect)
(assoc :trace-redirects trace-redirects)
(reuse-pool resp)))
(defn follow-redirect
"Attempts to follow the redirects from the \"location\" header, if no such
header exists (bad server!), returns the response without following the
request."
[client {:keys [uri url scheme server-name server-port async? respond raise]
:as req}
{:keys [trace-redirects ^InputStream body] :as resp}]
(let [url (or url (str (name scheme) "://" server-name
(when server-port (str ":" server-port)) uri))]
(if-let [raw-redirect (get-in resp [:headers "location"])]
(let [redirect (str (URL. (URL. url) raw-redirect))]
(try (.close body) (catch Exception _))
(if-not async?
((wrap-redirects client)
(follow-redirect-request req redirect trace-redirects resp))
(if (some nil? [respond raise])
(raise
(IllegalArgumentException.
"If :async? is true, you must set :respond and :raise"))
((wrap-redirects client)
(follow-redirect-request req redirect trace-redirects resp)
respond raise))))
;; Oh well, we tried, but if no location is set, return the response
(if-not async?
resp
(respond resp)))))
(defn- respond*
[resp req]
(if (opt req :async)
((:respond req) resp)
resp))
(defn- redirects-response
[client
{:keys [request-method max-redirects redirects-count trace-redirects url]
:or {redirects-count 1 trace-redirects []
;; max-redirects default taken from Firefox
max-redirects 20}
:as req} {:keys [status] :as resp}]
(let [resp-r (assoc resp :trace-redirects
(if url
(conj trace-redirects url)
trace-redirects))]
(cond
(false? (opt req :follow-redirects))
(respond* resp req)
(not (redirect? resp-r))
(respond* resp-r req)
(and max-redirects (> redirects-count max-redirects))
(if (opt req :throw-exceptions)
(throw+ resp-r "Too many redirects: %s" redirects-count)
(respond* resp-r req))
(= 303 status)
(follow-redirect client (assoc req :request-method :get
:redirects-count (inc redirects-count))
resp-r)
(#{301 302} status)
(cond
(#{:get :head} request-method)
(follow-redirect client (assoc req :redirects-count
(inc redirects-count)) resp-r)
(opt req :force-redirects)
(follow-redirect client (assoc req
:request-method :get
:redirects-count (inc redirects-count))
resp-r)
:else
(respond* resp-r req))
(#{307 308} status)
(if (or (#{:get :head} request-method)
(opt req :force-redirects))
(follow-redirect client (assoc req :redirects-count
(inc redirects-count)) resp-r)
(respond* resp-r req))
:else
(respond* resp-r req))))
(defn ^:deprecated wrap-redirects
"Middleware that follows redirects in the response. A slingshot exception is
thrown if too many redirects occur. Options
:follow-redirects - default:true, whether to follow redirects
:max-redirects - default:20, maximum number of redirects to follow
:force-redirects - default:false, force redirecting methods to GET requests
In the response:
:redirects-count - number of redirects
:trace-redirects - vector of sites the request was redirected from"
[client]
(fn
([req]
(redirects-response client req (client req)))
([req respond raise]
(client req
#(redirects-response client
(assoc req :async? true
:respond respond
:raise raise)
%)
raise))))
;; Multimethods for Content-Encoding dispatch automatically
;; decompressing response bodies
(defmulti decompress-body
(fn [resp] (get-in resp [:headers "content-encoding"])))
(defmethod decompress-body "gzip"
[resp]
(-> resp
(update :body util/gunzip)
(assoc :orig-content-encoding (get-in resp [:headers "content-encoding"]))
(dissoc-in [:headers "content-encoding"])))
(defmethod decompress-body "deflate"
[resp]
(-> resp
(update :body util/inflate)
(assoc :orig-content-encoding (get-in resp [:headers "content-encoding"]))
(dissoc-in [:headers "content-encoding"])))
(defmethod decompress-body :default [resp]
(assoc resp
:orig-content-encoding
(get-in resp [:headers "content-encoding"])))
(defn- decompression-request
[req]
(if (false? (opt req :decompress-body))
req
(update-in req [:headers "accept-encoding"]
#(str/join ", " (remove nil? [% "gzip, deflate"])))))
(defn- decompression-response
[req resp]
(if (false? (opt req :decompress-body))
resp
(decompress-body resp)))
(defn wrap-decompression
"Middleware handling automatic decompression of responses from web servers. If
:decompress-body is set to false, does not automatically set `Accept-Encoding`
header or decompress body."
[client]
(fn
([req]
(decompression-response req (client (decompression-request req))))
([req respond raise]
(client (decompression-request req)
#(respond (decompression-response req %))
raise))))
;; Multimethods for coercing body type to the :as key
(defmulti coerce-response-body (fn [req _] (:as req)))
(defmethod coerce-response-body :byte-array [_ resp]
(update resp :body util/force-byte-array))
(defmethod coerce-response-body :stream [_ resp]
(update resp :body util/force-stream))
(defn- response-charset [response]
(or (-> response :content-type-params :charset)
"UTF-8"))
(defmethod coerce-response-body :reader
[_ {:keys [body] :as resp}]
(let [header (get-in resp [:headers "content-type"])
parsed-values (util/parse-content-type header)
charset (response-charset parsed-values)]
(assoc resp :body (io/reader body :encoding charset))))
(defn- can-parse-body? [{:keys [coerce] :as request} {:keys [status] :as _response}]
(or (= coerce :always)
(and (unexceptional-status-for-request? request status)
(or (nil? coerce)
(= coerce :unexceptional)))
(and (not (unexceptional-status-for-request? request status))
(= coerce :exceptional))))
(defn- decode-json-body [body keyword? charset]
(let [^BufferedReader br (io/reader (util/force-stream body) :encoding charset)]
(try
(.mark br 1)
(let [first-char (int (try (.read br) (catch EOFException _ -1)))]
(case first-char
-1 nil
(do (.reset br)
(json-decode br keyword?))))
(finally (.close br)))))
(defn coerce-json-body
[request {:keys [body] :as resp} keyword? & [charset]]
{:pre [json-enabled?]}
(let [charset (or charset (response-charset resp))
body (if (can-parse-body? request resp)
(decode-json-body body keyword? charset)
(util/force-string body charset))]
(assoc resp :body body)))
(defn coerce-clojure-body
[_request {:keys [body] :as resp}]
(let [charset (response-charset resp)
body (util/force-string body charset)]
(assoc resp :body (cond
(empty? body) nil
edn-enabled? (parse-edn body)
:else (binding [*read-eval* false]
(read-string body))))))
(defn coerce-transit-body
[{:keys [transit-opts] :as request}
{:keys [body] :as resp} type & [charset]]
{:pre [transit-enabled?]}
(let [charset (or charset (response-charset resp))
body (if (can-parse-body? request resp)
(with-open [in (util/force-stream body)]
(parse-transit in type transit-opts))
(util/force-string body charset))]
(assoc resp :body body)))
(defn coerce-form-urlencoded-body
[_request {:keys [body] :as resp}]
{:pre [ring-codec-enabled?]}
(let [charset (response-charset resp)
body (util/force-string body charset)]
(assoc resp :body (-> body form-decode keywordize-keys))))
(defmulti coerce-content-type (fn [req resp] (:content-type resp)))
(defmethod coerce-content-type :application/clojure [req resp]
(coerce-clojure-body req resp))
(defmethod coerce-content-type :application/edn [req resp]
(coerce-clojure-body req resp))
(defmethod coerce-content-type :application/json [req resp]
(coerce-json-body req resp true false))
(defmethod coerce-content-type :application/transit+json [req resp]
(coerce-transit-body req resp :json))
(defmethod coerce-content-type :application/transit+msgpack [req resp]
(coerce-transit-body req resp :msgpack))
(defmethod coerce-content-type :application/x-www-form-urlencoded [req resp]
(coerce-form-urlencoded-body req resp))
(defmethod coerce-content-type :default [req resp]
(if-let [charset (-> resp :content-type-params :charset)]
(coerce-response-body {:as charset} resp)
(coerce-response-body {:as :default} resp)))
(defmethod coerce-response-body :auto [request resp]
(let [header (get-in resp [:headers "content-type"])]
(->> (merge resp (util/parse-content-type header))
(coerce-content-type request))))
(defmethod coerce-response-body :json [req resp]
(coerce-json-body req resp true))
(defmethod coerce-response-body :json-string-keys [req resp]
(coerce-json-body req resp false))
;; There is no longer any distinction between strict and non-strict JSON parsing
;; options.
;;
;; `:json-strict` and `:json-strict-string-keys` will be removed in a future version
(defmethod coerce-response-body :json-strict [req resp]
(coerce-json-body req resp true))
(defmethod coerce-response-body :json-strict-string-keys [req resp]
(coerce-json-body req resp false))
(defmethod coerce-response-body :clojure [req resp]
(coerce-clojure-body req resp))
(defmethod coerce-response-body :transit+json [req resp]
(coerce-transit-body req resp :json))
(defmethod coerce-response-body :transit+msgpack [req resp]
(coerce-transit-body req resp :msgpack))
(defmethod coerce-response-body :x-www-form-urlencoded [req resp]
(coerce-form-urlencoded-body req resp))
(defmethod coerce-response-body :default
[{:keys [as]} {:keys [body] :as resp}]
(assoc resp :body (util/force-string body (if (string? as) as "UTF-8"))))
(defn- output-coercion-response
[req {:keys [body] :as resp}]
(if body
(coerce-response-body req resp)
resp))
(defn wrap-output-coercion
"Middleware converting a response body from a byte-array to a different
object. Defaults to a String if no :as key is specified, the
`coerce-response-body` multimethod may be extended to add
additional coercions."
[client]
(fn
([req]
(output-coercion-response req (client req)))
([req respond raise]
(client req
#(respond (output-coercion-response req %))
raise))))
(defn maybe-wrap-entity
"Wrap an HttpEntity in a BufferedHttpEntity if warranted."
[{:keys [entity-buffering]} entity]
(if (and entity-buffering (not= BufferedHttpEntity (class entity)))
(BufferedHttpEntity. entity)
entity))
(defn- input-coercion-request
[{:keys [body body-encoding length]
:or {^String body-encoding "UTF-8"} :as req}]
(if body
(cond
(string? body)
(-> req (assoc :body (maybe-wrap-entity
req (StringEntity. ^String body
^String body-encoding))
:character-encoding (or body-encoding
"UTF-8")))
(instance? File body)
(-> req (assoc :body
(maybe-wrap-entity
req (FileEntity. ^File body
^String body-encoding))))
;; A length of -1 instructs HttpClient to use chunked encoding.
(instance? InputStream body)
(-> req
(assoc :body
(if length
(InputStreamEntity.
^InputStream body (long length))
(maybe-wrap-entity
req
(InputStreamEntity. ^InputStream body -1)))))
(instance? (Class/forName "[B") body)
(-> req (assoc :body (maybe-wrap-entity
req (ByteArrayEntity. body))))
:else
req)
req))
(defn wrap-input-coercion
"Middleware coercing the :body of a request from a number of formats into an
Apache Entity. Currently supports Strings, Files, InputStreams
and byte-arrays."
[client]
(fn
([req]
(client (input-coercion-request req)))
([req respond raise]
(client (input-coercion-request req) respond raise))))
(defn get-headers-from-body
"Given a map of body content, return a map of header-name to header-value."
[body-map]
(let [;; parse out HTML content
h (or (:content body-map)
(:content (first (filter #(= (:tag %) :html) body-map))))
;; parse out tags
heads (:content (first (filter #(= (:tag %) :head) h)))
;; parse out attributes of 'meta' head tags
attrs (map :attrs (filter #(= (:tag %) :meta) heads))
;; parse out the 'http-equiv' meta head tags
http-attrs (filter :http-equiv attrs)
;; parse out HTML5 charset meta tags
html5-charset (filter :charset attrs)
;; convert http-attributes into map of headers (lowercased)
headers (apply merge (map (fn [{:keys [http-equiv content]}]
{(.toLowerCase ^String http-equiv) content})
http-attrs))
;; merge in html5 charset setting
headers (merge headers
(when-let [cs (:charset (first html5-charset))]
{"content-type" (str "text/html; charset=" cs)}))]
headers))
(defn- additional-header-parsing-response
[req resp]
(if (and (opt req :decode-body-headers)
crouton-enabled?
(:body resp)
(let [^String content-type (get-in resp [:headers "content-type"])]
(or (str/blank? content-type)
(.startsWith content-type "text"))))
(let [body-bytes (util/force-byte-array (:body resp))
body-stream1 (java.io.ByteArrayInputStream. body-bytes)
body-map (parse-html body-stream1)
additional-headers (get-headers-from-body body-map)
body-stream2 (java.io.ByteArrayInputStream. body-bytes)]
(assoc resp
:headers (merge (:headers resp) additional-headers)
:body body-stream2))
resp))
(defn wrap-additional-header-parsing
"Middleware that parses additional http headers from the body of a web page,
adding them into the headers map of the response if any are found. Only looks
at the body if the :decode-body-headers option is set to a truthy value. Will
be silently disabled if crouton is excluded from clj-http's dependencies. Will
do nothing if no body is returned, e.g. HEAD requests"
[client]
(fn
([req]
(additional-header-parsing-response req (client req)))
([req respond raise]
(client req
#(respond (additional-header-parsing-response req %)) raise))))
(defn content-type-value [type]
(if (keyword? type)
(str "application/" (name type))
type))
(defn- content-type-request
[{:keys [content-type character-encoding] :as req}]
(if content-type
(let [ctv (content-type-value content-type)
ct (if character-encoding
(str ctv "; charset=" character-encoding)
ctv)]
(update-in req [:headers] assoc "content-type" ct))
req))
(defn wrap-content-type
"Middleware converting a `:content-type ` option to the formal
application/ format and adding it as a header."
[client]
(fn
([req]
(client (content-type-request req)))
([req respond raise]
(client (content-type-request req) respond raise))))
(defn- accept-request
[{:keys [accept] :as req}]
(if accept
(-> req (dissoc :accept)
(assoc-in [:headers "accept"]
(content-type-value accept)))
req))
(defn wrap-accept
"Middleware converting the :accept key in a request to application/"
[client]
(fn
([req]
(client (accept-request req)))
([req respond raise]
(client (accept-request req) respond raise))))
(defn accept-encoding-value [accept-encoding]
(str/join ", " (map name accept-encoding)))
(defn- accept-encoding-request
[{:keys [accept-encoding] :as req}]
(if accept-encoding
(-> req
(dissoc :accept-encoding)
(assoc-in [:headers "accept-encoding"]
(accept-encoding-value accept-encoding)))
req))
(defn wrap-accept-encoding
"Middleware converting the :accept-encoding option to an acceptable
Accept-Encoding header in the request."
[client]
(fn
([req]
(client (accept-encoding-request req)))
([req respond raise]
(client (accept-encoding-request req) respond raise))))
(defn detect-charset
"Given a charset header, detect the charset, returns UTF-8 if not found."
[content-type]
(or
(when-let [found (when content-type
(re-find #"(?i)charset\s*=\s*([^\s]+)" content-type))]
(second found))
"UTF-8"))
(defn- multi-param-entries [key values multi-param-style encoding]
(let [key (util/url-encode (name key) encoding)
values (map #(util/url-encode (str %) encoding) values)]
(case multi-param-style
:indexed
(map-indexed #(vector (str key \[ %1 \]) %2) values)
:array
(map #(vector (str key "[]") %) values)
:comma-separated
;; See sub-delims in https://tools.ietf.org/html/rfc3986#section-2.2
[[key (str/join "," values)]]
;; default: repeat the key multiple times
(map #(vector key %) values))))
(defn generate-query-string-with-encoding [params encoding multi-param-style]
(str/join "&"
(mapcat (fn [[k v]]
(if (sequential? v)
(map #(str/join "=" %) (multi-param-entries k v multi-param-style encoding))
[(str (util/url-encode (name k) encoding)
"="
(util/url-encode (str v) encoding))]))
params)))
(defn generate-query-string [params & [content-type multi-param-style]]
(let [encoding (detect-charset content-type)]
(generate-query-string-with-encoding params encoding multi-param-style)))
(defn- query-params-request
[{:keys [query-params content-type multi-param-style]
:or {content-type :x-www-form-urlencoded}
:as req}]
(if query-params
(-> req (dissoc :query-params)
(update-in [:query-string]
(fn [old-query-string new-query-string]
(if-not (empty? old-query-string)
(str old-query-string "&" new-query-string)
new-query-string))
(generate-query-string
query-params
(content-type-value content-type)
multi-param-style)))
req))
(defn wrap-query-params
"Middleware converting the :query-params option to a querystring on
the request."
[client]
(fn
([req]
(client (query-params-request req)))
([req respond raise]
(client (query-params-request req) respond raise))))
(defn basic-auth-value [basic-auth]
(let [basic-auth (if (string? basic-auth)
basic-auth
(str (first basic-auth) ":" (second basic-auth)))]
(str "Basic " (util/base64-encode (util/utf8-bytes basic-auth)))))
(defn- basic-auth-request
[req]
(if-let [basic-auth (:basic-auth req)]
(-> req (dissoc :basic-auth)
(assoc-in [:headers "authorization"]
(basic-auth-value basic-auth)))
req))
(defn wrap-basic-auth
"Middleware converting the :basic-auth option into an Authorization header."
[client]
(fn
([req]
(client (basic-auth-request req)))
([req respond raise]
(client (basic-auth-request req) respond raise))))
(defn- oauth-request
[req]
(if-let [oauth-token (:oauth-token req)]
(-> req (dissoc :oauth-token)
(assoc-in [:headers "authorization"]
(str "Bearer " oauth-token)))
req))
(defn wrap-oauth
"Middleware converting the :oauth-token option into an Authorization header."
[client]
(fn
([req]
(client (oauth-request req)))
([req respond raise]
(client (oauth-request req) respond raise))))
(defn parse-user-info [user-info]
(when user-info
(str/split user-info #":")))
(defn- user-info-request
[req]
(if-let [[user password] (parse-user-info (:user-info req))]
(assoc req :basic-auth [user password])
req))
(defn wrap-user-info
"Middleware converting the :user-info option into a :basic-auth option"
[client]
(fn
([req]
(client (user-info-request req)))
([req respond raise]
(client (user-info-request req) respond raise))))
(defn- method-request
[req]
(if-let [m (:method req)]
(-> req (dissoc :method)
(assoc :request-method m))
req))
(defn wrap-method
"Middleware converting the :method option into the :request-method option"
[client]
(fn
([req]
(client (method-request req)))
([req respond raise]
(client (method-request req) respond raise))))
(defmulti coerce-form-params
(fn [req] (keyword (content-type-value (:content-type req)))))
(defmethod coerce-form-params :application/edn
[{:keys [form-params]}]
(pr-str form-params))
(defn- coerce-transit-form-params [type {:keys [form-params transit-opts]}]
(when-not transit-enabled?
(throw (ex-info (format (str "Can't encode form params as "
"\"application/transit+%s\". "
"Transit dependency not loaded.")
(name type))
{:type :transit-not-loaded
:form-params form-params
:transit-opts transit-opts
:transit-type type})))
(transit-encode form-params type transit-opts))
(defmethod coerce-form-params :application/transit+json [req]
(coerce-transit-form-params :json req))
(defmethod coerce-form-params :application/transit+msgpack [req]
(coerce-transit-form-params :msgpack req))
(defmethod coerce-form-params :application/json
[{:keys [form-params json-opts]}]
(when-not json-enabled?
(throw (ex-info (str "Can't encode form params as \"application/json\". "
"Cheshire dependency not loaded.")
{:type :cheshire-not-loaded
:form-params form-params
:json-opts json-opts})))
(json-encode form-params json-opts))
(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 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]
:or {content-type :x-www-form-urlencoded}
:as req}]
(if (and form-params (#{:post :put :patch :delete} request-method))
(-> req
(dissoc :form-params)
(assoc :content-type (content-type-value content-type)
:body (coerce-form-params req)))
req))
(defn wrap-form-params
"Middleware wrapping the submission or form parameters."
[client]
(fn
([req]
(client (form-params-request req)))
([req respnd raise]
(client (form-params-request req) respnd raise))))
(defn- nest-params
[request param-key]
(if-let [params (request param-key)]
(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
[{:keys [flatten-nested-keys] :as req}]
(if (seq flatten-nested-keys)
(reduce
nest-params
req
flatten-nested-keys)
req))
(defn wrap-nested-params
"Middleware wrapping nested parameters for query strings."
[client]
(fn
([req]
(client (nest-params-request req)))
([req respond raise]
(client (nest-params-request req) respond raise))))
(defn- nested-keys-to-flatten
[{:keys [flatten-nested-keys] :as req}]
(when (and (or (not (nil? (opt req :ignore-nested-query-string)))
(not (nil? (opt req :flatten-nested-form-params))))
flatten-nested-keys)
(throw (IllegalArgumentException.
(str "only :flatten-nested-keys or :ignore-nested-query-string/"
":flatten-nested-keys may be specified, not both"))))
(let [iqs-key (when-not (opt req :ignore-nested-query-string) :query-params)
ifp-key (when (opt req :flatten-nested-form-params) :form-params)]
(or flatten-nested-keys
(remove nil? (list iqs-key ifp-key)))))
(defn wrap-flatten-nested-params
"Middleware wrapping options for whether or not to flatten `:query-params` and
`:form-params`. Modifies the request by adding a `:flatten-nested-keys`
sequence of the nested keys that will be flattened."
[client]
(fn
([req]
(client
(assoc req :flatten-nested-keys (nested-keys-to-flatten req))))
([req respond raise]
(client
(assoc req :flatten-nested-keys (nested-keys-to-flatten req))
respond raise))))
(defn- url-request
[req]
(if-let [url (:url req)]
(-> req (dissoc :url) (merge (parse-url url)))
req))
(defn wrap-url
"Middleware wrapping request URL parsing."
[client]
(fn
([req]
(client (url-request req)))
([req respond raise]
(client (url-request req) respond raise))))
(defn wrap-unknown-host
"Middleware ignoring unknown hosts when the :ignore-unknown-host? option
is set."
[client]
(fn
([req]
(try
(client req)
(catch Exception e
(if (= (type (root-cause e)) UnknownHostException)
(when-not (opt req :ignore-unknown-host)
(throw (root-cause e)))
(throw (root-cause e))))))
([req respond raise]
(client (assoc req :unknown-host-respond respond) respond raise))))
(defn wrap-lower-case-headers
"Middleware lowercasing all headers, as per RFC (case-insensitive) and
Ring spec."
[client]
(let [lower-case-headers
#(if-let [headers (:headers %1)]
(assoc %1 :headers (util/lower-case-keys headers))
%1)]
(fn
([req]
(-> (client (lower-case-headers req))
(lower-case-headers)))
([req respond raise]
(client (lower-case-headers req)
#(respond (lower-case-headers %))
raise)))))
(defn- request-timing-response
[resp start]
(assoc resp :request-time (- (System/currentTimeMillis) start)))
(defn wrap-request-timing
"Middleware that times the request, putting the total time (in milliseconds)
of the request into the :request-time key in the response."
[client]
(fn
([req]
(let [start (System/currentTimeMillis)
resp (client req)]
(request-timing-response resp start)))
([req respond raise]
(let [start (System/currentTimeMillis)]
(client req
#(respond (request-timing-response % start))
raise)))))
(def default-middleware
"The default list of middleware clj-http uses for wrapping requests."
[wrap-request-timing
wrap-header-map
wrap-query-params
wrap-basic-auth
wrap-oauth
wrap-user-info
wrap-url
wrap-decompression
wrap-input-coercion
;; put this before output-coercion, so additional charset
;; headers can be used if desired
wrap-additional-header-parsing
wrap-output-coercion
wrap-exceptions
wrap-accept
wrap-accept-encoding
wrap-content-type
wrap-form-params
wrap-nested-params
wrap-flatten-nested-params
wrap-method
wrap-cookies
wrap-links
wrap-unknown-host])
(def ^:dynamic
*current-middleware*
"Available at any time to retrieve the middleware being used.
Automatically bound when `with-middleware` is used."
default-middleware)
(defn wrap-request
"Returns a batteries-included HTTP request function corresponding to the given
core client. See default-middleware for the middleware wrappers that are used
by default"
[request]
(reduce (fn wrap-request* [request middleware]
(middleware request))
request
default-middleware))
(def ^:dynamic request
"Executes the HTTP request corresponding to the given map and returns
the response map for corresponding to the resulting HTTP response.
In addition to the standard Ring request keys, the following keys are also
recognized:
* :url
* :method
* :query-params
* :basic-auth
* :content-type
* :accept
* :accept-encoding
* :as
The following keys make an async HTTP request, like ring's CPS handler.
* :async?
* :respond
* :raise
The following additional behaviors are also automatically enabled:
* Exceptions are thrown for status codes other than 200-207, 300-303, or 307
* Gzip and deflate responses are accepted and decompressed
* Input and output bodies are coerced as required and indicated by the :as
option."
(wrap-request #'core/request))
;; Inline function to throw a slightly more readable exception when
;; the URL is nil
(definline check-url! [url]
`(when (nil? ~url)
(throw (IllegalArgumentException. "Host URL cannot be nil"))))
(defn- request*
[req [respond raise]]
(if (opt req :async)
(if (some nil? [respond raise])
(throw (IllegalArgumentException.
"If :async? is true, you must pass respond and raise"))
(request (dissoc req :respond :raise) respond raise))
(request req)))
(defn get
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :get :url url}) r))
(defn head
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :head :url url}) r))
(defn post
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :post :url url}) r))
(defn put
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :put :url url}) r))
(defn delete
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :delete :url url}) r))
(defn options
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :options :url url}) r))
(defn copy
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :copy :url url}) r))
(defn move
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :move :url url}) r))
(defn patch
"Like #'request, but sets the :method and :url as appropriate."
[url & [req & r]]
(check-url! url)
(request* (merge req {:method :patch :url url}) r))
(defmacro with-middleware
"Perform the body of the macro with a custom middleware list.
It is highly recommended to at least include:
clj-http.client/wrap-url
clj-http.client/wrap-method
Unless you really know what you are doing."
[middleware & body]
`(let [m# ~middleware]
(binding [*current-middleware* m#
clj-http.client/request (reduce #(%2 %1)
clj-http.core/request
m#)]
~@body)))
(defmacro with-additional-middleware
"Perform the body of the macro with a list of additional middleware.
The given `middleware-seq' is concatenated to the beginning of the
`*current-middleware*' sequence."
[middleware-seq & body]
`(with-middleware (concat ~middleware-seq *current-middleware*)
~@body))
(defmacro with-connection-pool
"Macro to execute the body using a connection manager. Creates a
PoolingHttpClientConnectionManager to use for all requests within the
body of the expression. An option map is allowed to set options for the
connection manager.
The following options are supported:
:timeout - Time that connections are left open before automatically closing
default: 5
:threads - Maximum number of threads that will be used for connecting
default: 4
:default-per-route - Maximum number of simultaneous connections per host
default: 2
:insecure? - Boolean flag to specify allowing insecure HTTPS connections
default: false
:keystore - keystore file path or KeyStore instance to be used for
connection manager
:keystore-pass - keystore password
:trust-store - trust store file path or KeyStore instance to be used for
connection manager
:trust-store-pass - trust store password
Note that :insecure? and :keystore/:trust-store options are mutually exclusive
If the value 'nil' is specified or the value is not set, the default value
will be used."
[opts & body]
;; I'm leaving the connection bindable for now because in the
;; future I'm toying with the idea of managing the connection
;; manager yourself and passing it into the request
`(let [cm# (conn/make-reusable-conn-manager ~opts)]
(binding [conn/*connection-manager* cm#]
(try
~@body
(finally
(.shutdown
^PoolingHttpClientConnectionManager
conn/*connection-manager*))))))
(defn reuse-pool
"A helper function takes a request options map and a response map respond
from a pooled async request, the returned options map will be set to reuse
the connection pool which used by the former request"
[options response]
(if-let [info (:pooling-info response)]
(assoc options :pooling-info info)
options))
(defmacro with-async-connection-pool
"Macro to execute the body using a connection manager. Creates a
PoolingNHttpClientConnectionManager to use for all requests within the body of
the expression. An option map is allowed to set options for the connection
manager.
Handles the same options as `with-connection-pool` plus:
:io-config which should be a map containing some of the following keys:
:connect-timeout - int the default connect timeout value for connection
requests (default 0, meaning no timeout)
:interest-op-queued - boolean, whether or not I/O interest operations are to
be queued and executed asynchronously or to be applied to the underlying
SelectionKey immediately (default false)
:io-thread-count - int, the number of I/O dispatch threads to be used
(default is the number of available processors)
:rcv-buf-size - int the default value of the SO_RCVBUF parameter for
newly created sockets (default is 0, meaning the system default)
:select-interval - long, time interval in milliseconds at which to check for
timed out sessions and session requests (default 1000)
:shutdown-grace-period - long, grace period in milliseconds to wait for
individual worker threads to terminate cleanly (default 500)
:snd-buf-size - int, the default value of the SO_SNDBUF parameter for
newly created sockets (default is 0, meaning the system default)
:so-keep-alive - boolean, the default value of the SO_KEEPALIVE parameter for
newly created sockets (default false)
:so-linger - int, the default value of the SO_LINGER parameter for
newly created sockets (default -1)
:so-timeout - int, the default socket timeout value for I/O operations
(default 0, meaning no timeout)
:tcp-no-delay - boolean, the default value of the TCP_NODELAY parameter for
newly created sockets (default true)
If the value 'nil' is specified or the value is not set, the default value
will be used."
[opts & body]
`(let [cm# (conn/make-reuseable-async-conn-manager ~opts)]
(binding [conn/*async-connection-manager* cm#]
(try
~@body
(finally
(.shutdown
^PoolingNHttpClientConnectionManager
cm#))))))
clj-http-3.12.3/src/clj_http/conn_mgr.clj 0000664 0000000 0000000 00000040326 14070752310 0020223 0 ustar 00root root 0000000 0000000 (ns clj-http.conn-mgr
"Utility methods for Scheme registries and HTTP connection managers"
(:require [clj-http.util :refer [opt]]
[clojure.java.io :as io])
(:import [java.net InetSocketAddress Proxy Proxy$Type Socket]
java.security.KeyStore
[javax.net.ssl HostnameVerifier KeyManager SSLContext TrustManager]
[org.apache.http.config ConnectionConfig Registry RegistryBuilder SocketConfig]
org.apache.http.conn.HttpClientConnectionManager
org.apache.http.conn.socket.PlainConnectionSocketFactory
[org.apache.http.conn.ssl DefaultHostnameVerifier NoopHostnameVerifier SSLConnectionSocketFactory SSLContexts TrustStrategy]
[org.apache.http.impl.conn BasicHttpClientConnectionManager PoolingHttpClientConnectionManager]
org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager
org.apache.http.impl.nio.DefaultHttpClientIODispatch
[org.apache.http.impl.nio.reactor DefaultConnectingIOReactor IOReactorConfig]
[org.apache.http.nio.conn NHttpClientConnectionManager NoopIOSessionStrategy]
org.apache.http.nio.conn.ssl.SSLIOSessionStrategy
org.apache.http.nio.protocol.HttpAsyncRequestExecutor))
;; -- Interop Helpers ---------------------------------------------------------
(defn ^Registry into-registry [registry]
(cond
(instance? Registry registry)
registry
(map? registry)
(let [registry-builder (RegistryBuilder/create)]
(doseq [[k v] registry]
(.register registry-builder k v))
(.build registry-builder))
:else
(throw (IllegalArgumentException. "Cannot coerce into a Registry"))))
;; -- SocketFactory -----------------------------------------------------------
(defn ^SSLConnectionSocketFactory SSLGenericSocketFactory
"Given a function that returns a new socket, create an
SSLConnectionSocketFactory that will use that socket."
([socket-factory]
(SSLGenericSocketFactory socket-factory nil))
([socket-factory ^SSLContext ssl-context]
(let [^SSLContext ssl-context' (or ssl-context (SSLContexts/createDefault))]
(proxy [SSLConnectionSocketFactory] [ssl-context']
(connectSocket [timeout socket host remoteAddress localAddress context]
(let [^SSLConnectionSocketFactory this this] ;; avoid reflection
(proxy-super connectSocket timeout (socket-factory) host remoteAddress
localAddress context)))))))
(defn ^PlainConnectionSocketFactory PlainGenericSocketFactory
"Given a Function that returns a new socket, create a
PlainConnectionSocketFactory that will use that socket."
[socket-factory]
(proxy [PlainConnectionSocketFactory] []
(createSocket [context]
(socket-factory))))
(defn socks-proxied-socket
"Create a Socket proxied through socks, using the given hostname and port"
[^String hostname ^Integer port]
(Socket. (Proxy. Proxy$Type/SOCKS (InetSocketAddress. hostname port))))
;; -- SSL Contexts ------------------------------------------------------------
(defn ^KeyStore get-keystore*
[keystore-file keystore-type ^String keystore-pass]
(when keystore-file
(let [keystore (KeyStore/getInstance (or keystore-type
(KeyStore/getDefaultType)))]
(with-open [is (io/input-stream keystore-file)]
(.load keystore is (when keystore-pass (.toCharArray keystore-pass)))
keystore))))
(defn ^KeyStore get-keystore [keystore & args]
(if (instance? KeyStore keystore)
keystore
(apply get-keystore* keystore args)))
(defn- ssl-context-for-keystore
;; TODO: use something else for passwords
;; Note: JVM strings aren't ideal for passwords - see
;; https://tinyurl.com/azm3ab9
[{:keys [keystore keystore-type ^String keystore-pass
trust-store trust-store-type trust-store-pass]}]
(let [ks (get-keystore keystore keystore-type keystore-pass)
ts (get-keystore trust-store trust-store-type trust-store-pass)]
(-> (SSLContexts/custom)
(.loadKeyMaterial
ks (when keystore-pass
(.toCharArray keystore-pass)))
(.loadTrustMaterial
ts nil)
(.build))))
(defn- ssl-context-for-trust-or-key-manager
"Given an instance or seqable data structure of TrustManager or KeyManager
will create and return an SSLContexts object including the resulting managers"
[{:keys [trust-managers key-managers]}]
(let [x-or-xs->x-array (fn [type x-or-xs]
(cond
(or (-> x-or-xs class .isArray)
(sequential? x-or-xs))
(into-array type (seq x-or-xs))
:else
(into-array type [x-or-xs])))
trust-managers (when trust-managers
(x-or-xs->x-array TrustManager trust-managers))
key-managers (when key-managers
(x-or-xs->x-array KeyManager key-managers))]
(doto (.build (SSLContexts/custom))
(.init key-managers trust-managers nil))))
(defn- ssl-context-insecure
"Creates a SSL Context that trusts all material."
[]
(-> (SSLContexts/custom)
(.loadTrustMaterial nil (reify TrustStrategy
(isTrusted [_ chain auth-type] true)))
(.build)))
(defn ^SSLContext get-ssl-context
"Gets the SSL Context from a request or connection pool settings"
[{:keys [keystore trust-store key-managers trust-managers] :as config}]
(cond (or keystore trust-store)
(ssl-context-for-keystore config)
(or key-managers trust-managers)
(ssl-context-for-trust-or-key-manager config)
(opt config :insecure)
(ssl-context-insecure)
:else
(SSLContexts/createDefault)))
(defn ^HostnameVerifier get-hostname-verifier [config]
(if (opt config :insecure)
NoopHostnameVerifier/INSTANCE
(DefaultHostnameVerifier.)))
;; -- Connection Managers -----------------------------------------------------
(defn make-socks-proxied-conn-manager
"Given an optional hostname and a port, create a connection manager that's
proxied using a SOCKS proxy."
([^String hostname ^Integer port]
(make-socks-proxied-conn-manager hostname port {}))
([^String hostname ^Integer port
{:keys [keystore keystore-type keystore-pass
trust-store trust-store-type trust-store-pass
trust-managers key-managers] :as config}]
(let [socket-factory #(socks-proxied-socket hostname port)
registry (into-registry
{"http" (PlainGenericSocketFactory socket-factory)
"https" (SSLGenericSocketFactory socket-factory (get-ssl-context config))})]
(PoolingHttpClientConnectionManager. registry))))
(defn ^BasicHttpClientConnectionManager make-regular-conn-manager
[{:keys [dns-resolver
keystore trust-store
key-managers trust-managers
socket-timeout] :as config}]
(let [registry (into-registry
{"http" (PlainConnectionSocketFactory/getSocketFactory)
"https" (SSLConnectionSocketFactory.
(get-ssl-context config)
(get-hostname-verifier config))})
conn-manager (BasicHttpClientConnectionManager. registry
nil nil
dns-resolver)]
(when socket-timeout
(.setSocketConfig conn-manager
(-> (.getSocketConfig conn-manager)
(SocketConfig/copy)
(.setSoTimeout socket-timeout) ;modify only the socket-timeout
(.build))))
conn-manager))
(defn- ^DefaultConnectingIOReactor make-ioreactor
[{:keys [connect-timeout interest-op-queued io-thread-count rcv-buf-size
select-interval shutdown-grace-period snd-buf-size
so-keep-alive so-linger so-timeout tcp-no-delay]}]
(as-> (IOReactorConfig/custom) c
(if-some [v connect-timeout] (.setConnectTimeout c v) c)
(if-some [v interest-op-queued] (.setInterestOpQueued c v) c)
(if-some [v io-thread-count] (.setIoThreadCount c v) c)
(if-some [v rcv-buf-size] (.setRcvBufSize c v) c)
(if-some [v select-interval] (.setSelectInterval c v) c)
(if-some [v shutdown-grace-period] (.setShutdownGracePeriod c v) c)
(if-some [v snd-buf-size] (.setSndBufSize c v) c)
(if-some [v so-keep-alive] (.setSoKeepAlive c v) c)
(if-some [v so-linger] (.setSoLinger c v) c)
(if-some [v so-timeout] (.setSoTimeout c v) c)
(if-some [v tcp-no-delay] (.setTcpNoDelay c v) c)
(DefaultConnectingIOReactor. (.build c))))
(defn ^PoolingNHttpClientConnectionManager
make-regular-async-conn-manager
[{:keys [keystore trust-store
key-managers trust-managers] :as config}]
(let [^Registry registry (into-registry
{"http" (NoopIOSessionStrategy/INSTANCE)
"https" (SSLIOSessionStrategy.
(get-ssl-context config)
(get-hostname-verifier config))})
io-reactor (make-ioreactor {:shutdown-grace-period 1})]
(doto (PoolingNHttpClientConnectionManager. io-reactor registry)
(.setMaxTotal 1))))
(definterface ReuseableAsyncConnectionManager)
;; need the fully qualified class name because this fn is later used in a
;; macro from a different ns
(defn ^org.apache.http.impl.conn.PoolingHttpClientConnectionManager
make-reusable-conn-manager*
"Given an timeout and optional insecure? flag, create a
PoolingHttpClientConnectionManager with seconds set as the
timeout value."
[{:keys [dns-resolver
timeout
keystore trust-store
key-managers trust-managers] :as config}]
(let [registry (into-registry
{"http" (PlainConnectionSocketFactory/getSocketFactory)
"https" (SSLConnectionSocketFactory.
(get-ssl-context config)
(get-hostname-verifier config))})]
(PoolingHttpClientConnectionManager.
registry nil nil dns-resolver timeout java.util.concurrent.TimeUnit/SECONDS)))
(defn reusable? [conn-mgr]
(or (instance? PoolingHttpClientConnectionManager conn-mgr)
(instance? ReuseableAsyncConnectionManager conn-mgr)))
(defn ^PoolingHttpClientConnectionManager make-reusable-conn-manager
"Creates a default pooling connection manager with the specified options.
The following options are supported:
:timeout - Time that connections are left open before automatically closing
default: 5
:threads - Maximum number of threads that will be used for connecting
default: 4
:default-per-route - Maximum number of simultaneous connections per host
default: 2
:insecure? - Boolean flag to specify allowing insecure HTTPS connections
default: false
:keystore - keystore file to be used for connection manager
:keystore-pass - keystore password
:trust-store - trust store file to be used for connection manager
:trust-store-pass - trust store password
:key-managers - KeyManager objects to be used for connection manager
:trust-managers - TrustManager objects to be used for connection manager
:dns-resolver - Use a custom DNS resolver instead of the default DNS resolver.
Note that :insecure? and :keystore/:trust-store/:key-managers/:trust-managers options are mutually exclusive
Note that :key-managers/:trust-managers have precedence over :keystore/:trust-store options
If the value 'nil' is specified or the value is not set, the default value
will be used."
[opts]
(let [timeout (or (:timeout opts) 5)
threads (or (:threads opts) 4)
default-per-route (:default-per-route opts)
insecure? (opt opts :insecure)
leftovers (dissoc opts :timeout :threads :insecure? :insecure)
conn-man (make-reusable-conn-manager* (merge {:timeout timeout
:insecure? insecure?}
leftovers))]
(.setMaxTotal conn-man threads)
(when default-per-route
(.setDefaultMaxPerRoute conn-man default-per-route))
conn-man))
(defn- ^PoolingNHttpClientConnectionManager make-reusable-async-conn-manager*
[{:keys [dns-resolver
timeout keystore trust-store io-config
key-managers trust-managers] :as config}]
(let [registry (into-registry
{"http" (NoopIOSessionStrategy/INSTANCE)
"https" (SSLIOSessionStrategy.
(get-ssl-context config)
(get-hostname-verifier config))})
io-reactor (make-ioreactor io-config)
protocol-handler (HttpAsyncRequestExecutor.)
io-event-dispatch (DefaultHttpClientIODispatch. protocol-handler
ConnectionConfig/DEFAULT)]
(future (.execute io-reactor io-event-dispatch))
(proxy [PoolingNHttpClientConnectionManager ReuseableAsyncConnectionManager]
[io-reactor nil registry nil dns-resolver timeout
java.util.concurrent.TimeUnit/SECONDS])))
(defn ^PoolingNHttpClientConnectionManager make-reusable-async-conn-manager
"Creates a default pooling async connection manager with the specified
options. Handles the same options as make-reusable-conn-manager plus
:io-config which should be a map containing some of the following keys:
:connect-timeout - int the default connect timeout value for connection
requests (default 0, meaning no timeout)
:interest-op-queued - boolean, whether or not I/O interest operations are to
be queued and executed asynchronously or to be applied to the underlying
SelectionKey immediately (default false)
:io-thread-count - int, the number of I/O dispatch threads to be used
(default is the number of available processors)
:rcv-buf-size - int the default value of the SO_RCVBUF parameter for
newly created sockets (default is 0, meaning the system default)
:select-interval - long, time interval in milliseconds at which to check for
timed out sessions and session requests (default 1000)
:shutdown-grace-period - long, grace period in milliseconds to wait for
individual worker threads to terminate cleanly (default 500)
:snd-buf-size - int, the default value of the SO_SNDBUF parameter for
newly created sockets (default is 0, meaning the system default)
:so-keep-alive - boolean, the default value of the SO_KEEPALIVE parameter for
newly created sockets (default false)
:so-linger - int, the default value of the SO_LINGER parameter for
newly created sockets (default -1)
:so-timeout - int, the default socket timeout value for I/O operations
(default 0, meaning no timeout)
:tcp-no-delay - boolean, the default value of the TCP_NODELAY parameter for
newly created sockets (default true)
If the value 'nil' is specified or the value is not set, the default value
will be used."
[opts]
(let [timeout (or (:timeout opts) 5)
threads (or (:threads opts) 4)
default-per-route (:default-per-route opts)
insecure? (opt opts :insecure)
leftovers (dissoc opts :timeout :threads :insecure? :insecure)
conn-man (make-reusable-async-conn-manager*
(merge {:timeout timeout :insecure? insecure?} leftovers))]
(.setMaxTotal conn-man threads)
(when default-per-route
(.setDefaultMaxPerRoute conn-man default-per-route))
conn-man))
(defn ^PoolingNHttpClientConnectionManager make-reuseable-async-conn-manager
"Wraps correctly-spelled version - keeping for backwards compatibility."
[opts]
(make-reusable-async-conn-manager opts))
(defmulti shutdown-manager
"Shut down the given connection manager, if it is not nil"
class)
(defmethod shutdown-manager nil [conn-mgr] nil)
(defmethod shutdown-manager org.apache.http.conn.HttpClientConnectionManager
[^HttpClientConnectionManager conn-mgr] (.shutdown conn-mgr))
(defmethod shutdown-manager
org.apache.http.nio.conn.NHttpClientConnectionManager
[^NHttpClientConnectionManager conn-mgr] (.shutdown conn-mgr))
(def ^:dynamic *connection-manager*
"connection manager to be rebound during request execution"
nil)
(def ^:dynamic *async-connection-manager*
"connection manager to be rebound during async request execution"
nil)
clj-http-3.12.3/src/clj_http/cookies.clj 0000664 0000000 0000000 00000012513 14070752310 0020052 0 ustar 00root root 0000000 0000000 (ns clj-http.cookies
"Namespace dealing with HTTP cookies"
(:require [clj-http.util :refer [opt]]
[clojure.string :refer [blank? join lower-case]])
(:import org.apache.http.client.CookieStore
[org.apache.http.cookie ClientCookie CookieOrigin CookieSpec]
org.apache.http.Header
org.apache.http.impl.client.BasicCookieStore
[org.apache.http.impl.cookie BasicClientCookie2 BrowserCompatSpecFactory]
org.apache.http.message.BasicHeader
org.apache.http.protocol.BasicHttpContext))
(defn cookie-spec ^org.apache.http.cookie.CookieSpec []
(.create
(BrowserCompatSpecFactory.)
(BasicHttpContext.)))
(defn compact-map
"Removes all map entries where value is nil."
[m]
(reduce (fn [newm k]
(if (not (nil? (get m k)))
(assoc newm k (get m k))
newm))
(sorted-map) (sort (keys m))))
(defn to-cookie
"Converts a ClientCookie object into a tuple where the first item is
the name of the cookie and the second item the content of the
cookie."
[^ClientCookie cookie]
[(.getName cookie)
(compact-map
{:comment (.getComment cookie)
:comment-url (.getCommentURL cookie)
:discard (not (.isPersistent cookie))
:domain (.getDomain cookie)
:expires (when (.getExpiryDate cookie) (.getExpiryDate cookie))
:path (.getPath cookie)
:ports (when (.getPorts cookie) (seq (.getPorts cookie)))
:secure (.isSecure cookie)
:value (.getValue cookie)
:version (.getVersion cookie)})])
(defn ^BasicClientCookie2
to-basic-client-cookie
"Converts a cookie seq into a BasicClientCookie2."
[[cookie-name cookie-content]]
(doto (BasicClientCookie2. (name cookie-name)
(name (:value cookie-content)))
(.setComment (:comment cookie-content))
(.setCommentURL (:comment-url cookie-content))
(.setDiscard (:discard cookie-content true))
(.setDomain (:domain cookie-content))
(.setExpiryDate (:expires cookie-content))
(.setPath (:path cookie-content))
(.setPorts (int-array (:ports cookie-content)))
(.setSecure (:secure cookie-content false))
(.setVersion (:version cookie-content 0))))
(defn decode-cookie
"Decode the Set-Cookie string into a cookie seq."
[set-cookie-str]
(if-not (blank? set-cookie-str)
;; I just want to parse a cookie without providing origin. How?
(let [domain (lower-case (str (gensym)))
origin (CookieOrigin. domain 80 "/" false)
[cookie-name cookie-content] (-> (cookie-spec)
(.parse (BasicHeader.
"set-cookie"
set-cookie-str)
origin)
first
to-cookie)]
[cookie-name
(if (= domain (:domain cookie-content))
(dissoc cookie-content :domain) cookie-content)])))
(defn decode-cookies
"Converts a cookie string or seq of strings into a cookie map."
[cookies]
(reduce #(assoc %1 (first %2) (second %2)) {}
(map decode-cookie (if (sequential? cookies) cookies [cookies]))))
(defn decode-cookie-header
"Decode the Set-Cookie header into the cookies key."
[response]
(if-let [cookies (get (:headers response) "set-cookie")]
(assoc response
:cookies (decode-cookies cookies)
:headers (dissoc (:headers response) "set-cookie"))
response))
(defn encode-cookie
"Encode the cookie into a string used by the Cookie header."
[cookie]
(when-let [header (-> (cookie-spec)
(.formatCookies [(to-basic-client-cookie cookie)])
first)]
(.getValue ^Header header)))
(defn encode-cookies
"Encode the cookie map into a string."
[cookie-map] (join ";" (map encode-cookie (seq cookie-map))))
(defn encode-cookie-header
"Encode the :cookies key of the request into a Cookie header."
[request]
(if (:cookies request)
(-> request
(assoc-in [:headers "Cookie"] (encode-cookies (:cookies request)))
(dissoc :cookies))
request))
(defn- cookies-response
[request response]
(if (= false (opt request :decode-cookies))
response
(decode-cookie-header response)))
(defn wrap-cookies
"Middleware wrapping cookie handling. Handles converting
the :cookies request parameter into the 'Cookies' header for an HTTP
request."
[client]
(fn
([request]
(cookies-response request (client (encode-cookie-header request))))
([request respond raise]
(client (encode-cookie-header request)
#(respond (cookies-response request %))
raise))))
(defn cookie-store
"Returns a new, empty instance of the default implementation of the
org.apache.http.client.CookieStore interface."
[]
(BasicCookieStore.))
(defn get-cookies
"Given a cookie-store, return a map of cookie name to a map of cookie values."
[^CookieStore cookie-store]
(when cookie-store
(into {} (map to-cookie (.getCookies cookie-store)))))
(defn add-cookie
"Add a ClientCookie to a cookie-store"
[^CookieStore cookie-store ^ClientCookie cookie]
(.addCookie cookie-store cookie))
(defn clear-cookies
"Clears all cookies from cookie-store"
[^CookieStore cookie-store]
(.clear cookie-store))
clj-http-3.12.3/src/clj_http/core.clj 0000664 0000000 0000000 00000072274 14070752310 0017360 0 ustar 00root root 0000000 0000000 (ns clj-http.core
"Core HTTP request/response implementation. Rewrite for Apache 4.3"
(:require [clj-http.conn-mgr :as conn]
[clj-http.headers :as headers]
[clj-http.multipart :as mp]
[clj-http.util :refer [opt]]
clojure.pprint)
(:import [java.io ByteArrayOutputStream FilterInputStream InputStream]
[java.net InetAddress ProxySelector URI URL]
java.util.Locale
[org.apache.http HeaderIterator HttpEntity HttpEntityEnclosingRequest HttpHost HttpRequestInterceptor HttpResponse HttpResponseInterceptor ProtocolException]
[org.apache.http.auth AuthScope NTCredentials UsernamePasswordCredentials]
[org.apache.http.client CredentialsProvider HttpRequestRetryHandler RedirectStrategy]
org.apache.http.client.cache.HttpCacheContext
[org.apache.http.client.config CookieSpecs RequestConfig]
[org.apache.http.client.methods CloseableHttpResponse HttpDelete HttpEntityEnclosingRequestBase HttpGet HttpHead HttpOptions HttpPatch HttpPost HttpPut HttpRequestBase HttpUriRequest]
org.apache.http.client.protocol.HttpClientContext
org.apache.http.client.utils.URIUtils
org.apache.http.config.RegistryBuilder
org.apache.http.conn.routing.HttpRoutePlanner
org.apache.http.cookie.CookieSpecProvider
[org.apache.http.entity ByteArrayEntity StringEntity]
[org.apache.http.impl.client BasicCredentialsProvider CloseableHttpClient DefaultRedirectStrategy HttpClientBuilder HttpClients LaxRedirectStrategy]
[org.apache.http.impl.client.cache CacheConfig CachingHttpClientBuilder]
[org.apache.http.impl.conn DefaultProxyRoutePlanner SystemDefaultRoutePlanner]
[org.apache.http.impl.nio.client CloseableHttpAsyncClient HttpAsyncClientBuilder HttpAsyncClients]))
(def CUSTOM_COOKIE_POLICY "_custom")
(defn parse-headers
"Takes a HeaderIterator and returns a map of names to values.
If a name appears more than once (like `set-cookie`) then the value
will be a vector containing the values in the order they appeared
in the headers."
[^HeaderIterator headers & [use-header-maps-in-response?]]
(if-not use-header-maps-in-response?
(->> (headers/header-iterator-seq headers)
(map (fn [[k v]]
[(.toLowerCase ^String k) v]))
(reduce (fn [hs [k v]]
(headers/assoc-join hs k v))
{}))
(->> (headers/header-iterator-seq headers)
(reduce (fn [hs [k v]]
(headers/assoc-join hs k v))
(headers/header-map)))))
(defn graceful-redirect-strategy
"Similar to the default redirect strategy, however, does not throw an error
when the maximum number of redirects has been reached. Still supports
validating that the new redirect host is not empty."
[req]
(let [validate? (opt req :validate-redirects)]
(reify RedirectStrategy
(getRedirect [this request response context]
(let [new-request (.getRedirect DefaultRedirectStrategy/INSTANCE
request response context)]
(when (or validate? (nil? validate?))
(let [uri (.getURI new-request)
new-host (URIUtils/extractHost uri)]
(when (nil? new-host)
(throw
(ProtocolException.
(str "Redirect URI does not specify a valid host name: "
uri))))))
new-request))
(isRedirected [this request response context]
(let [^HttpClientContext typed-context context
max-redirects (-> (.getRequestConfig typed-context)
.getMaxRedirects)
num-redirects (count (.getRedirectLocations typed-context))]
(if (<= max-redirects num-redirects)
false
(.isRedirected DefaultRedirectStrategy/INSTANCE
request response typed-context)))))))
(defn default-redirect-strategy
"Proxies calls to whatever original redirect strategy is passed in, however,
if :validate-redirects is set in the request, checks that the redirected host
is not empty."
[^RedirectStrategy original req]
(let [validate? (opt req :validate-redirects)]
(reify RedirectStrategy
(getRedirect [this request response context]
(let [new-request (.getRedirect original request response context)]
(when (or validate? (nil? validate?))
(let [uri (.getURI new-request)
new-host (URIUtils/extractHost uri)]
(when (nil? new-host)
(throw
(ProtocolException.
(str "Redirect URI does not specify a valid host name: "
uri))))))
new-request))
(isRedirected [this request response context]
(.isRedirected original request response context)))))
(defn get-redirect-strategy [{:keys [redirect-strategy] :as req}]
(case redirect-strategy
:none (reify RedirectStrategy
(getRedirect [this request response context] nil)
(isRedirected [this request response context] false))
;; Like default, but does not throw exceptions when max redirects is
;; reached.
:graceful (graceful-redirect-strategy req)
:default (default-redirect-strategy DefaultRedirectStrategy/INSTANCE req)
:lax (default-redirect-strategy (LaxRedirectStrategy.) req)
nil (default-redirect-strategy DefaultRedirectStrategy/INSTANCE req)
;; use directly as reifed RedirectStrategy
redirect-strategy))
(defn ^HttpClientBuilder add-retry-handler [^HttpClientBuilder builder handler]
(when handler
(.setRetryHandler
builder
(proxy [HttpRequestRetryHandler] []
(retryRequest [e cnt context]
(handler e cnt context)))))
builder)
(defn create-custom-cookie-policy-registry
"Given a function that will take an HttpContext and return a CookieSpec,
create a new Registry for the cookie policy under the CUSTOM_COOKIE_POLICY
string."
[cookie-spec-fn]
(-> (RegistryBuilder/create)
(.register CUSTOM_COOKIE_POLICY
(proxy [CookieSpecProvider] []
(create [context]
(cookie-spec-fn context))))
(.build)))
(defmulti get-cookie-policy
"Method to retrieve the cookie policy that should be used for the request.
This is a multimethod that may be extended to return your own cookie policy.
Dispatches based on the `:cookie-policy` key in the request map."
(fn get-cookie-dispatch [request] (:cookie-policy request)))
(defmethod get-cookie-policy :none none-cookie-policy
[_] CookieSpecs/IGNORE_COOKIES)
(defmethod get-cookie-policy :default default-cookie-policy
[_] CookieSpecs/DEFAULT)
(defmethod get-cookie-policy nil nil-cookie-policy
[_] CookieSpecs/DEFAULT)
(defmethod get-cookie-policy :netscape netscape-cookie-policy
[_] CookieSpecs/NETSCAPE)
(defmethod get-cookie-policy :standard standard-cookie-policy
[_] CookieSpecs/STANDARD)
(defmethod get-cookie-policy :stardard-strict standard-strict-cookie-policy
[_] CookieSpecs/STANDARD_STRICT)
(defn request-config [{:keys [connection-timeout
connection-request-timeout
socket-timeout
max-redirects
cookie-spec
normalize-uri
; deprecated
conn-request-timeout
conn-timeout]
:as req}]
(let [config (-> (RequestConfig/custom)
(.setConnectTimeout (or connection-timeout conn-timeout -1))
(.setSocketTimeout (or socket-timeout -1))
(.setConnectionRequestTimeout
(or connection-request-timeout conn-request-timeout -1))
(.setRedirectsEnabled true)
(.setCircularRedirectsAllowed
(boolean (opt req :allow-circular-redirects)))
(.setRelativeRedirectsAllowed
((complement false?)
(opt req :allow-relative-redirects))))]
(if cookie-spec
(.setCookieSpec config CUSTOM_COOKIE_POLICY)
(.setCookieSpec config (get-cookie-policy req)))
(when max-redirects (.setMaxRedirects config max-redirects))
(when-not (nil? normalize-uri) (.setNormalizeUri config normalize-uri))
(.build config)))
(defmulti ^:private construct-http-host (fn [proxy-host proxy-port]
(class proxy-host)))
(defmethod construct-http-host String
[^String proxy-host ^Long proxy-port]
(if proxy-port
(HttpHost. proxy-host proxy-port)
(HttpHost. proxy-host)))
(defmethod construct-http-host java.net.InetAddress
[^InetAddress proxy-host ^Long proxy-port]
(if proxy-port
(HttpHost. proxy-host proxy-port)
(HttpHost. proxy-host)))
(defn ^HttpRoutePlanner get-route-planner
"Return an HttpRoutePlanner that either use the supplied proxy settings
if any, or the JVM/system proxy settings otherwise"
[^String proxy-host ^Long proxy-port proxy-ignore-hosts http-url]
(let [ignore-proxy? (and http-url
(contains? (set proxy-ignore-hosts)
(.getHost (URL. http-url))))]
(if (and proxy-host (not ignore-proxy?))
(DefaultProxyRoutePlanner. (construct-http-host proxy-host proxy-port))
(SystemDefaultRoutePlanner. (ProxySelector/getDefault)))))
(defn build-cache-config
"Given a request with :cache-config as a map or a CacheConfig object, return a
CacheConfig object, or nil if no cache config is found. If :cache-config is a
map, it checks for the following options:
- :allow-303-caching
- :asynchronous-worker-idle-lifetime-secs
- :asynchronous-workers-core
- :asynchronous-workers-max
- :heuristic-caching-enabled
- :heuristic-coefficient
- :heuristic-default-lifetime
- :max-cache-entries
- :max-object-size
- :max-update-retries
- :never-cache-http10-responses-with-query-string
- :revalidation-queue-size
- :shared-cache
- :weak-etag-on-put-delete-allowed"
[request]
(when-let [cc (:cache-config request)]
(if (instance? CacheConfig cc)
cc
(let [config (CacheConfig/custom)
{:keys [allow-303-caching
asynchronous-worker-idle-lifetime-secs
asynchronous-workers-core
asynchronous-workers-max
heuristic-caching-enabled
heuristic-coefficient
heuristic-default-lifetime
max-cache-entries
max-object-size
max-update-retries
never-cache-http10-responses-with-query-string
revalidation-queue-size
shared-cache
weak-etag-on-put-delete-allowed]} cc]
(when (instance? Boolean allow-303-caching)
(.setAllow303Caching config allow-303-caching))
(when asynchronous-worker-idle-lifetime-secs
(.setAsynchronousWorkerIdleLifetimeSecs
config asynchronous-worker-idle-lifetime-secs))
(when asynchronous-workers-core
(.setAsynchronousWorkersCore config asynchronous-workers-core))
(when asynchronous-workers-max
(.setAsynchronousWorkersMax config asynchronous-workers-max))
(when (instance? Boolean heuristic-caching-enabled)
(.setHeuristicCachingEnabled config heuristic-caching-enabled))
(when heuristic-coefficient
(.setHeuristicCoefficient config heuristic-coefficient))
(when heuristic-default-lifetime
(.setHeuristicDefaultLifetime config heuristic-default-lifetime))
(when max-cache-entries
(.setMaxCacheEntries config max-cache-entries))
(when max-object-size
(.setMaxObjectSize config max-object-size))
(when max-update-retries
(.setMaxUpdateRetries config max-update-retries))
;; I would add this option, but there is a bug in 4.x CacheConfig that
;; it does not actually correctly use the object from the builder.
;; It's fixed in 5.0 however
;; (when (boolean? never-cache-http10-responses-with-query-string)
;; (.setNeverCacheHTTP10ResponsesWithQueryString
;; config never-cache-http10-responses-with-query-string))
(when revalidation-queue-size
(.setRevalidationQueueSize config revalidation-queue-size))
(when (instance? Boolean shared-cache)
(.setSharedCache config shared-cache))
(when (instance? Boolean weak-etag-on-put-delete-allowed)
(.setWeakETagOnPutDeleteAllowed config weak-etag-on-put-delete-allowed))
(.build config)))))
(defn build-http-client
"Builds an Apache `HttpClient` from a clj-http request map. Optional arguments
`http-url` and `proxy-ignore-hosts` are used to specify the host and a list of
hostnames to ignore for any proxy settings. They can be safely ignored if not
using proxies."
[{:keys [retry-handler request-interceptor
response-interceptor proxy-host proxy-port
http-builder-fns cookie-spec
cookie-policy-registry
^HttpClientBuilder http-client-builder]
:as req}
caching?
conn-mgr
& [http-url proxy-ignore-hosts]]
;; have to let first, otherwise we get a reflection warning on (.build)
(let [cache? (opt req :cache)
builder (-> (cond
http-client-builder http-client-builder
caching?
^HttpClientBuilder (CachingHttpClientBuilder/create)
:else
^HttpClientBuilder (HttpClients/custom))
(.setConnectionManager conn-mgr)
(.setRedirectStrategy
(get-redirect-strategy req))
(add-retry-handler retry-handler)
;; prefer using clj-http.client/wrap-decompression
;; for consistency between sync/async client options
(.disableContentCompression)
;; By default, get the proxy settings
;; from the jvm or system properties
(.setRoutePlanner
(get-route-planner
proxy-host proxy-port
proxy-ignore-hosts http-url)))]
(when cache?
(.setCacheConfig ^CachingHttpClientBuilder builder (build-cache-config req)))
(when (or cookie-policy-registry cookie-spec)
(if cookie-policy-registry
;; They have a custom registry they'd like to re-use, so use that
(.setDefaultCookieSpecRegistry builder cookie-policy-registry)
;; They have only a one-time function for cookie spec, so use that
(.setDefaultCookieSpecRegistry
builder (create-custom-cookie-policy-registry cookie-spec))))
(when request-interceptor
(.addInterceptorLast
builder (proxy [HttpRequestInterceptor] []
(process [req ctx]
(request-interceptor req ctx)))))
(when response-interceptor
(.addInterceptorLast
builder (proxy [HttpResponseInterceptor] []
(process [resp ctx]
(response-interceptor
resp ctx)))))
(doseq [http-builder-fn http-builder-fns]
(http-builder-fn builder req))
(.build builder)))
(defn build-async-http-client
"Builds an Apache `HttpAsyncClient` from a clj-http request map. Optional
arguments `http-url` and `proxy-ignore-hosts` are used to specify the host
and a list of hostnames to ignore for any proxy settings. They can be safely
ignored if not using proxies."
[{:keys [request-interceptor response-interceptor
proxy-host proxy-port async-http-builder-fns]
:as req}
conn-mgr & [http-url proxy-ignore-hosts]]
;; have to let first, otherwise we get a reflection warning on (.build)
(let [^HttpAsyncClientBuilder builder (-> (HttpAsyncClients/custom)
(.setConnectionManager conn-mgr)
(.setRedirectStrategy
(get-redirect-strategy req))
;; By default, get the proxy
;; settings from the jvm or system
;; properties
(.setRoutePlanner
(get-route-planner
proxy-host proxy-port
proxy-ignore-hosts http-url)))]
(when (conn/reusable? conn-mgr)
(.setConnectionManagerShared builder true))
(when request-interceptor
(.addInterceptorLast
builder (proxy [HttpRequestInterceptor] []
(process [req ctx]
(request-interceptor req ctx)))))
(when response-interceptor
(.addInterceptorLast
builder (proxy [HttpResponseInterceptor] []
(process [resp ctx]
(response-interceptor
resp ctx)))))
(doseq [async-http-builder-fn async-http-builder-fns]
(async-http-builder-fn builder req))
(.build builder)))
(defn http-get []
(HttpGet. "https://www.google.com"))
(defn make-proxy-method-with-body
[method]
(fn [url]
(doto (proxy [HttpEntityEnclosingRequestBase] []
(getMethod [] (.toUpperCase (name method) Locale/ROOT)))
(.setURI (URI. url)))))
(def proxy-delete-with-body (make-proxy-method-with-body :delete))
(def proxy-get-with-body (make-proxy-method-with-body :get))
(def proxy-copy-with-body (make-proxy-method-with-body :copy))
(def proxy-move-with-body (make-proxy-method-with-body :move))
(def proxy-patch-with-body (make-proxy-method-with-body :patch))
(def ^:dynamic *cookie-store* nil)
(defn make-proxy-method [method url]
(doto (proxy [HttpRequestBase] []
(getMethod
[]
(str method)))
(.setURI (URI/create url))))
(defn http-request-for
"Provides the HttpRequest object for a particular request-method and url"
[request-method ^String http-url body]
(case request-method
:get (if body
(proxy-get-with-body http-url)
(HttpGet. http-url))
:head (HttpHead. http-url)
:put (HttpPut. http-url)
:post (HttpPost. http-url)
:options (HttpOptions. http-url)
:delete (if body
(proxy-delete-with-body http-url)
(HttpDelete. http-url))
:copy (proxy-copy-with-body http-url)
:move (proxy-move-with-body http-url)
:patch (if body
(proxy-patch-with-body http-url)
(HttpPatch. http-url))
(if body
((make-proxy-method-with-body request-method) http-url)
(make-proxy-method request-method http-url))))
(defn ^HttpClientContext http-context [caching? request-config http-client-context]
(let [^HttpClientContext typed-context (or http-client-context
(if caching?
(HttpCacheContext/create)
(HttpClientContext/create)))]
(doto typed-context
(.setRequestConfig request-config))))
(defn ^CredentialsProvider credentials-provider []
(BasicCredentialsProvider.))
(defn- coerce-body-entity
"Coerce the http-entity from an HttpResponse to a stream that closes itself
and the connection manager when closed."
[^HttpEntity http-entity conn-mgr ^CloseableHttpResponse response]
(if http-entity
(proxy [FilterInputStream]
[^InputStream (.getContent http-entity)]
(close []
(try
;; Eliminate the reflection warning from proxy-super
(let [^InputStream this this]
(proxy-super close))
(finally
(when (instance? CloseableHttpResponse response)
(.close response))
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))))))
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))))
(defn- print-debug!
"Print out debugging information to *out* for a given request."
[{:keys [debug-body body] :as req} http-req]
(println
(with-out-str
(println "Request:" (type body))
(clojure.pprint/pprint
(assoc req
:body (if (opt req :debug-body)
(cond
(isa? (type body) String)
body
(isa? (type body) HttpEntity)
(let [baos (ByteArrayOutputStream.)]
(.writeTo ^HttpEntity body baos)
(.toString baos "UTF-8"))
:else nil)
(if (isa? (type body) String)
(format "... %s bytes ..."
(count body))
(and body (bean body))))
:body-type (type body)))
(println "HttpRequest:")
(clojure.pprint/pprint (bean http-req)))))
(defn- build-response-map
[^HttpResponse response req ^HttpUriRequest http-req http-url
conn-mgr ^HttpClientContext context ^CloseableHttpClient client]
(let [^HttpEntity entity (.getEntity response)
status (.getStatusLine response)
protocol-version (.getProtocolVersion status)
body (:body req)
response
{:body (coerce-body-entity entity conn-mgr response)
:http-client client
:headers (parse-headers
(.headerIterator response)
(opt req :use-header-maps-in-response))
:length (if (nil? entity) 0 (.getContentLength entity))
:chunked? (if (nil? entity) false (.isChunked entity))
:repeatable? (if (nil? entity) false (.isRepeatable entity))
:streaming? (if (nil? entity) false (.isStreaming entity))
:status (.getStatusCode status)
:protocol-version {:name (.getProtocol protocol-version)
:major (.getMajor protocol-version)
:minor (.getMinor protocol-version)}
:reason-phrase (.getReasonPhrase status)
:trace-redirects (mapv str (.getRedirectLocations context))
:cached (when (instance? HttpCacheContext context)
(when-let [cache-resp (.getCacheResponseStatus ^HttpCacheContext context)]
(-> cache-resp str keyword)))}]
(if (opt req :save-request)
(-> response
(assoc :request req)
(assoc-in [:request :body-type] (type body))
(assoc-in [:request :http-url] http-url)
(update-in [:request]
#(if (opt req :debug-body)
(assoc % :body-content
(cond
(isa? (type (:body %)) String)
(:body %)
(isa? (type (:body %)) HttpEntity)
(let [baos (ByteArrayOutputStream.)]
(.writeTo ^HttpEntity (:body %) baos)
(.toString baos "UTF-8"))
:else nil))
%))
(assoc-in [:request :http-req] http-req))
response)))
(defn- get-conn-mgr
[async? req]
(if async?
(or conn/*async-connection-manager*
(conn/make-regular-async-conn-manager req))
(or conn/*connection-manager*
(conn/make-regular-conn-manager req))))
(defn request
([req] (request req nil nil))
([{:keys [body connection-timeout connection-request-timeout connection-manager
cookie-store cookie-policy headers multipart query-string
redirect-strategy max-redirects retry-handler
request-method scheme server-name server-port socket-timeout
uri response-interceptor proxy-host proxy-port
http-client-context http-request-config http-client
proxy-ignore-hosts proxy-user proxy-pass digest-auth ntlm-auth
multipart-mode multipart-charset
; deprecated
conn-timeout conn-request-timeout]
:as req} respond raise]
(let [async? (opt req :async)
cache? (opt req :cache)
scheme (name scheme)
http-url (str scheme "://" server-name
(when server-port (str ":" server-port))
uri
(when query-string (str "?" query-string)))
conn-mgr (or connection-manager
(get-conn-mgr async? req))
proxy-ignore-hosts (or proxy-ignore-hosts
#{"localhost" "127.0.0.1"})
^RequestConfig request-config (or http-request-config
(request-config req))
^HttpClientContext context
(http-context cache? request-config http-client-context)
^HttpUriRequest http-req (http-request-for
request-method http-url body)]
(when-not (conn/reusable? conn-mgr)
(.addHeader http-req "Connection" "close"))
(when-let [cookie-jar (or cookie-store *cookie-store*)]
(.setCookieStore context cookie-jar))
(when-let [[user pass] digest-auth]
(.setCredentialsProvider
context
(doto (credentials-provider)
(.setCredentials (AuthScope. nil -1 nil)
(UsernamePasswordCredentials. user pass)))))
(when-let [[user password host domain] ntlm-auth]
(.setCredentialsProvider
context
(doto (credentials-provider)
(.setCredentials (AuthScope. nil -1 nil)
(NTCredentials. user password host domain)))))
(when (and proxy-user proxy-pass)
(let [authscope (AuthScope. proxy-host proxy-port)
creds (UsernamePasswordCredentials. proxy-user proxy-pass)]
(.setCredentialsProvider
context
(doto (credentials-provider)
(.setCredentials authscope creds)))))
(if multipart
(.setEntity ^HttpEntityEnclosingRequest http-req
(mp/create-multipart-entity multipart req))
(when (and body (instance? HttpEntityEnclosingRequest http-req))
(if (instance? HttpEntity body)
(.setEntity ^HttpEntityEnclosingRequest http-req body)
(.setEntity ^HttpEntityEnclosingRequest http-req
(if (string? body)
(StringEntity. ^String body "UTF-8")
(ByteArrayEntity. body))))))
(doseq [[header-n header-v] headers]
(if (coll? header-v)
(doseq [header-vth header-v]
(.addHeader http-req header-n header-vth))
(.addHeader http-req header-n (str header-v))))
(when (opt req :debug) (print-debug! req http-req))
(if-not async?
(let [^CloseableHttpClient client
(or http-client
(build-http-client req cache?
conn-mgr http-url proxy-ignore-hosts))]
(try
(build-response-map (.execute client http-req context)
req http-req http-url conn-mgr context client)
(catch Throwable t
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))
(throw t))))
(let [^CloseableHttpAsyncClient client
(or http-client
(build-async-http-client req conn-mgr http-url proxy-ignore-hosts))
original-thread-bindings (clojure.lang.Var/getThreadBindingFrame)]
(when cache?
(throw (IllegalArgumentException.
"caching is not yet supported for async clients")))
(.start client)
(.execute client http-req context
(reify org.apache.http.concurrent.FutureCallback
(failed [this ex]
(clojure.lang.Var/resetThreadBindingFrame original-thread-bindings)
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))
(if (opt req :ignore-unknown-host)
((:unknown-host-respond req) nil)
(raise ex)))
(completed [this resp]
(clojure.lang.Var/resetThreadBindingFrame original-thread-bindings)
(try
(respond (build-response-map
resp req http-req http-url
conn-mgr context client))
(catch Throwable t
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))
(raise t))))
(cancelled [this]
(clojure.lang.Var/resetThreadBindingFrame original-thread-bindings)
;; Run the :oncancel function if available
(when-let [oncancel (:oncancel req)]
(oncancel))
;; Attempt to abort the execution of the request
(.abort http-req)
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))))))))))
clj-http-3.12.3/src/clj_http/core_old.clj 0000664 0000000 0000000 00000033046 14070752310 0020210 0 ustar 00root root 0000000 0000000 (ns clj-http.core-old
"Core HTTP request/response implementation."
(:require [clj-http.conn-mgr :as conn]
[clj-http.headers :as headers]
[clj-http.multipart :as mp]
[clj-http.util :refer [opt]]
clojure.pprint)
(:import [java.io ByteArrayOutputStream FilterInputStream InputStream]
java.net.URI
[org.apache.http HeaderIterator HttpEntity HttpEntityEnclosingRequest HttpHost HttpResponseInterceptor]
[org.apache.http.auth AuthScope NTCredentials UsernamePasswordCredentials]
[org.apache.http.client HttpClient HttpRequestRetryHandler]
[org.apache.http.client.methods HttpDelete HttpEntityEnclosingRequestBase HttpGet HttpHead HttpOptions HttpPatch HttpPost HttpPut HttpUriRequest]
[org.apache.http.client.params ClientPNames CookiePolicy]
org.apache.http.conn.ClientConnectionManager
org.apache.http.conn.params.ConnRoutePNames
org.apache.http.conn.routing.HttpRoute
org.apache.http.cookie.CookieSpecFactory
org.apache.http.cookie.params.CookieSpecPNames
[org.apache.http.entity ByteArrayEntity StringEntity]
org.apache.http.impl.client.DefaultHttpClient
org.apache.http.impl.conn.ProxySelectorRoutePlanner
org.apache.http.impl.cookie.BrowserCompatSpec
org.apache.http.params.CoreConnectionPNames))
(defn parse-headers
"Takes a HeaderIterator and returns a map of names to values.
If a name appears more than once (like `set-cookie`) then the value
will be a vector containing the values in the order they appeared
in the headers."
[^HeaderIterator headers & [use-header-maps-in-response?]]
(if-not use-header-maps-in-response?
(->> (headers/header-iterator-seq headers)
(map (fn [[k v]]
[(.toLowerCase ^String k) v]))
(reduce (fn [hs [k v]]
(headers/assoc-join hs k v))
{}))
(->> (headers/header-iterator-seq headers)
(reduce (fn [hs [k v]]
(headers/assoc-join hs k v))
(headers/header-map)))))
(defn set-client-param [^HttpClient client key val]
(when-not (nil? val)
(-> client
(.getParams)
(.setParameter key val))))
(defn make-proxy-method-with-body
[method]
(fn [^String url]
(doto (proxy [HttpEntityEnclosingRequestBase] []
(getMethod [] (.toUpperCase (name method))))
(.setURI (URI. url)))))
(def proxy-delete-with-body (make-proxy-method-with-body :delete))
(def proxy-get-with-body (make-proxy-method-with-body :get))
(def proxy-copy-with-body (make-proxy-method-with-body :copy))
(def proxy-move-with-body (make-proxy-method-with-body :move))
(def proxy-patch-with-body (make-proxy-method-with-body :patch))
(def ^:dynamic *cookie-store* nil)
(defn- set-routing
"Use ProxySelectorRoutePlanner to choose proxy sensible based on
http.nonProxyHosts"
[^DefaultHttpClient client]
(.setRoutePlanner client
(ProxySelectorRoutePlanner.
(.. client getConnectionManager getSchemeRegistry) nil))
client)
(defn maybe-force-proxy [^DefaultHttpClient client
^HttpEntityEnclosingRequestBase request
proxy-host proxy-port proxy-ignore-hosts]
(let [uri (.getURI request)]
(when (and (nil? ((set proxy-ignore-hosts) (.getHost uri))) proxy-host)
(let [target (HttpHost. (.getHost uri) (.getPort uri) (.getScheme uri))
route (HttpRoute. target nil (HttpHost. ^String proxy-host
(int proxy-port))
(.. client getConnectionManager getSchemeRegistry
(getScheme target) isLayered))]
(set-client-param client ConnRoutePNames/FORCED_ROUTE route)))
request))
(defn cookie-spec
"Create an instance of a
org.apache.http.impl.cookie.BrowserCompatSpec with a validate
function that you pass in. This function takes two parameters, a
cookie and an origin."
[f]
(proxy [BrowserCompatSpec] []
(validate [cookie origin] (f cookie origin))))
(defn cookie-spec-factory
"Create an instance of a org.apache.http.cookie.CookieSpecFactory
with a newInstance implementation that returns a cookie
specification with a validate function that you pass in. The
function takes two parameters: cookie and origin."
[f]
(proxy
[CookieSpecFactory] []
(newInstance [params] (cookie-spec f))))
(defn add-client-params!
"Add various client params to the http-client object, if needed."
[^DefaultHttpClient http-client kvs]
(let [cookie-policy (:cookie-policy kvs)
cookie-policy-name (str (type cookie-policy))
kvs (dissoc kvs :cookie-policy)]
(when cookie-policy
(-> http-client
.getCookieSpecs
(.register cookie-policy-name (cookie-spec-factory cookie-policy))))
(doto http-client
(set-client-param ClientPNames/COOKIE_POLICY
(if cookie-policy
cookie-policy-name
CookiePolicy/BROWSER_COMPATIBILITY))
(set-client-param CookieSpecPNames/SINGLE_COOKIE_HEADER true)
(set-client-param ClientPNames/HANDLE_REDIRECTS false))
(doseq [[k v] kvs]
(set-client-param http-client
k (cond
(and (not= ClientPNames/CONN_MANAGER_TIMEOUT k)
(instance? Long v))
(Integer. ^Long v)
true v)))))
(defn- coerce-body-entity
"Coerce the http-entity from an HttpResponse to either a byte-array, or a
stream that closes itself and the connection manager when closed."
[{:keys [as]} ^HttpEntity http-entity ^ClientConnectionManager conn-mgr]
(if http-entity
(proxy [FilterInputStream]
[^InputStream (.getContent http-entity)]
(close []
(try
;; Eliminate the reflection warning from proxy-super
(let [^InputStream this this]
(proxy-super close))
(finally
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))))))
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))))
(defn- print-debug!
"Print out debugging information to *out* for a given request."
[{:keys [debug-body body] :as req} http-req]
(println "Request:" (type body))
(clojure.pprint/pprint
(assoc req
:body (if (opt req :debug-body)
(cond
(isa? (type body) String)
body
(isa? (type body) HttpEntity)
(let [baos (ByteArrayOutputStream.)]
(.writeTo ^HttpEntity body baos)
(.toString baos "UTF-8"))
:else nil)
(if (isa? (type body) String)
(format "... %s bytes ..."
(count body))
(and body (bean body))))
:body-type (type body)))
(println "HttpRequest:")
(clojure.pprint/pprint (bean http-req)))
(defn http-request-for
"Provides the HttpRequest object for a particular request-method and url"
[request-method ^String http-url body]
(case request-method
:get (if body
(proxy-get-with-body http-url)
(HttpGet. http-url))
:head (HttpHead. http-url)
:put (HttpPut. http-url)
:post (HttpPost. http-url)
:options (HttpOptions. http-url)
:delete (if body
(proxy-delete-with-body http-url)
(HttpDelete. http-url))
:copy (proxy-copy-with-body http-url)
:move (proxy-move-with-body http-url)
:patch (if body
(proxy-patch-with-body http-url)
(HttpPatch. http-url))
(throw (IllegalArgumentException.
(str "Invalid request method " request-method)))))
(defn request
"Executes the HTTP request corresponding to the given Ring request map and
returns the Ring response map corresponding to the resulting HTTP response.
Note that where Ring uses InputStreams for the request and response bodies,
the clj-http uses ByteArrays for the bodies."
[{:keys [request-method scheme server-name server-port uri query-string
headers body multipart socket-timeout connection-timeout proxy-host
proxy-ignore-hosts proxy-port proxy-user proxy-pass as cookie-store
retry-handler response-interceptor digest-auth ntlm-auth
connection-manager client-params
; deprecated
conn-timeout
]
:as req}]
(let [^ClientConnectionManager conn-mgr
(or connection-manager
conn/*connection-manager*
(conn/make-regular-conn-manager req))
^DefaultHttpClient http-client (set-routing
(DefaultHttpClient. conn-mgr))
scheme (name scheme)]
(when-let [cookie-store (or cookie-store *cookie-store*)]
(.setCookieStore http-client cookie-store))
(when retry-handler
(.setHttpRequestRetryHandler
http-client
(proxy [HttpRequestRetryHandler] []
(retryRequest [e cnt context]
(retry-handler e cnt context)))))
(add-client-params!
http-client
;; merge in map of specified timeouts, to
;; support backward compatibility.
(merge {CoreConnectionPNames/SO_TIMEOUT socket-timeout
CoreConnectionPNames/CONNECTION_TIMEOUT (or connection-timeout
conn-timeout)}
client-params))
(when-let [[user pass] digest-auth]
(.setCredentials
(.getCredentialsProvider http-client)
(AuthScope. nil -1 nil)
(UsernamePasswordCredentials. user pass)))
(when-let [[user password host domain] ntlm-auth]
(.setCredentials
(.getCredentialsProvider http-client)
(AuthScope. nil -1 nil)
(NTCredentials. user password host domain)))
(when (and proxy-user proxy-pass)
(let [authscope (AuthScope. proxy-host proxy-port)
creds (UsernamePasswordCredentials. proxy-user proxy-pass)]
(.setCredentials (.getCredentialsProvider http-client)
authscope creds)))
(let [http-url (str scheme "://" server-name
(when server-port (str ":" server-port))
uri
(when query-string (str "?" query-string)))
req (assoc req :http-url http-url)
proxy-ignore-hosts (or proxy-ignore-hosts
#{"localhost" "127.0.0.1"})
^HttpUriRequest http-req (maybe-force-proxy
http-client
(http-request-for request-method
http-url body)
proxy-host
proxy-port
proxy-ignore-hosts)]
(when response-interceptor
(.addResponseInterceptor
http-client
(proxy [HttpResponseInterceptor] []
(process [resp ctx]
(response-interceptor resp ctx)))))
(when-not (conn/reusable? conn-mgr)
(.addHeader http-req "Connection" "close"))
(doseq [[header-n header-v] headers]
(if (coll? header-v)
(doseq [header-vth header-v]
(.addHeader http-req header-n header-vth))
(.addHeader http-req header-n (str header-v))))
(if multipart
(.setEntity ^HttpEntityEnclosingRequest http-req
(mp/create-multipart-entity multipart req))
(when (and body (instance? HttpEntityEnclosingRequest http-req))
(if (instance? HttpEntity body)
(.setEntity ^HttpEntityEnclosingRequest http-req body)
(.setEntity ^HttpEntityEnclosingRequest http-req
(if (string? body)
(StringEntity. ^String body "UTF-8")
(ByteArrayEntity. body))))))
(when (opt req :debug) (print-debug! req http-req))
(try
(let [http-resp (.execute http-client http-req)
http-entity (.getEntity http-resp)
resp {:status (.getStatusCode (.getStatusLine http-resp))
:headers (parse-headers
(.headerIterator http-resp)
(opt req :use-header-maps-in-response))
:body (coerce-body-entity req http-entity conn-mgr)}]
(if (opt req :save-request)
(-> resp
(assoc :request req)
(assoc-in [:request :body-type] (type body))
(update-in [:request]
#(if (opt req :debug-body)
(assoc % :body-content
(cond
(isa? (type (:body %)) String)
(:body %)
(isa? (type (:body %)) HttpEntity)
(let [baos (ByteArrayOutputStream.)]
(.writeTo ^HttpEntity (:body %) baos)
(.toString baos "UTF-8"))
:else nil))
%))
(assoc-in [:request :http-req] http-req)
(dissoc :save-request?))
resp))
(catch Throwable e
(when-not (conn/reusable? conn-mgr)
(conn/shutdown-manager conn-mgr))
(throw e))))))
clj-http-3.12.3/src/clj_http/headers.clj 0000664 0000000 0000000 00000011325 14070752310 0020031 0 ustar 00root root 0000000 0000000 (ns clj-http.headers
"Provides wrap-header-map, which is middleware allows headers to be
specified more flexibly. In requests and responses, headers can be
accessed as strings or keywords of any case. In requests, string
header names will be sent to the server with their casing unchanged,
while keyword header names will be transformed into their canonical
HTTP representation (e.g. :accept-encoding will become
\"Accept-Encoding\")."
(:require [clojure.string :as s]
[potemkin :as potemkin])
(:import java.util.Locale
[org.apache.http Header HeaderIterator]))
(def special-cases
"A collection of HTTP headers that do not follow the normal
Looks-Like-This casing."
["Content-MD5"
"DNT"
"ETag"
"P3P"
"TE"
"WWW-Authenticate"
"X-ATT-DeviceId"
"X-UA-Compatible"
"X-WebKit-CSP"
"X-XSS-Protection"])
(defn special-case
"Returns the special-case capitalized version of a string if that
string is a special case, otherwise returns the string unchanged."
[^String s]
(or (first (filter #(.equalsIgnoreCase ^String % s) special-cases))
s))
(defn ^String lower-case
"Converts a string to all lower-case, using the root locale.
Warning: This is not a general purpose lower-casing function -- it
is useful for case-insensitive comparisons of strings, not for
converting a string into something that's useful for humans."
[^CharSequence s]
(when s
(.toLowerCase (.toString s) Locale/ROOT)))
(defn title-case
"Converts a character to titlecase."
[^Character c]
(when c
(Character/toTitleCase c)))
(defn canonicalize
"Transforms a keyword header name into its canonical string
representation.
The canonical string representation is title-cased words separated
by dashes, like so: :date -> \"Date\", :DATE -> \"Date\", and
:foo-bar -> \"Foo-Bar\".
However, there is special-casing for some common headers, so: :p3p
-> \"P3P\", and :content-md5 -> \"Content-MD5\"."
[k]
(when k
(-> (name k)
(lower-case)
(s/replace #"(?:^.|-.)"
(fn [s]
(if (next s)
(str (first s)
(title-case (second s)))
(str (title-case (first s))))))
(special-case))))
(defn normalize
"Turns a string or keyword into normalized form, which is a
lowercase string."
[k]
(when k
(lower-case (name k))))
(defn header-iterator-seq
"Takes a HeaderIterator and returns a seq of vectors of name/value
pairs of headers."
[^HeaderIterator headers]
(for [^Header h (iterator-seq headers)]
[(.getName h) (.getValue h)]))
(defn assoc-join
"Like assoc, but will join multiple values into a vector if the
given key is already present into the map."
[headers name value]
(update-in headers [name]
(fn [existing]
(cond (vector? existing)
(conj existing value)
(nil? existing)
value
:else
[existing value]))))
;; a map implementation that stores both the original (or canonical)
;; key and value for each key/value pair, but performs lookups and
;; other operations using the normalized -- this allows a value to be
;; looked up by many similar keys, and not just the exact precise key
;; it was originally stored with.
(potemkin/def-map-type HeaderMap [m mta]
(get [_ k v]
(second (get m (normalize k) [nil v])))
(assoc [_ k v]
(HeaderMap. (assoc m (normalize k) [(if (keyword? k)
(canonicalize k)
k)
v])
mta))
(dissoc [_ k]
(HeaderMap. (dissoc m (normalize k)) mta))
(keys [_]
(map first (vals m)))
(meta [_]
mta)
(with-meta [_ mta]
(HeaderMap. m mta))
clojure.lang.Associative
(containsKey [_ k]
(contains? m (normalize k)))
(entryAt [_ k]
(if (contains? m (normalize k))
(clojure.lang.MapEntry. k (get _ k))))
(empty [_]
(HeaderMap. {} nil)))
(defn header-map
"Returns a new header map with supplied mappings."
[& keyvals]
(into (HeaderMap. {} nil)
(apply array-map keyvals)))
(defn- header-map-request
[req]
(let [req-headers (:headers req)]
(if req-headers
(-> req (assoc :headers (into (header-map) req-headers)
:use-header-maps-in-response? true))
req)))
(defn wrap-header-map
"Middleware that converts headers from a map into a header-map."
[client]
(fn
([req]
(client (header-map-request req)))
([req respond raise]
(client (header-map-request req) respond raise))))
clj-http-3.12.3/src/clj_http/links.clj 0000664 0000000 0000000 00000003563 14070752310 0017543 0 ustar 00root root 0000000 0000000 (ns clj-http.links
"Namespace dealing with HTTP link headers")
(def ^:private quoted-string
#"\"((?:[^\"]|\\\")*)\"")
(def ^:private token
#"([^,\";]*)")
(def ^:private link-param
(re-pattern (str "(\\w+)=(?:" quoted-string "|" token ")")))
(def ^:private uri-reference
#"<([^>]*)>")
(def ^:private link-value
(re-pattern (str uri-reference "((?:\\s*;\\s*" link-param ")*)")))
(def ^:private link-header
(re-pattern (str "(?:\\s*(" link-value ")\\s*,?\\s*)")))
(defn read-link-params [params]
(into {}
(for [[_ name quot tok] (re-seq link-param params)]
[(keyword name) (or quot tok)])))
(defn read-link-value [value]
(let [[_ uri params] (re-matches link-value value)
param-map (read-link-params params)]
[(keyword (:rel param-map))
(-> param-map
(assoc :href uri)
(dissoc :rel))]))
(defn read-link-headers [header]
(->> (re-seq link-header header)
(map second)
(map read-link-value)
(into {})))
(defn- links-response
[response]
(if-let [link-headers (get-in response [:headers "link"])]
(let [link-headers (if (coll? link-headers)
link-headers
[link-headers])]
(assoc response
:links
(into {} (map read-link-headers link-headers))))
response))
(defn wrap-links
"Add a :links key to the response map that contains parsed Link headers. The
links will be represented as a map, with the 'rel' value being the key. The
URI is placed under the 'href' key, to mimic the HTML link element.
e.g. Link: ; rel=next; title=\"Page 2\"
=> {:links {:next {:href \"http://example.com/page2.html\"
:title \"Page 2\"}}}"
[client]
(fn
([request]
(links-response (client request)))
([request respond raise]
(client request #(respond (links-response %)) raise))))
clj-http-3.12.3/src/clj_http/multipart.clj 0000664 0000000 0000000 00000014064 14070752310 0020442 0 ustar 00root root 0000000 0000000 (ns clj-http.multipart
"Namespace used for clj-http to create multipart entities and bodies."
(:import [java.io File InputStream]
org.apache.http.Consts
org.apache.http.entity.ContentType
[org.apache.http.entity.mime HttpMultipartMode MultipartEntityBuilder]
[org.apache.http.entity.mime.content ByteArrayBody ContentBody FileBody InputStreamBody StringBody]))
;; we don't need to make a fake byte-array every time, only once
(def byte-array-type (type (byte-array 0)))
(defmulti
make-multipart-body
"Create a body object from the given map, dispatching on the type of its
content. By default supported content body types are:
- String
- byte array (requires providing name)
- InputStream (requires providing name)
- File
- org.apache.http.entity.mime.content.ContentBody (which is just returned)"
(fn [multipart] (type (:content multipart))))
(defmethod make-multipart-body nil
[multipart]
(throw (Exception. "Multipart content cannot be nil")))
(defmethod make-multipart-body :default
[multipart]
(throw (Exception. (str "Unsupported type for multipart content: "
(type (:content multipart))))))
(defmethod make-multipart-body File
;; Create a FileBody object from the given map, requiring at least :content
[{:keys [^String name ^String mime-type ^File content ^String encoding]}]
(cond
(and name mime-type content encoding)
(FileBody. content (ContentType/create mime-type encoding) name)
(and mime-type content encoding)
(FileBody. content (ContentType/create mime-type encoding))
(and name mime-type content)
(FileBody. content (ContentType/create mime-type) name)
(and mime-type content)
(FileBody. content (ContentType/create mime-type))
content
(FileBody. content)
:else
(throw (Exception. "Multipart file body must contain at least :content"))))
(defmethod make-multipart-body InputStream
;; Create an InputStreamBody object from the given map, requiring at least
;; :content and :name. If no :length is specified, clj-http will use
;; chunked transfer-encoding, if :length is specified, clj-http will
;; workaround things be proxying the InputStreamBody to return a length.
[{:keys [^String name ^String mime-type ^InputStream content length]}]
(cond
(and content name length)
(if mime-type
(proxy [InputStreamBody] [content (ContentType/create mime-type) name]
(getContentLength []
length))
(proxy [InputStreamBody] [content name]
(getContentLength []
length)))
(and content mime-type name)
(InputStreamBody. content (ContentType/create mime-type) name)
(and content name)
(InputStreamBody. content name)
:else
(throw (Exception. (str "Multipart input stream body must contain "
"at least :content and :name")))))
(defmethod make-multipart-body byte-array-type
;; Create a ByteArrayBody object from the given map, requiring at least
;; :content and :name.
[{:keys [^String name ^String mime-type ^bytes content]}]
(cond
(and content name mime-type)
(ByteArrayBody. content (ContentType/create mime-type) name)
(and content name)
(ByteArrayBody. content name)
:else
(throw (Exception. (str "Multipart byte array body must contain "
"at least :content and :name")))))
(defmulti ^java.nio.charset.Charset encoding-to-charset class)
(defmethod encoding-to-charset nil [encoding] nil)
(defmethod encoding-to-charset java.nio.charset.Charset [encoding] encoding)
(defmethod encoding-to-charset java.lang.String [encoding]
(java.nio.charset.Charset/forName encoding))
(defmethod make-multipart-body String
;; Create a StringBody object from the given map, requiring at least :content.
;; If :encoding is specified, it will be created using the Charset for that
;; encoding.
[{:keys [^String mime-type ^String content encoding]}]
(cond
(and content mime-type encoding)
(StringBody.
content (ContentType/create mime-type (encoding-to-charset encoding)))
(and content encoding)
(StringBody.
content (ContentType/create "text/plain" (encoding-to-charset encoding)))
content
(StringBody. content (ContentType/create "text/plain" Consts/UTF_8))))
(defmethod make-multipart-body ContentBody
;; Use provided org.apache.http.entity.mime.content.ContentBody directly
[{:keys [^ContentBody content]}]
content)
(defn- multipart-workaround
"Workaround for AsyncHttpClient to bypass 25kb restriction on getContent.
See https://github.com/dakrone/clj-http/issues/560.
"
[^org.apache.http.entity.mime.MultipartFormEntity mp-entity]
(reify org.apache.http.HttpEntity
(isRepeatable [_] (.isRepeatable mp-entity))
(isChunked [_] (.isChunked mp-entity))
(isStreaming [_] (.isStreaming mp-entity))
(getContentLength [_] (.getContentLength mp-entity))
(getContentType [_] (.getContentType mp-entity))
(getContentEncoding [_] (.getContentEncoding mp-entity))
(consumeContent [_] (.consumeContent mp-entity))
(getContent [_]
(let [os (java.io.ByteArrayOutputStream.)]
(.writeTo mp-entity os)
(.flush os)
(java.io.ByteArrayInputStream. (.toByteArray os))))
(writeTo [_ output-stream] (.writeTo mp-entity output-stream))))
(defn create-multipart-entity
"Takes a multipart vector of maps and creates a MultipartEntity with each
map added as a part, depending on the type of content."
[multipart {:keys [mime-subtype multipart-mode multipart-charset]
:or {mime-subtype "form-data"
multipart-mode HttpMultipartMode/STRICT}}]
(let [mp-entity (doto (MultipartEntityBuilder/create)
(.setMode multipart-mode)
(.setMimeSubtype mime-subtype))]
(when multipart-charset
(.setCharset mp-entity (encoding-to-charset multipart-charset)))
(doseq [m multipart]
(let [name (or (:part-name m) (:name m))
part (make-multipart-body m)]
(.addPart mp-entity name part)))
(multipart-workaround
(.build mp-entity))))
clj-http-3.12.3/src/clj_http/util.clj 0000664 0000000 0000000 00000012602 14070752310 0017372 0 ustar 00root root 0000000 0000000 (ns clj-http.util
"Helper functions for the HTTP client."
(:require [clojure.string :refer [blank? lower-case split trim]]
[clojure.walk :refer [postwalk]])
(:import [java.io BufferedInputStream ByteArrayInputStream ByteArrayOutputStream EOFException InputStream PushbackInputStream]
[java.net URLDecoder URLEncoder]
[java.util.zip DeflaterInputStream GZIPInputStream GZIPOutputStream InflaterInputStream]
org.apache.commons.codec.binary.Base64
org.apache.commons.io.IOUtils))
(defn utf8-bytes
"Returns the encoding's bytes corresponding to the given string. If no
encoding is specified, UTF-8 is used."
[^String s & [^String encoding]]
(.getBytes s (or encoding "UTF-8")))
(defn utf8-string
"Returns the String corresponding to the given encoding's decoding of the
given bytes. If no encoding is specified, UTF-8 is used."
[^"[B" b & [^String encoding]]
(String. b (or encoding "UTF-8")))
(defn url-decode
"Returns the form-url-decoded version of the given string, using either a
specified encoding or UTF-8 by default."
[^String encoded & [^String encoding]]
(URLDecoder/decode encoded (or encoding "UTF-8")))
(defn url-encode
"Returns an UTF-8 URL encoded version of the given string."
[^String unencoded & [^String encoding]]
(URLEncoder/encode unencoded (or encoding "UTF-8")))
(defn base64-encode
"Encode an array of bytes into a base64 encoded string."
[unencoded]
(utf8-string (Base64/encodeBase64 unencoded)))
(defn gunzip
"Returns a gunzip'd version of the given byte array."
[b]
(when b
(cond
(instance? InputStream b)
(let [^PushbackInputStream b (PushbackInputStream. b)
first-byte (int (try (.read b) (catch EOFException _ -1)))]
(case first-byte
-1 b
(do (.unread b first-byte)
(GZIPInputStream. b))))
:else
(IOUtils/toByteArray (GZIPInputStream. (ByteArrayInputStream. b))))))
(defn gzip
"Returns a gzip'd version of the given byte array."
[b]
(when b
(let [baos (ByteArrayOutputStream.)
gos (GZIPOutputStream. baos)]
(IOUtils/copy (ByteArrayInputStream. b) gos)
(.close gos)
(.toByteArray baos))))
(defn force-stream
"Force b as InputStream if it is a ByteArray."
^InputStream [b]
(if (instance? InputStream b)
b
(ByteArrayInputStream. b)))
(defn force-byte-array
"force b as byte array if it is an InputStream, also close the stream"
^bytes [b]
(if (instance? InputStream b)
(let [^PushbackInputStream bs (PushbackInputStream. b)]
(try
(let [first-byte (int (try (.read bs) (catch EOFException _ -1)))]
(case first-byte
-1 (byte-array 0)
(do (.unread bs first-byte)
(IOUtils/toByteArray bs))))
(finally (.close bs))))
b))
(defn force-string
"Convert s (a ByteArray or InputStream) to String."
^String [s ^String charset]
(if (instance? InputStream s)
(let [^PushbackInputStream bs (PushbackInputStream. s)]
(try
(let [first-byte (int (try (.read bs) (catch EOFException _ -1)))]
(case first-byte
-1 ""
(do (.unread bs first-byte)
(IOUtils/toString bs charset))))
(finally (.close bs))))
(IOUtils/toString ^"[B" s charset)))
(defn inflate
"Returns a zlib inflate'd version of the given byte array or InputStream."
[b]
(when b
;; This weirdness is because HTTP servers lie about what kind of deflation
;; they're using, so we try one way, then if that doesn't work, reset and
;; try the other way
(let [stream (BufferedInputStream. (if (instance? InputStream b)
b
(ByteArrayInputStream. b)))
_ (.mark stream 512)
iis (InflaterInputStream. stream)
readable? (try (.read iis) true
(catch java.util.zip.ZipException _ false))]
(.reset stream)
(if readable?
(InflaterInputStream. stream)
(InflaterInputStream. stream (java.util.zip.Inflater. true))))))
(defn deflate
"Returns a deflate'd version of the given byte array."
[b]
(when b
(IOUtils/toByteArray (DeflaterInputStream. (ByteArrayInputStream. b)))))
(defn lower-case-keys
"Recursively lower-case all map keys that are strings."
[m]
(let [f (fn [[k v]] (if (string? k) [(lower-case k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn opt
"Check the request parameters for a keyword boolean option, with or without
the ?
Returns false if either of the values are false, or the value of
(or key1 key2) otherwise (truthy)"
[req param]
(let [param-? (keyword (str (name param) "?"))
v1 (clojure.core/get req param)
v2 (clojure.core/get req param-?)]
(if (false? v1)
false
(if (false? v2)
false
(or v1 v2)))))
(defn- trim-quotes [s]
(clojure.string/replace s #"^\s*(\"(.*)\"|(.*?))\s*$" "$2$3"))
(defn parse-content-type
"Parse `s` as an RFC 2616 media type."
[s]
(when-let [m (re-matches #"\s*(([^/]+)/([^ ;]+))\s*(\s*;.*)?" (str s))]
{:content-type (keyword (nth m 1))
:content-type-params
(->> (split (str (nth m 4)) #"\s*;\s*")
(remove blank?)
(map #(split % #"="))
(mapcat (fn [[k v]] [(keyword (lower-case k)) (trim-quotes v)]))
(apply hash-map))}))
clj-http-3.12.3/test-resources/ 0000775 0000000 0000000 00000000000 14070752310 0016313 5 ustar 00root root 0000000 0000000 clj-http-3.12.3/test-resources/big_array_json.json 0000664 0000000 0000000 00000013417 14070752310 0022204 0 ustar 00root root 0000000 0000000 [
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]},
{"foo": "bar", "baz": "qux", "values": [1, 2, 3, 4, 5]}
]
clj-http-3.12.3/test-resources/client-keystore 0000664 0000000 0000000 00000006607 14070752310 0021370 0 ustar 00root root 0000000 0000000 0
0
< *H
-
)0
%0i *H
ZV0R0N*H
00)
*H
0:-QM~]cXOenPR PM Hݝx/ NsHRM]gJE>ҶK.;'{)r~|lJ`7F# FinǢ̶B|J9Ggk;,7*K:NG:"^NMY#E很Uz~V(-PؑE
LEUĻNL%?G!1~gguj~Vzw]dqڱl
Wf$Cq@dKÙ%$9d#V{!UQ)ZUi\ihN99G$o3+[&k/bkrftͱH;eRe SӁߢ^$XA"W,8}9,5>}㼬oדȀo7?TX=4D "Ni͇'~zq`d㞟{B4z