Transports
Transports are roughly analogous to Ring’s adapters: they provide an
implementation of a common protocol (nrepl.transport.Transport
)
to enable nREPL clients and servers to send and receive messages without regard
for the underlying channel or particulars of message encoding.
nREPL includes three transports, all of which are socket-based: a "tty"
transport that allows one to connect to an nREPL endpoint using e.g. telnet
(which therefore supports only the most simplistic interactive evaluation of
expressions), one that uses
bencode to encode
nREPL messages over sockets, and one that uses EDN as the data exchange format.
It’s the bencode transport that is used by default by
nrepl.server/start-server and nrepl.core/connect .
|
Bencode Transport
This is, and will mostly likely remain, nREPL’s default and primary transport. It is pronounced B-Encode, and is a relatively simple format, comprising of only two scalar types (byte strings and integer) and two collection types (dictionaries and lists).
A simple nREPL message, to run the operation eval
on the code (+ 2 2)
would
look like this in bencode (line breaks added for clarity):
d 4:code 7:(+ 2 2) 2:op 4:eval e
In documentation, this message would typically be expressed in EDN format:
{:op "eval" :code "(+ 2 2)"}
Given that many, if not most, nREPL clients do not use EDN as their native data format, it’s best to think of this as the format of a message once received by nREPL. Correspondingly, responses are in the form they take just before being sent through the transport.
nREPL’s bencode encoder/decoder performs the following type conversions:
EDN type > | Bencode type > | EDN type |
---|---|---|
String |
String |
String |
Keyword |
String |
String |
Symbol |
String |
String |
Integer |
Integer |
Integer |
Map |
Dictionary |
Map |
Vector |
List |
Vector |
List |
List |
Vector |
Set |
List |
Vector |
In addition, nREPL server performs a keywordize-keys
operation on the received
map, restoring the likes of :op
and :code
to keywords. Thus, the use of
keywords in writing nREPL message is mainly for clarity. The following messages,
when encoded using nREPL’s bencode encoder and received by nREPL, are equivalent:
{:op "eval" ...} ;; this is also the internal representation
{:op :eval ...}
{"op" "eval" ...}
{"op" :eval ...}
There’s nothing special you have to do to use the bencode transport,
as it’s the default transport for nrepl.server/start-server
.
You’ll need a bencode capable client to connect to an nREPL server that transport.
nREPL’s bencode implementation is available as a standalone library.
Keep in mind that nREPL itself doesn’t use this library, as it’s committed to having 0 runtime dependencies.
The code in the library and in nREPL nrepl.bencode namespace is identical, though, and will be kept in sync
in the future.
|
EDN Transport
The EDN transport was introduced in nREPL 0.7. |
The main difference between the bencode transport and the EDN one is that instead of bencode dictionaries and lists, and being limited to integer and byte string types, you’d be sending and receiving full EDN structures and types, including maps, vectors, lists, sets, strings, keywords and symbols. The structures of the messages is similar, only the data format changes.
This may be useful in a couple of usecases: In some clients, including ClojureScript ones, where EDN is easier to handle than bencode. Furthermore, because this transport exposes more of the richer data types in the internals of nREPL and its middleware, it may support more complex usecases.
The introduction of the EDN transport does require more specification of the message format, whereby some vagueness was allowed for by the limitatioms of bencode. For example, :op
values are strings, and :status
is either a keyword, or a set of keywords. This may cause some surprised, unintended consequences. Thus tweaks to the EDN message format may be possible until the transport is declared mature.
These details are reflected in the nrepl.spec
ns, against which responses are verified during testing. This is not used by nREPL anywhere else at moment.
For example, of the (same as before) four messages
{:op "eval" ...}
{:op :eval ...}
{"op" "eval" ...}
{"op" :eval ...}
the first one is considered canonical, though nREPL will accept the second one as well. The third and fourth one will not work.
Using the EDN transport is pretty simple. You just need to start an nREPL server with EDN transport and you’re good to go:
(require
'[nrepl.server :as server]
'[nrepl.transport :as transport])
(server/start-server :port 12345 :transport-fn transport/edn)
You can also start an nREPL with a EDN transport using clj
:
$ clj -M:nrepl -m nrepl.cmdline -t nrepl.transport/edn
nREPL server started on port 63266 on host localhost - nrepl+edn://localhost:63266
TTY Transport
Using the TTY transport is pretty simple. You just need to start an nREPL server with TTY transport and you’re good to go:
(require
'[nrepl.server :as server]
'[nrepl.transport :as transport])
(server/start-server :port 12345 :transport-fn transport/tty :greeting-fn transport/tty-greeting)
The :greeting-fn is responsible for printing the initial message you’ll see
upon connecting.
|
Afterwards you can simply connect to the server with some TTY client like telnet
, nc
or inf-clojure
.
$ nc localhost 12345
;; Clojure 1.9.0
user=>
Starting with nREPL 0.5 you can also start an nREPL with a TTY transport using clj
:
$ clj -M:nrepl -m nrepl.cmdline -t nrepl.transport/tty
nREPL server started on port 63266 on host localhost - telnet://localhost:63266