commit - 958b98ffc8f0bd8a11fbf894dfced6fb3914c0a2
commit + 0c76ea8b82b9d5f72359186f2321d4257a917cb9
blob - 433d9d6f88de57d8f81b651ec51f38fe0d2de6db
blob + d6893054dd424cf0413db01feac1c84bf0d1594f
--- src/gemini/core.clj
+++ src/gemini/core.clj
(:require
[clojure.java.io :as io])
(:import
+ (java.net URI)
(com.omarpolo.gemini Request)))
-(defmacro ^:private request->map [& args]
- `(try
- (let [req# (Request. ~@args)]
- {:request req#
- :code (.getCode req#)
- :meta (.getMeta req#)
- :body (.body req#)})
- (catch Throwable e#
- {:error e#})))
+(comment
+ (set! *warn-on-reflection* true)
+)
-(defn fetch
- "Make a gemini request. `uri` may be a URI, URL or string, and
- represent the request to perform. `host` and `port` are extracted
- from the given `uri` in not given, and port defaults to 1965. The
- returned request needs to be closed when done."
- ([uri]
- (request->map uri))
- ([host uri]
- (fetch host 1965 uri))
- ([host port uri]
- (request->map host port uri)))
-
-(defn body-as-string!
- "Read all the response into a strings and returns it. The request
- will be closed."
- [{r :request}]
- (let [sw (java.io.StringWriter.)]
- (with-open [r r]
- (io/copy (.body r) sw)
- (.toString sw))))
-
-(defn close
- "Close a request."
- [{r :request}]
- (.close r))
-
-(defmacro with-request
- "Make a request, eval `body` when it succeed and automatically close
- the request, or throw an exception if the request fails."
- [[var req] & body]
- `(let [~var ~req]
- (when-let [e# (:error ~var)]
- (throw e#))
- (with-open [req# (:request ~var)]
- ~@body)))
-
;; helpers
(defn is-temporary-failure? [{c :code}] (= 4 (/ c 10)))
(defn is-permanent-failure? [{c :code}] (= 5 (/ c 10)))
(defn is-client-cert-required? [{c :code}] (= 6 (/ c 10)))
+
+
+
+(defn- parse-params [{:keys [proxy request follow-redirects?]}]
+ (when (and (:host proxy)
+ (not (:port proxy)))
+ (throw (ex-info "invalid proxy definition" {:got proxy
+ :reason "missing proxy host"})))
+ {:host (:host proxy)
+ :port (:port proxy)
+ :request (or request
+ (throw (ex-info ":request is nil" {})))
+ :follow-redirects? (case follow-redirects?
+ nil 0
+ false 0
+ true 5
+ follow-redirects?)})
+
+(defn- resolve-uri [request meta]
+ (let [uri (URI. request)
+ rel (URI. meta)]
+ (str (.resolve uri rel))))
+
+(defn- fetch' [host port uri]
+ (try
+ (let [req (cond
+ host (Request. ^String host ^int port ^String uri)
+ :else (Request. ^String uri))]
+ {:uri uri
+ :request req
+ :code (.getCode req)
+ :meta (.getMeta req)
+ :body (.body req)})
+ (catch Throwable e
+ {:error e})))
+
+(defn fetch
+ "Make a gemini request. `params` is a map with the following
+ keys (only `:request` is mandatory):
+
+ - `:proxy`: a map of `:host` and `:port`, identifies the server to
+ send the requests to. This allows to use a gemini server as a
+ proxy, it doesn't do any other kind of proxying (e.g. SOCK5.)
+
+ - `:request` the URI (as string) to require.
+
+ - `:follow-redirects?` if `false` or `nil` don't follow redirects,
+ if `true` follow up to 5 redirects, or the number of redirects to
+ follow.
+
+ Return a map with `:request`, `:code`, `:meta`, `:body` on success
+ or `:error` on failure. The request needs to be closed when done
+ usign `close`."
+ [params]
+ (let [{:keys [host port request follow-redirects?] :as orig} (parse-params params)]
+ (loop [n follow-redirects?
+ request request
+ redirected? false]
+ (let [res (fetch' host port request)
+ redirect? (and (not (:error res))
+ (is-redirect? res))]
+ (cond
+ (:error res) res
+ (= follow-redirects? 0) res
+ (and (= 0 n)
+ redirect?) (do (.close ^Request (:request res))
+ (throw (ex-info "too many redirects"
+ {:original orig
+ :redirects follow-redirects?
+ :code (:code res)
+ :meta (:meta res)})))
+ redirect? (do (.close ^Request (:request res))
+ (recur (dec n)
+ (resolve-uri request (:meta res))
+ true))
+ :else res)))))
+
+(defn body-as-string!
+ "Read all the response into a strings and returns it. The request
+ will be closed."
+ [{r :request}]
+ (let [sw (java.io.StringWriter.)]
+ (with-open [r ^Request r]
+ (io/copy (.body r) sw)
+ (.toString sw))))
+
+(defn close
+ "Close a request."
+ [{r :request}]
+ (.close ^Request r))
+
+(defmacro with-request
+ "Make a request, eval `body` when it succeed and automatically close
+ the request, or throw an exception if the request fails."
+ [[var req] & body]
+ `(let [~var ~req]
+ (when-let [e# (:error ~var)]
+ (throw e#))
+ (with-open [req# (:request ~var)]
+ ~@body)))