class MyAuthHttpClient {
private token;
public MyAuthHttpClient(String usr, String psw) {...}
public ... getResponse(Url) {
// here check if token is available and if it is expiring;
// if expiring -> fetch a new token before call the http service
// caller doesn't even know there's a token involved in the process
}
}
(defn gettkn [usr, psw, tkn] (return a new token if tkn is expiring or tkn if not expiring))
(def wrap-gettkn (partial gettkn "myuser" "mypass"))
(defn geturl [url, tkn] client/get url {:oauth-token (wrap-gettkn tkn)})
I can "save" usr and psw, but I always have to "keep" the tkn around at every level;
while I would like the token to be "hidden" to the "geturl" clients (just like I did in the "private token" in the pseudo-Java).
What's the proper way of doing something like this in Clojure?
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/6aa66613-24d9-4ebc-87d4-e9a6cca05165%40googlegroups.com.
(defmethod ig/init-key ::get-google-keys [_ _] (let [gkeys (ref {:keys nil :expires 0})] (fn [] (let [current @gkeys] (:keys (if (>= (c/to-epoch (t/now)) (:expires current)) (do (log/info "Fetching Google Keys, expiry: " (:expires current)) (dosync (ref-set gkeys (fetch-google-keys ::jwk)))) ;; only use JWK for now current))))))
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clo...@googlegroups.com.
It's just an idea, but I think this might work better than trying to convert existing oop code.
(defrecord MyAuthHttpClient [token user psw])
(defn my-auth-http-client [usr psw]
(let [token (atom nil)]
(maybe-update-token token usr psw)
(->MyAuthHttpClient token usr psw)))
(defn get-response [client url]
(maybe-update-token (:token client) (:user client) (:psw client))
(let [token @(:token client)]
(do-request token url)))
(defrecord MyAuthHttpClient [token refresh-token])
(defn my-auth-http-client [usr psw]
(let [refresh-token (fn [t] (maybe-update-token t usr psw))
token (atom nil)]
(refresh-token token)
(->MyAuthHttpClient token refresh-token)))
(defn get-response [client url]
((:refresh-token client) (:token client))
(let [token @(:token client)]
(do-request token url)))
(defprotocol Client
(get-response [this url] "Perform an HTTP GET against `url`."))
(defrecord NoAuthClient []
Client
(get-response [this url] (slurp url)))
;; E.g.
(get-response (->NoAuthClient) "https://blockchain.info/stats?format=json")
;=> "{\"timestamp\":1.589365823E12,\"market_price_usd\":8920.2,..."
(defrecord MyAuthHttpClient [token refresh-token]
(get-response [this url]
(refresh-token token)
(do-request token url)))
(defprotocol Client
(request [this request-data]
"Build a request and return `this`.")
(response [this response-data]
"Return a possibly updated `this` for a response to one of the
client's requests.")
(consume-requests [this]
"Consume all queued requests, return [this requests]."))
;; The NoAuthClient doesnt do much...
(defrecord NoAuthClient [queued]
Client
(request [this request-data] (assoc this :queued (conj queued request-data)))
(response [this _] this)
(consume-requests [this] [(assoc this :queued []) queued]))
;; Your token auth client, on the other hand...
(defrecord MyAuthHttpClient
[token refresh-token requests queued-for-after-refresh]
Client
(request [this request-data]
;; It is not actually making any requests to refresh, just
;; figuring out _what_ needs to be done, and letting the calling
;; code take care of _how_ to do it (via clj-http?, aleph?, right
;; away?, later?, etc)
(if (token-expired-given-current-time? token (:ts request-data))
(-> this
(update :queued-for-after-refresh conj request-data)
(update :requests conj (refresh-token token)))
(update this :requests conj request-data)))
(response [this response-data]
(if (is-token-refresh-response? response-data)
(let [new-token (parse-new-token response-data)
;; Any requests that were awaiting a new token are
;; now ready, but probably need to have the token
;; included
insert (map #(assoc % :token new-token))
requests (into requests insert queued-for-after-refresh)]
(assoc this :token new-token
:requests requests
:queued-for-after-refresh []))
this))
(consume-requests [this]
[(assoc this :requests []) requests]))
The most similar thing you could do to your Java code would be keepingthe token in an atom inside of a connection record.
(defonce user-tokens (atom {:users {}))
(defn get-updated-token [usr pwd tok]
;; Returns tok if still valid or a new token
)
(defn get-token [usr, psw]
(let [f (fn f [tok]
[tok, (fn [] (f (get-updated-token usr psw tok)))])
path [:users usr]
thunk (or (get-in @user-tokens path) (f nil))
[token thunk'] (thunk)]
(do (swap! user-tokens assoc-in path thunk')
token)))
(defn geturl [url]
(client/get url {:oauth-token (get-token "user" "pwd")}))
(defn fact-imp [n]
(let [result (atom 1)]
(dotimes [i n]
(swap! result * (inc i)))
@result))
(defn fact-rec [n]
(if (<= n 0)
1
(* n (fact-rec (dec n)))))
(defn fact-lazy [n]
(letfn [(next-step [[i x]] [(inc i) (* x (inc i))])
(fact-seq [pair] (lazy-seq (cons pair (fact-seq (next-step pair)))))]
(second (nth (fact-seq [0 1]) n))))
(def state (atom 0))
(defn get-token
"Somehow get an OAuth token for this user+pass combination."
[user pass]
{:token "some-token" ; replace this with something real
:expires (+ (System/currentTimeMillis) 1000000)})
(defn expired?
"Returns true if the token has expired."
[token]
(> (System/currentTimeMillis) (:expires token)))
(defn make-http-client
"Return a closure that retains the token associated with this
user+pass combination and can make HTTP requests using it."
[user pass]
(let [token (atom (get-token user pass))]
(fn [url]
(when (expired? @token)
(reset! token (get-token user pass)))
(client/get url {:oauth-token (:token @token)}))))
(def get-response (make-http-client "my-user" "my-pass"))
(get-response "some-url")
(get-response "another-url")
(get-response "yet-another-url")
(get-response "even-another-url")
(hack :the-planet)
(http/request
{:method :get
:url "https://example.com/"
:oauth-token (revise-oauth-token token-store)})
(defn revise-oauth-token [token-store]
(:access_token
@(swap! token-store
(fn [token-promise]
(if (token-needs-refresh? @token-promise (Instant/now))
(delay (refresh-oauth-token (:refresh_token @token-promise)))
token-promise)))))
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/Vur5Lol45EE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clojure/CAGokn9L_Od2ZN2LJAsYUfJ2G_hbLKkamkUxgFX2vTKySxpHQWg%40mail.gmail.com.
Hi, it's a long time that this question was posted, but I have found it interesting in the implementation of token refreshes.First of all, for service invocation, given a `revise-oauth-token` method, I think this is good client code:
(http/request
{:method :get
:url "https://example.com/"
:oauth-token (revise-oauth-token token-store)})If you find it too repetitive or fragile in your client code, you can make a local function, but I wouldn't abstract the service invocation at a higher layer.Regarding the implementation of the token store, we could initially think of a synchronized store, like an atom, and `revise-oauth-token` would swap its content when a refresh is required. This is inconvenient for multithreaded clients, because there could be several refresh invocations going on concurrently.In order to avoid concurrent refreshes, I propose to implement the token store as an atom of promises. Implementation of `revise-oauth-token` would be:
(defn revise-oauth-token [token-store]
(:access_token
@(swap! token-store
(fn [token-promise]
(if (token-needs-refresh? @token-promise (Instant/now))
(delay (refresh-oauth-token (:refresh_token @token-promise)))
token-promise)))))Note that using a delay avoids running `refresh-oauth-token` within the `swap!` operation, as this operation may be run multiple times.Also note that `token-needs-refresh` takes an argument with the present time. This keeps the function pure, which could help for unit testing, for example.There is an alternative implementation using `compare-and-set!` that avoids checking `token-needs-refresh?` several times, but it is more complicated. I have posted full sample code in a gist: https://gist.github.com/titogarcia/4f09bcc5fa38fbdc1076954b9a99a8fc