Skip to content

Commit 4b4939d

Browse files
committed
Add complete usage examples for server and client to README
1 parent 7cfa8de commit 4b4939d

1 file changed

Lines changed: 147 additions & 47 deletions

File tree

README.adoc

Lines changed: 147 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -79,53 +79,94 @@ configuration and can alleviate security concerns, and works around
7979
limitations in various deployment environments where traditional
8080
socket-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
135176
production. Use HTTPS to prevent credentials and REPL traffic from being
136177
transmitted 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

163263
The primary support channel for Drawbridge is the http://clojurians.net/[Clojurians Slack]. Feel

0 commit comments

Comments
 (0)