@@ -79,53 +79,94 @@ configuration and can alleviate security concerns, and works around
7979limitations in various deployment environments where traditional
8080socket-based channels are limited or entirely unavailable.
8181
82- === In a Ring web application
82+ Drawbridge has two sides:
8383
84- Once you have added Drawbridge to your project's dependencies, just
85- add its Ring handler to your application. For example, if you're using
86- https://github.com/weavejester/compojure[Compojure] for routing and
87- such:
84+ * A *server side* (`drawbridge.core/ring-handler`) that exposes an nREPL
85+ endpoint as a Ring handler you mount in your web app.
86+ * A *client side* (`drawbridge.client`) that lets Clojure tooling connect
87+ to such an endpoint over HTTP/HTTPS.
88+
89+ The sections below cover each in turn, with complete examples.
90+
91+ == Server: exposing an nREPL endpoint over HTTP
92+
93+ `drawbridge.core/ring-handler` returns an ordinary Ring handler. It needs
94+ the standard param middleware (`wrap-keyword-params`, `wrap-nested-params`
95+ and `wrap-params`, a.k.a. the Compojure "api" stack) applied to it, and it
96+ only responds to `GET` and `POST` requests. Session state is managed
97+ internally, so you don't need to add session middleware yourself.
98+
99+ === A standalone endpoint
100+
101+ The smallest possible server: wrap the handler in the required middleware
102+ and run it with the Jetty adapter.
88103
89104[source,clojure]
90105----
91- (require 'drawbridge.core)
106+ (ns my-app.repl
107+ (:require
108+ [drawbridge.core :refer [ring-handler]]
109+ [ring.adapter.jetty :refer [run-jetty]]
110+ [ring.middleware.keyword-params :refer [wrap-keyword-params]]
111+ [ring.middleware.nested-params :refer [wrap-nested-params]]
112+ [ring.middleware.params :refer [wrap-params]]))
113+
114+ (def app
115+ (-> (ring-handler)
116+ wrap-keyword-params
117+ wrap-nested-params
118+ wrap-params))
119+
120+ (defn -main [& _]
121+ (run-jetty app {:port 8080 :join? false}))
122+ ----
123+
124+ Run it and you have an nREPL endpoint at `http://localhost:8080/`. See
125+ the client section below for how to connect.
126+
127+ === Mounting into an existing app
92128
93- (let [nrepl-handler (drawbridge.core/ring-handler)]
94- (ANY "/repl" request (nrepl-handler request)))
129+ More commonly you'll mount the endpoint at some route within a larger
130+ application. Wrap the Drawbridge handler in its middleware, then route a
131+ single URI to it. With
132+ https://github.com/weavejester/compojure[Compojure]:
133+
134+ [source,clojure]
95135----
136+ (ns my-app.handler
137+ (:require
138+ [compojure.core :refer [ANY defroutes]]
139+ [compojure.route :as route]
140+ [drawbridge.core :refer [ring-handler]]
141+ [ring.middleware.keyword-params :refer [wrap-keyword-params]]
142+ [ring.middleware.nested-params :refer [wrap-nested-params]]
143+ [ring.middleware.params :refer [wrap-params]]))
144+
145+ (def drawbridge
146+ (-> (ring-handler)
147+ wrap-keyword-params
148+ wrap-nested-params
149+ wrap-params))
150+
151+ (defroutes app
152+ (ANY "/repl" request (drawbridge request))
153+ (route/not-found "Not found"))
154+ ----
155+
156+ Any HTTP or HTTPS client can now send nREPL messages to `/repl` and read
157+ responses from the same URI.
96158
97- With this, any HTTP or HTTPS client can send nREPL messages to the
98- `/repl` URI, and read responses from the same. Conveniently, any
99- security measures applied within your application will work fine in
100- conjunction with Drawbridge; so, if you configure its route to require
101- authentication or authorization to some application-specific role, those
102- prerequisites will apply just as with any other Ring handler in the same
103- context.
159+ === Handler options
104160
105161`ring-handler` accepts the following options:
106162
107- * `:nrepl-handler` -- a custom nREPL handler (default: `nrepl.server/default-handler`)
163+ * `:nrepl-handler` -- a custom nREPL handler (default: `( nrepl.server/default-handler) `)
108164* `:default-read-timeout` -- milliseconds to wait for additional nREPL responses before finalizing each HTTP response (default: `0`)
109165* `:cookie-name` -- the session cookie name (default: `"drawbridge-session"`)
110166
111- Some things to be aware of when using `drawbridge.core/ring-handler`:
112-
113- * It requires `GET` and `POST` requests
114- to be routed to whatever URI to which it is mapped; other request
115- methods result in an HTTP error response.
116- * It requires these standard Ring middlewares to function properly:
117- ** `keyword-params`
118- ** `nested-params`
119- ** `wrap-params`
120-
121- Especially if you are going to be connecting to your webapp's nREPL
122- endpoint with a client that uses Drawbridge's own HTTP/HTTPS client
123- transport (see below), this is all you need to know.
124-
125- If you are interested in the implementation details and semantics,
126- perhaps because you'd like to implement support for Drawbridge in
127- non-Clojure nREPL clients, you'll want to review the documentation for
128- `ring-handler`, which contains additional important details.
167+ If you'd like to implement Drawbridge support in a non-Clojure nREPL
168+ client, the docstring of `ring-handler` documents the request/response
169+ semantics and message format in detail.
129170
130171=== Security
131172
@@ -135,29 +176,88 @@ endpoint with authentication and authorization middleware before deploying to
135176production. Use HTTPS to prevent credentials and REPL traffic from being
136177transmitted in cleartext.
137178
138- === In Clojure tooling
179+ Because the endpoint is just a Ring handler, any authentication or
180+ authorization middleware you already use applies. For example, gating it
181+ behind HTTP Basic auth:
182+
183+ [source,clojure]
184+ ----
185+ (require '[ring.middleware.basic-authentication :refer [wrap-basic-authentication]])
186+
187+ (def secured-drawbridge
188+ (-> drawbridge
189+ (wrap-basic-authentication
190+ (fn [user pass] (and (= user "me") (= pass "secret"))))))
191+ ----
139192
140- Drawbridge also provides a client-side nREPL transport implementation
141- for the Ring handler in `drawbridge.client/ring-client-transport`.
193+ == Client: connecting to an endpoint
142194
143- Note that the `drawbridge.client` namespace implicitly adds
144- implementations to the `nrepl.core/url-connect` multimethod for
145- `"http"` and `"https"` schemes. So, once this namespace is loaded, any
146- tool that uses `url-connect` will use `ring-client-transport` for
147- connecting to HTTP and HTTPS nREPL endpoints.
195+ `drawbridge.client` provides a client-side nREPL transport for the HTTP
196+ endpoint. Loading the namespace registers implementations of the
197+ `nrepl.core/url-connect` multimethod for the `http` and `https` schemes,
198+ so any tool that connects via a URL going through `url-connect` will speak
199+ the HTTP transport. Note that loading `drawbridge.client` is what performs
200+ this registration -- until it's loaded, `url-connect` doesn't know about
201+ HTTP.
148202
149- === Configuration
203+ === From Leiningen
150204
151- The client supports additional HTTP headers, which is useful e.g. for
152- using Bearer authorization to connect to the endpoint. The headers can
153- be set in the nREPL configuration. For example, create `.nrepl.edn` in
154- the working directory with the contents:
205+ The simplest way to connect is Leiningen's REPL, which loads Drawbridge's
206+ client transport automatically when the connect URL uses the `http`/`https`
207+ scheme:
208+
209+ [source,shell]
210+ ----
211+ lein repl :connect http://localhost:8080/
212+ ----
213+
214+ For this to work Drawbridge needs to be on the classpath. Leiningen bundles
215+ an old version, so to pin the current one add it to `~/.lein/profiles.clj`:
216+
217+ [source,clojure]
218+ ----
219+ {:user {:dependencies [[nrepl/drawbridge "0.3.1"]]}}
220+ ----
221+
222+ === From another Clojure process
223+
224+ To connect programmatically, require `drawbridge.client` (to register the
225+ `url-connect` implementations) and then use `nrepl.core/url-connect`:
226+
227+ [source,clojure]
228+ ----
229+ (require '[drawbridge.client] ;; registers http/https on url-connect
230+ '[nrepl.core :as nrepl])
231+
232+ (with-open [conn (nrepl/url-connect "http://localhost:8080/")]
233+ (-> (nrepl/client conn 1000)
234+ (nrepl/message {:op "eval" :code "(+ 1 2)"})
235+ nrepl/response-values))
236+ ;; => [3]
237+ ----
238+
239+ === Authentication headers
240+
241+ The client can send extra HTTP headers, which is handy for Bearer or Basic
242+ authorization against a secured endpoint. Set them in the nREPL
243+ configuration by creating `.nrepl.edn` in the working directory:
155244
156245[source,clojure]
157246----
158247{:drawbridge {:http-headers {:Authorization "Bearer <JWT token>"}}}
159248----
160249
250+ These headers are attached to every request the client makes.
251+
252+ === A note on editor clients
253+
254+ Editor tooling such as CIDER, Calva, and Conjure connects using a host and
255+ port rather than a URL, so it does not go through `url-connect` and cannot
256+ talk to a Drawbridge endpoint directly. Drawbridge targets URL-based
257+ connections (`lein repl :connect`) and programmatic clients. To reach an
258+ endpoint from an editor you'd need an nREPL client that speaks the HTTP
259+ transport, or a local process that bridges a socket to the HTTP endpoint.
260+
161261== Need Help?
162262
163263The primary support channel for Drawbridge is the http://clojurians.net/[Clojurians Slack]. Feel
0 commit comments