Blob


1 (ns gemini.core
2 (:require
3 [clojure.java.io :as io])
4 (:import
5 (java.net URI)
6 (com.omarpolo.gemini Request)))
8 (comment
9 (set! *warn-on-reflection* true)
10 )
13 ;; helpers
15 (def code-description
16 "Human description for every response code."
17 {10 "input"
18 11 "sensitive input"
19 20 "success"
20 30 "temporary redirect"
21 31 "permanent redirect"
22 40 "temporary failure"
23 41 "server unavailable"
24 42 "CGI error"
25 43 "proxy error"
26 44 "slow down"
27 50 "permanent failure"
28 51 "not found"
29 52 "gone"
30 53 "proxy request refused"
31 59 "bad request"
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)
47 (not (:port proxy)))
48 (throw (ex-info "invalid proxy definition" {:got proxy
49 :reason "missing proxy host"})))
50 {:host (:host proxy)
51 :port (:port proxy)
52 :request (or request
53 (throw (ex-info ":request is nil" {})))
54 :follow-redirects? (case follow-redirects?
55 nil 0
56 false 0
57 true 5
58 follow-redirects?)})
60 (defn- resolve-uri [request meta]
61 (let [uri (URI. request)
62 rel (URI. meta)]
63 (str (.resolve uri rel))))
65 (defn- fetch' [host port uri]
66 (try
67 (let [req (cond
68 host (Request. ^String host ^int port ^String uri)
69 :else (Request. ^String uri))]
70 {:uri uri
71 :request req
72 :code (.getCode req)
73 :meta (.getMeta req)
74 :body (.body req)})
75 (catch Throwable e
76 {:error e})))
78 (defn fetch
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
90 follow.
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."
106 [params]
107 (let [{:keys [host port request follow-redirects?] :as orig} (parse-params params)]
108 (loop [n follow-redirects?
109 request request
110 redirected? false]
111 (let [res (fetch' host port request)
112 redirect? (and (not (:error res))
113 (is-redirect? res))]
114 (cond
115 (:error res) res
116 (= follow-redirects? 0) res
117 (and (= 0 n)
118 redirect?) (do (.close ^Request (:request res))
119 (throw (ex-info "too many redirects"
120 {:original orig
121 :redirects follow-redirects?
122 :code (:code res)
123 :meta (:meta res)})))
124 redirect? (do (.close ^Request (:request res))
125 (recur (dec n)
126 (resolve-uri request (:meta res))
127 true))
128 :else (assoc res :redirected? redirected?))))))
130 (defn body-as-string!
131 "Read all the response into a strings and returns it. The request
132 will be closed."
133 [{r :request}]
134 (let [sw (java.io.StringWriter.)]
135 (with-open [r ^Request r]
136 (io/copy (.body r) sw)
137 (.toString sw))))
139 (defn close
140 "Close a request."
141 [{r :request}]
142 (.close ^Request r))
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."
147 [[var spec] & body]
148 `(let [~var (fetch ~spec)]
149 (when-let [e# (:error ~var)]
150 (throw e#))
151 (with-open [req# ^Request (:request ~var)]
152 ~@body)))