Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Finishing touches on authentication, getting ready for next release

and a name change.
  • Loading branch information...
commit 7c2f820550e693ab3ce0805c2edf9afd6fa79118 1 parent df8e02b
@neotyk neotyk authored
View
24 docs.org
@@ -223,6 +223,7 @@
- [[*%20body][:body]] :: body to be sent, allowed only with PUT/POST
- [[*%20cookies][:cookies]] :: cookies to be sent
- [[*%20proxy][:proxy]] :: proxy to be used
+ - [[*%20auth][:auth]] :: authentication map
**** :query
Query parameters is a map of keywords and their values. You use
it like so:
@@ -285,6 +286,29 @@
Proxy expects a map with following keys:
- *:host* :: proxy host
- *:port* :: proxy port
+**** :auth
+ Authentication can be configured per request basis.
+ For now BASIC and DIGEST methods are supported.
+
+ Basic method is default, so you don't have to specify it:
+#+BEGIN_SRC clojure
+ (let [resp (c/GET url :auth {:user u :password p})]
+ ;; Check if response is not 401 or so and process response
+ )
+#+END_SRC
+ Though you can:
+#+BEGIN_SRC clojure
+ (let [resp (c/GET url :auth {:type :basic :user u :password p})]
+ ;; Check if response is not 401 or so and process response
+ )
+#+END_SRC
+ And for digest method you will need realm as well:
+#+BEGIN_SRC clojure
+ (let [resp (c/GET url
+ :auth {:type :digest :user u :password p :realm r})]
+ ;; Check if response is not 401 or so and process response
+ )
+#+END_SRC
*** Streaming
HTTP Stream is response with chunked content encoding.
Those streams might not be meant to ever finish, see twitter.com
View
6 project.clj
@@ -1,6 +1,6 @@
-(defproject ahc-clj "0.2.0-SNAPSHOT"
- :description "Async Http Client for Clojure"
- :namespaces [async.http.client]
+(defproject http.async.client "0.2.0-SNAPSHOT"
+ :description "Asynchronous HTTP Client for Clojure"
+ :namespaces [http.async.client]
:dependencies [[org.clojure/clojure "1.2.0"]
[org.clojure/clojure-contrib "1.2.0"]
[com.ning/async-http-client "1.1.0"]]
View
4 src/ahc/RequestBuilderWrapper.java
@@ -1,5 +1,6 @@
package ahc;
import java.io.InputStream;
+import com.ning.http.client.Realm;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.ProxyServer;
import com.ning.http.client.Cookie;
@@ -30,4 +31,7 @@ public RequestBuilderWrapper setProxyServer(ProxyServer proxy) {
public RequestBuilderWrapper addCookie(Cookie cookie) {
rb.addCookie(cookie);
return this;}
+ public RequestBuilderWrapper setRealm(Realm realm) {
+ rb.setRealm(realm);
+ return this;}
public RequestBuilder getRequestBuilder() { return rb; }}
View
43 src/http/async/client/request.clj
@@ -25,7 +25,9 @@
AsyncHandler Cookie
FluentCaseInsensitiveStringsMap
HttpResponseStatus HttpResponseHeaders
- HttpResponseBodyPart Request RequestBuilder
+ HttpResponseBodyPart
+ Realm$RealmBuilder Realm$AuthScheme
+ Request RequestBuilder
RequestType ProxyServer)
(ahc RequestBuilderWrapper)
(java.net URLEncoder)
@@ -112,21 +114,29 @@
:headers - map of headers
:body - body
:cookies - cookies to send
- :proxy - map with proxy configuration to be used (:host and :port)"
+ :proxy - map with proxy configuration to be used (:host and :port)
+ :auth - map with authentication to be used
+ :type - either :basic or :digest
+ :user - user name to be used
+ :password - password to be used
+ :realm - realm name to authenticate in"
{:tag Request}
[method #^String url & {headers :headers
query :query
body :body
cookies :cookies
- proxy :proxy}]
+ proxy :proxy
+ auth :auth}]
;; RequestBuilderWrapper is needed for now, until RequestBuilder
;; is able to be used directly from Clojure.
(let [#^RequestBuilderWrapper rbw
(RequestBuilderWrapper.
(RequestBuilder. (convert-method method)))]
+ ;; headers
(doseq [[k v] headers] (.addHeader rbw
(if (keyword? k) (name k) k)
(str v)))
+ ;; cookies
(doseq [{domain :domain
name :name
value :value
@@ -137,9 +147,11 @@
max-age 30
secure false}} cookies]
(.addCookie rbw (Cookie. domain name value path max-age secure)))
+ ;; query parameters
(doseq [[k v] query] (.addQueryParameter rbw
(if (keyword? k) (name k) k)
(str v)))
+ ;; message body
(cond
(map? body) (doseq [[k v] body]
(.addParameter rbw
@@ -147,8 +159,33 @@
(str v)))
(string? body) (.setBody rbw (.getBytes (url-encode body) "UTF-8"))
(instance? InputStream body) (.setBody rbw body))
+ ;; authentication
+ (when-let [{type :type
+ user :user
+ password :password
+ realm :realm
+ :or {:type :basic}} auth]
+ (let [rbld (Realm$RealmBuilder.)]
+ (if (nil? user)
+ (if (nil? password)
+ (throw (IllegalArgumentException. "For authentication user and password is required"))
+ (throw (IllegalArgumentException. "For authentication user is required"))))
+ (if (nil? password)
+ (throw (IllegalArgumentException. "For authentication password is required")))
+ (if (= :digest type)
+ (do
+ (if (nil? realm) (throw (IllegalArgumentException.
+ "For DIGEST authentication realm is required")))
+ (.setRealmName rbld realm)
+ (.setScheme rbld Realm$AuthScheme/DIGEST)))
+ (doto rbld
+ (.setPrincipal user)
+ (.setPassword password))
+ (.setRealm rbw (.build rbld))))
+ ;; proxy
(if proxy
(.setProxyServer rbw (ProxyServer. (:host proxy) (:port proxy))))
+ ;; fine
(.. (.getRequestBuilder rbw) (setUrl url) (build))))
(defn convert-action
View
30 test/http/async/client/test.clj
@@ -85,6 +85,12 @@
(doseq [c (.getCookies hReq)]
(.addCookie hResp c)))
"/branding" (.setHeader hResp "X-User-Agent" (.getHeader hReq "User-Agent"))
+ "/basic-auth" (let [auth (.getHeader hReq "Authorization")]
+ (.setStatus
+ hResp
+ (if (= auth "Basic YmVhc3RpZTpib3lz")
+ 200
+ 401)))
(doseq [n (enumeration-seq (.getParameterNames hReq))]
(doseq [v (.getParameterValues hReq n)]
(.addHeader hResp n v))))
@@ -322,6 +328,30 @@
(is (= (.getMessage (error resp)) "Connection refused to http://notexisting/"))
(is (true? (failed? resp)))))
+(deftest no-real-for-digest
+ (is (thrown-with-msg? IllegalArgumentException #"For DIGEST authentication realm is required"
+ (GET "http://not-important/"
+ :auth {:type :digest
+ :user "user"
+ :password "secret"}))))
+
+(deftest authentication-without-user-or-password
+ (is (thrown-with-msg? IllegalArgumentException #"For authentication user is required"
+ (GET "http://not-important/"
+ :auth {:password "secret"})))
+ (is (thrown-with-msg? IllegalArgumentException #"For authentication password is required"
+ (GET "http://not-important/"
+ :auth {:user "user"})))
+ (is (thrown-with-msg? IllegalArgumentException #"For authentication user and password is required"
+ (GET "http://not-important/"
+ :auth {:type :basic}))))
+
+(deftest basic-authentication
+ (is (=
+ (:code (status (GET "http://localhost:8123/basic-auth"
+ :auth {:user "beastie"
+ :password "boys"})))
+ 200)))
;;(deftest profile-get-stream
;; (let [gets (repeat (GET "http://localhost:8123/stream"))
;; seqs (repeat (stream-seq :get "http://localhost:8123/stream"))
View
5 todo.org
@@ -12,7 +12,7 @@
** DONE Implement response *promise* :rel1:
** DONE Consume stream :rel1:
** DONE Error function :rel1:
-* Send [5/9] :code:
+* Send [6/9] :code:
** DONE Query parameters :rel1:
** DONE Parameters :rel1:
** DONE Parameters allowed only on PUT/POST :rel1:
@@ -36,7 +36,8 @@
AHC, or alternatively will *clojure.core/send-off* to execute
IO operation in separate thread.
** TODO Multipart :rel4:
-** TODO Authentication :rel3:
+** DONE Authentication :rel3:
+*** DONE Test Authentication
* Configuration [0/2]
** TODO Global configuration of AHC [1/4] :rel4:
*** DONE Branding User-agent :rel3:
Please sign in to comment.
Something went wrong with that request. Please try again.