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:

Table 1. How nrepl.bencode handles types
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