= Middleware

NOTE: Keep in mind that the plural form of "middleware" is also "middleware".

_Middleware_ are higher-order functions that accept a handler and return a new
handler that may compose additional functionality onto or around the original.
For example, some middleware that handles a hypothetical `"time?"` `:op` by
replying with the local time on the server:

[source,clojure]
----
(require
 '[nrepl.misc :refer (response-for)]
 '[nrepl.transport :as t])

(defn current-time
  [h]
  (fn [{:keys [op transport] :as msg}]
    (if (= "time?" op)
      (t/send transport (response-for msg :status :done :time (System/currentTimeMillis)))
      (h msg))))
----

A little silly, but this pattern should be familiar to you if you have
implemented Ring middleware before.  Nearly all of the same patterns and
expectations associated with Ring middleware should be applicable to nREPL
middleware.

All of nREPL's provided default functionality is implemented in terms of
middleware, even foundational bits like session and eval support.  This default
middleware "stack" aims to match and exceed the functionality offered by the
standard Clojure REPL, and is available at
`nrepl.server/default-middleware`.  Concretely, it consists of a
number of middleware functions' vars that are implicitly merged with any
user-specified middleware provided to
`nrepl.server/default-handler`.  To understand how that implicit
merge works, we'll first need to talk about middleware "descriptors".

https://github.com/nrepl/nrepl/wiki/Extensions[Other nREPL middleware are provided by the community].

(See xref:ops.adoc[this documentation listing] for
details as to the operations implemented by nREPL's default middleware stack,
what each operation expects in request messages, and what they emit for
responses.)

== Middleware descriptors and nREPL server configuration

It is generally the case that most users of nREPL will expect some minimal REPL
functionality to always be available: evaluation (and the ability to interrupt
evaluations), sessions, file loading, and so on.  However, as with all
middleware, the order in which nREPL middleware is applied to a base handler is
significant; e.g., the session middleware's handler must look up a user's
session and add it to the message map before delegating to the handler it wraps
(so that e.g. evaluation middleware can use that session data to stand up the
user's dynamic evaluation context).  If middleware were "just" functions, then
any customization of an nREPL middleware stack would need to explicitly repeat
all of the defaults, except for the edge cases where middleware is to be
appended or prepended to the default stack.

To eliminate this tedium, the vars holding nREPL middleware functions may have
a descriptor applied to them to specify certain constraints in how that
middleware is applied.  For example, the descriptor for the
`nrepl.middleware.session/add-stdin` middleware is set thusly:

[source,clojure]
----
(set-descriptor! #'add-stdin
  {:requires #{#'session}
   :expects #{"eval"}
   :handles {"stdin"
             {:doc "Add content from the value of \"stdin\" to *in* in the current session."
              :requires {"stdin" "Content to add to *in*."}
              :optional {}
              :returns {"status" "A status of \"need-input\" will be sent if a session's *in* requires content in order to satisfy an attempted read operation."}}}})
----

Middleware descriptors are implemented as a map in var metadata under a
`:nrepl.middleware/descriptor` key.  Each descriptor can contain
any of three entries:

* `:requires`, a set containing strings or vars identifying other middleware
  that must be applied at a higher level than the middleware being described.
Var references indicate an implementation detail dependency; string values
indicate a dependency on _any_ middleware that handles the specified `:op`.
* `:expects`, the same as `:requires`, except the referenced middleware must
  exist in the final stack at a lower level than the middleware being
described.

TIP: Another way to think of `:expects` and `:requires` would be
_before_ and _after_. Middleware you're expecting should have already
been applied by the time the middleware that expects it gets applied,
and middleware that's required should be applied afterwards. We'll
expand on this in the paragraphs to come.

* `:handles`, a map that documents the operations implemented by the
  middleware.  Each entry in this map must have as its key the string value of
the handled `:op` and a value that contains any of four entries:
** `:doc`, a human-readable docstring for the middleware
** `:requires`, a map of slots that the handled operation must find in request
    messages with the indicated `:op`
** `:optional`, a map of slots that the handled operation may utilize from the
    request messages with the indicated `:op`
** `:returns`, a map of slots that may be found in messages sent in response
    to handling the indicated `:op`

The values in the `:handles` map are used to support the `"describe"` operation,
which provides "a machine- and human-readable directory and documentation for
the operations supported by an nREPL endpoint" (see
`nrepl.impl.docs/generate-ops-info` and the results of
`lein with-profile +maint run nrepl.impl.docs` xref:ops.adoc[here]).

TIP: There's also `lein with-profile +maint run nrepl.impl.docs --output md` if you'd like to
generate an ops listing in Markdown format.

The `:requires` and `:expects` entries control the order in which
middleware is applied to a base handler.  In the `add-stdin` example above,
that middleware will be applied after any middleware that handles the `"eval"`
operation, but before the `nrepl.middleware.session/session`
middleware.  In the case of `add-stdin`, this ensures that incoming messages
hit the session middleware (thus ensuring that the user's dynamic scope —
including `*in*` — has been added to the message) before the ``add-stdin``'s
handler sees them, so that it may append the provided `stdin` content to the
buffer underlying `*in*`.  Additionally, `add-stdin` must be "above" any `eval`
middleware, as it takes responsibility for calling `clojure.main/skip-if-eol`
on `*in*` prior to each evaluation (in order to ensure functional parity with
Clojure's default stream-based REPL implementation).

The specific contents of a middleware's descriptor depends entirely on its
objectives: which operations it is to implement/define, how it is to modify
incoming request messages, and which higher- and lower-level middleware are to
aid in accomplishing its aims.

nREPL uses the dependency information in descriptors in order to produce a
linearization of a set of middleware; this linearization is exposed by
`nrepl.middleware/linearize-middleware-stack`, which is
implicitly used by `nrepl.server/default-handler` to combine the
default stack of middleware with any additional provided middleware vars.  The
primary contribution of `default-handler` is to use
`nrepl.server/unknown-op` as the base handler; this ensures that
unhandled messages will always produce a response message with an `:unknown-op`
`:status`.  Any handlers otherwise created (e.g. via direct usage of
`linearize-middleware-stack` to obtain a ordered sequence of middleware vars)
should do the same, or use a similar alternative base handler.

== Sessions

Each nREPL message is evaluated within a session. There are two types of sessions:
_ephemeral sessions_ and _long-lived sessions_ (or registered sessions).

*Ephemeral sessions* are used for once off processing of a single message. If a message
arrives without a session id, one is created and assigned to it. This is discarded
after processing the message. There's no serialisation guarantee with processing
of messages in ephemeral sessions (though they are serialized in the current
implementation since they run on the server IO thread). However evals run on a
dedicated thread so a running eval can't block another op.

*Long-lived sessions* provide two things, persistence of values between
messages, and a guarantee for serial execution of messages. The only way to
create a long-lived session is to clone an existing session (even an
ephemeral one).

Sessions persist https://clojure.org/reference/vars[dynamic vars]
(collected by `get-thread-bindings`) against a unique lookup. This allows
you to have a different value for `*e` from different REPL clients
(e.g. two separate REPL-y instances). An existing session can be cloned
to create a new one, which then can be modified. This allows for copying
of existing preferences into new environments.

Sessions become even more useful when different nREPL extensions start
taking advantage of
them. https://github.com/gfredericks/debug-repl/[debug-repl] uses
sessions to store information about the current breakpoint, allowing
debugging of two things
separately. https://github.com/nrepl/piggieback[piggieback] uses
sessions to allow host a ClojureScript REPL alongside an existing
Clojure one.

TIP: An easy mistake is to confuse a `session` with an `id`. The difference
between a session and id, is that an `id` is for tracking a single
message, and sessions are for tracking remote state. They're
fundamental to allowing simultaneous activities in the same nREPL.
For instance - if you want to evaluate two expressions simultaneously
you'll have to do this in separate session, as all requests within the
same session are serialized.

== Pretty Printing

NOTE: Pretty printing support was added in nREPL 0.5 and the API is still
considered experimental.

nREPL includes a `print` middleware to print the results of evaluated forms as
strings for returning to the client. This enables using libraries like
https://github.com/greglook/puget[puget] to pretty print the evaluation
results automatically. The middleware options may be provided in either requests
or responses (the former taking precedence over the latter if any options are
specified in both). The following options are supported:

* `:nrepl.middleware.print/print`: a fully-qualified symbol naming a var whose
  function to use for printing. Defaults to the equivalent of `clojure.core/pr`.

** The var must point to a function of three arguments:

*** `value`: the value to print.
*** `writer`: the `java.io.Writer` to print on.
*** `options`: a (possibly nil) map of options.

** Note well that the printing function is expected to not interact with
   `\*out*` or `\*err*` at all, even rebinding them (e.g. via `with-out-str`).
   Output may be printed to either of those streams during its operation –
   consider the following example:

+
[source,clojure]
----
(->> [1 2 3]
     (map (fn [n]
            (println n)
            n)))
----

** The result of the expression is `(1 2 3)`, and evaluating it will result in
   each of the three numbers being printed to `\*out*`. However, because `map`
   is lazy, the calls to `println` will be interleaved with the operation of the
   printer function. Hence if the printer function is coupled to `\*out*`, its
   output might be interleaved with that of the calls to `println`.

*** Technically, `map` is not fully lazy – it returns a chunked sequence – but
    the principle still applies.

** Further, note that `clojure.pprint/pprint` rebinds `\*out*` internally (even
   when using its explicit `writer` arity). It is not possible to prevent the
   interleaving of output when using `clojure.pprint`.

* `:nrepl.middleware.print/options`: a map of options to pass to the printing
  function. Defaults to `nil`.

* `:nrepl.middleware.print/stream?`: if logical true, the result of printing
  each value will be streamed to the client over one or more messages. Defaults
  to false.

* `:nrepl.middleware.print/buffer-size`: size of the buffer to use when
  streaming results. Defaults to 1024.

** Note that this only represents an upper bound on the number of bytes per
  message – the printing function may also call `flush` on `writer`, which will
  result in a response being sent immediately.

* `:nrepl.middleware.print/quota`: a hard limit on the number of bytes printed
  for each value.

** A status of `:nrepl.middleware.print/truncated` will be returned by the
   middleware if the quota is exceeded. In streamed mode, this will be conveyed
   as a discrete response after the final printing result. Otherwise, it will be
   added to the status of the response, and additionally the response will
   include `:nrepl.middleware.print/truncated-keys`, indicating which keys in
   the response were truncated.

* `:nrepl.middleware.print/keys`: a seq of the keys in the response whose values
  should be printed. Defaults to `[:value]` for `eval` and `load-file`
  responses.

[source,clojure]
----
{:op "eval"
 :code "(+ 1 1)"
 :nrepl.middleware.print/print 'my.custom/print-value
 :nrepl.middleware.print/options {:print-width 120}
 :nrepl.middleware.print/stream? true
 :nrepl.middleware.print/buffer-size 1024
 :nrepl.middleware.print/quota 8096}
----

The functionality of the `print` middleware is reusable by other middleware. If
a middleware descriptor's `:requires` set contains
`#'nrepl.middleware.print/wrap-print`, then it can expect:

* Any responses it returns will have its values printed according to the above
  options, as provided in the request and/or response.

** For example, to ensure that `:value` is printed, responses from the `eval`
  middleware look like this:

+
[source,clojure]
----
{:ns "user"
 :value '(1 2 3)
 :nrepl.middleware.print/keys #{:value}}
----

* Any requests it handles will contain the key `:nrepl.middleware.print/print-fn`,
  whose value is a function that calls the given printer function with the given
  options – i.e. its signature is `[value writer]`.

== Evaluation Errors

nREPL includes a `caught` middleware which provides a configurable hook for any
`java.lang.Throwable` that should be conveyed interactively (generally by
printing to `\*err*`). Like the `print` middleware, any options may be provided
in either requests or responses (the former taking precedence over the latter if
any options are specified in both). The following options are supported:

* `:nrepl.middleware.caught/caught`: a fully-qualified symbol naming a var whose
  function to use to convey interactive errors. Must point to a function that
  takes a `java.lang.Throwable` as its sole argument. Defaults to
  `clojure.main/repl-caught`.

* `:nrepl.middleware.caught/print?`: if logical true, the printed value of any
  interactive errors will be returned in the response (otherwise they will be
  elided). Delegates to `nrepl.middleware.print` to perform the printing.
  Defaults to false.

[source,clojure]
----
{:op "eval"
 :code "(/ 1 0)"
 :nrepl.middleware.caught/caught 'my.custom/print-stacktrace
 :nrepl.middleware.caught/print? true}
----

The functionality of the `caught` middleware is reusable by other middleware.
If a middleware descriptor's `:requires` set contains
`#'nrepl.middleware.caught/wrap-caught`, then it can expect:

* Any returned responses containing the key `:nrepl.middleware.caught/throwable`
  will have that key's corresponding value passed to the hook.

* Any handled requests will contain the key
  `:nrepl.middleware.caught/caught-fn`, whose value is a function that can be
  called on a `java.lang.Throwable` to convey errors interactively.

== Sideloading

NOTE: Sideloading support was added in nREPL 0.7 and the API is still
considered experimental, and may change.

nREPL includes `sideloader` middleware. This provides a Java Class Loader that is able to dynamically load classes and resources at runtime by interacting with the nREPL client (as opposed to using the classpath of the JVM hosting nREPL server).

This performs a similar functionality as the `load-file` operation, where we can load Clojure namespaces (as source files) or Java classes (as bytecode) by simply `require` or `import` them.

This allows a client to add new functionality to an already running instance of nREPL, and paves the way for a more client configurable nREPL.

=== Starting the sideloader

The sideloader is initialised by a new operation: `sideloader-start`. This will never return with status `:done`, but its message ID will be used for all future sideloading requests.

As additional resources/classes are looked up, server will send messages to the client with status `:sideloader-lookup`, and the following parameters

* `:type`, being `resource` or `class`
* `:name`, being a string (e.g. `foo/bar.clj` or `foo.bar`)

The client is responsible for responding to the lookup. It does so by replying with the operation `sideloader-provide`, and parameters

* `:type` and `:name` the same as the lookup.
* `:content` base-64 encoded byte array of the resource or class. An empty response indicates "resource/class not found."

See xref:building_clients.adoc[Building Clients] on how to implement the server side of this functionality.

=== Triggering the sideloader

Once the sideloader has been started, it can be triggered by an ordinary `eval` or `load-file` operation. The nREPL server will first try to find a resource/class on the classpath of its own JVM. Failing this, it will attempt to request it from the nREPL client by the above described mechanism. Should this still fail, expect a `ClassNotFoundException` as usual.

TIP: Once a class has been loaded, it will become available to all sessions on nREPL server.

== Dynamic middleware loading

NOTE: Dynamic middleware loading support was added in nREPL 0.8 and the API is still
considered experimental, and may change.

nREPL includes a `dynamic-loader` middleware, which can be used, at runtime,
query and change the middleware stack the nREPL server is using. This is especially
powerful when combined with sideloading, as it allows a client to configure the
server after connecting, and provides an alternative to having to specify the middleware required by the client as startup time.

This introduces three new operations:

* `ls-middleware`, to return a list of active middleware, ordered from inside outwards.
* `add-middleware`, which adds a middleware to the stack. Optionally, a list of `extra-namespaces` could be provided for loading. This is useful when adding middleware that implement some form of deferred loading. Examples include `cider-nrepl` and `refactor-nrepl`. In these cases, some of the required namespaces might only be loaded upon first use, which may occur outside of a sideloading session, and thus fail. This feature allows us to pre-load namespaces when we add a middleware. If loading of any particular middleware fails, the stack will be unchanged.
* `swap-middleware`, similar to `add-`, but replaces all existing middleware. Note that this _may_ remove the `dynamic-loader` itself.
