3 [clojure.java.io :as io])
6 (com.omarpolo.gemini Request)))
9 (set! *warn-on-reflection* true)
16 "Human description for every response code."
20 30 "temporary redirect"
21 31 "permanent redirect"
22 40 "temporary failure"
23 41 "server unavailable"
27 50 "permanent failure"
30 53 "proxy request refused"
32 60 "client certificate required"
33 61 "certificate not authorized"
34 62 "certificate not valid"})
36 (defn is-input? [{c :code}] (= 1 (quot c 10)))
37 (defn is-success? [{c :code}] (= 2 (quot c 10)))
38 (defn is-redirect? [{c :code}] (= 3 (quot c 10)))
39 (defn is-temporary-failure? [{c :code}] (= 4 (quot c 10)))
40 (defn is-permanent-failure? [{c :code}] (= 5 (quot c 10)))
41 (defn is-client-cert-required? [{c :code}] (= 6 (quot c 10)))
45 (defn- parse-params [{:keys [proxy request follow-redirects?]}]
46 (when (and (:host proxy)
48 (throw (ex-info "invalid proxy definition" {:got proxy
49 :reason "missing proxy host"})))
53 (throw (ex-info ":request is nil" {})))
54 :follow-redirects? (case follow-redirects?
60 (defn- resolve-uri [request meta]
61 (let [uri (URI. request)
63 (str (.resolve uri rel))))
65 (defn- fetch' [host port uri]
68 host (Request. ^String host ^int port ^String uri)
69 :else (Request. ^String uri))]
79 "Make a gemini request. `params` is a map with the following
80 keys (only `:request` is mandatory):
82 - `:proxy`: a map of `:host` and `:port`, identifies the server to
83 send the requests to. This allows to use a gemini server as a
84 proxy, it doesn't do any other kind of proxying (e.g. SOCK5.)
86 - `:request` the URI (as string) to require.
88 - `:follow-redirects?` if `false` or `nil` don't follow redirects,
89 if `true` follow up to 5 redirects, or the number of redirects to
92 If the returned map contains an `:error` key the request failed for
93 the specified reason. Otherwise, the map contains the following keys:
95 - `:uri`: the URI of the request. May be different from the
96 requested one if `:follow-redirects?` was specified.
98 - `:request`: the object backing the request.
100 - `:code` and `:meta` are the parsed header response.
102 - `:body` an instance of a `BufferedReader`. Note: closing the body
103 is not enugh, always call `clase` on the returned map.
105 - `:redirected?` true if a redirect was followed."
107 (let [{:keys [host port request follow-redirects?] :as orig} (parse-params params)]
108 (loop [n follow-redirects?
111 (let [res (fetch' host port request)
112 redirect? (and (not (:error res))
116 (= follow-redirects? 0) res
118 redirect?) (do (.close ^Request (:request res))
119 (throw (ex-info "too many redirects"
121 :redirects follow-redirects?
123 :meta (:meta res)})))
124 redirect? (do (.close ^Request (:request res))
126 (resolve-uri request (:meta res))
128 :else (assoc res :redirected? redirected?))))))
130 (defn body-as-string!
131 "Read all the response into a strings and returns it. The request
134 (let [sw (java.io.StringWriter.)]
135 (with-open [r ^Request r]
136 (io/copy (.body r) sw)
144 (defmacro with-request
145 "Make a request, eval `body` when it succeed and automatically close
146 the request, or throw an exception if the request fails."
148 `(let [~var (fetch ~spec)]
149 (when-let [e# (:error ~var)]
151 (with-open [req# ^Request (:request ~var)]