Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

A Clojure network REPL that provides a server and client, along with some common APIs of use to IDEs and other tools that may need to evaluate Clojure code in remote environments.

License

NotificationsYou must be signed in to change notification settings

clojure/tools.nrepl

Repository files navigation

This project's development moved to a newnrepl/nrepl repository, outside of Clojure Contrib, after version 0.2.13

nREPL is a Clojurenetwork REPL thatprovides a REPL server and client, along with some common APIsof use to IDEs and other tools that may need to evaluate Clojurecode in remote environments.

Usage

"Installation"

nREPL is available in Maven central. Add this to your Leiningenproject.clj:dependencies:

[org.clojure/tools.nrepl"0.2.13"]

Or, add this to your Maven project'spom.xml:

<dependency>  <groupId>org.clojure</groupId>  <artifactId>tools.nrepl</artifactId>  <version>0.2.13</version></dependency>

A list of all prior releases are availablehere.

Please note the changelog inCHANGELOG.md.

nREPL is compatible with Clojure 1.2.0 and higher.

Please post general questions or discussion on either theclojure-dev orclojure-tools mailing lists.Bug reports and such may be filed intonREPL'sJIRA.

nREPL's generated API documentation is availablehere. Ahistory of nREPLbuilds is available, as well asacompatibility testmatrix, verifyingnREPL's functionality against multiple versions of Clojure and multiple JVMs.

Connecting to an nREPL server

Most of the time, you will connect to an nREPL server using an existingclient/tool. Tools that support nREPL include:

If your preferred Clojure development environment supports nREPL, you're done.Use it or connect to an existing nREPL endpoint, and you're done.

Talking to an nREPL endpoint programmatically

If you want to connect to an nREPL server using the default transport, somethinglike this will work:

=> (require '[clojure.tools.nrepl:as repl])nil=> (with-open [conn (repl/connect:port59258)]     (-> (repl/client conn1000); message receive timeout required       (repl/message {:op"eval":code"(+ 2 3)"})       repl/response-values))[5]

response-values will return only the values of evaluated expressions, readfrom their (by default)pr-encoded representations viaread. You can seethe full content of message responses easily:

=> (with-open [conn (repl/connect:port59258)]     (-> (repl/client conn1000)       (repl/message {:op:eval:code"(time (reduce + (range 1e6)))"})       doall;; `message` and `client-session` all return lazy seqs       pprint))nil({:out"\"Elapsed time: 68.032 msecs\"\n",:session"2ba81681-5093-4262-81c5-edddad573201",:id"3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"} {:ns"user",:value"499999500000",:session"2ba81681-5093-4262-81c5-edddad573201",:id"3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"} {:status ["done"],:session"2ba81681-5093-4262-81c5-edddad573201",:id"3124d886-7a5d-4c1e-9fc3-2946b1b3cfaa"})

Each message must contain at least an:op (or"op") slot, which specifiesthe "type" of the operation to be performed. The operations supported by annREPL endpoint are determined by the handlers and middleware stack used whenstarting that endpoint; the default middleware stack (described below) supportsa particular set of operations,detailedhere.

Embedding nREPL, starting a server

If your project uses Leiningen (v2 or higher), you already have access to annREPL server for your project vialein repl (or,lein repl :headless if youdon't need the Reply terminal-based nREPL client to connect to the resultingnREPL server).

Otherwise, it can be extremely useful to have your application host a REPLserver whereever it might be deployed; this can greatly simplify debugging,sanity-checking, panicked code patching, and so on.

nREPL provides a socket-based server that you can trivially start from yourapplication.Add it to your project's dependencies, and add codelike this to your app:

=> (use '[clojure.tools.nrepl.server:only (start-server stop-server)])nil=> (defonceserver (start-server:port7888))#'user/server

Depending on what the lifecycle of your application is, whether you want to beable to easily restart the server, etc., you might want to put the valuestart-server returns into an atom or somesuch. Anyway, once your app isrunning an nREPL server, you can connect to it from a tool like Leiningen orCounterclockwise or Reply, or from another Clojure process:

=> (with-open [conn (repl/connect:port7888)]     (-> (repl/client conn1000)       (repl/message {:op:eval:code"(+ 1 1)"})       repl/response-values))[2]

You can stop the server with(stop-server server).

Server options

Note that nREPL is not limited to its default messaging protocol, nor to itsdefault use of sockets. nREPL provides atransport abstraction forimplementing support for alternative protocols and connection methods.Alternative transport implementations are available, and implementing your ownis not difficult; read more about transportshere.

Building nREPL

Releases are available from Maven Central, and SNAPSHOT builds from master'sHEAD are automatically deployed to Sonatype's OSS repository (seethis forhow to configure Leiningen or Maven to use OSS-snapshots), so building nREPLshouldn't ever be necessary. But, if you insist:

  1. Clone the repo
  2. Make sure you have maven installed
  3. Run the maven build, either:
    1. mvn package: This will produce an nREPL jar file in thetargetdirectory, and run all tests against Clojure 1.2.0.
    2. mvn verify: This does the same, but also runs the tests withother Clojure "profiles" (one for each supported version of Clojure).

Why nREPL?

nREPL has been designed with the aim of ensuring that it satisfies therequirements of both application developers (in support of activities rangingfrom interactive remote debugging and experimentation in developmentcontexts through to more advanced use cases such as updating deployedapplications) as well as toolmakers (providing a standard way to connect to andintrospect running environments as a way of informing user interfaces of allkinds, including "standard" interactive, text-based REPLs).

The default network protocol used is simple, depending neitheron JVM or Clojure specifics, thereby allowing (encouraging?) the developmentof non-Clojure REPL clients. The REPLs operational semantics are suchthat essentially any non-JVM Clojure implementation should be able toimplement it, with allowances for hosts that lack the concurrency primitives tosupport e.g. asynchronous evaluation, interrupts, etc.

For more information about the motivation, architecture, use cases, anddiscussion related to nREPL, see the see the original design notes,availablehere,and thenotes anddiscussionaround its recent redesign.

Design

nREPL largely consists of three abstractions: handlers, middleware, andtransports. These are roughly analogous to the handlers, middleware, andadapters ofRing, though there are someimportant semantic differences. Finally, nREPL is fundamentally message-orientedand asynchronous (in contrast to most REPLs that build on top of streamsprovided by e.g. terminals).

Messages

nREPL messages are maps. The keys and values that may be included in messagesdepends upon the transport being used; different transports may encode messagesdifferently, and therefore may or may not be able to represent certain datatypes.

Each message sent to an nREPL endpoint constitutes a "request" to perform aparticular operation, which is indicated by a"op" entry. Each operation mayfurther require the incoming message to contain other data. Which data anoperation requires or may accept varies; for example, a message to evaluatesome code might look like this:

{"op""eval""code""(+ 1 2 3)"}

The result(s) of performing each operation may be sent back to the nREPL clientin one or more response messages, the contents of which again depend upon theoperation.

Transports

Transports are roughly analogous to Ring's adapters: they provide animplementation of a common protocol (clojure.tools.nrepl.transport.Transport)to enable nREPL clients and servers to send and receive messages without regardfor the underlying channel or particulars of message encoding.

nREPL includes two transports, both 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 ofexpressions), and one that usesbencode to encodenREPL messages over sockets. It is the latter that is used by default byclojure.tools.nrepl.server/start-server andclojure.tools.nrepl/connect.

[Other nREPL transports are provided by the community](https://github.com/clojure/tools.nrepl/wiki/Extensions).

Handlers

Handlers are functions that accept a single incoming message as an argument.An nREPL server is started with a single handler function, which will be usedto process messages for the lifetime of the server. Note that handler returnvalues areignored; results of performing operations should be sent back tothe client via the transport in use (which will be explained shortly). Thismay seem peculiar, but is motivated by two factors:

  • Many operations — including something as simple as code evaluation — isfundamentally asynchronous with respect to the nREPL server
  • Many operations can produce multiple results (e.g. evaluating a snippet ofcode like"(+ 1 2) (def a 6)").

Thus, messages provided to nREPL handlers are guaranteed to contain a:transport entry containing thetransport that should be usedto send all responses precipitated by a given message. (This slot is added bythe nREPL server itself, thus, if a client sends any message containing a"transport" entry, it will be bashed out by theTransport that was thesource of the message.) Further, all messages provided to nREPL handlers havekeyword keys (as perclojure.walk/keywordize-keys).

Depending on its:op, a message might be required to contain other slots, andmight optionally contain others. It is generally the case that requestmessages should contain a globally-unique:id.Every request must provoke at least one and potentially many response messages,each of which should contain an:id slot echoing that of the provokingrequest.

Once a handler has completely processed a message, a responsecontaining a:status of:done must be sent. Some operations necessitatethat additional responses related to the processing of a request are sent aftera:done:status is reported (e.g. delivering content written to*out* byevaluated code that started afuture).Other statuses are possible, depending upon the semantics of the:op beinghandled; in particular, if the message is malformed or incomplete for aparticular:op, then a response with an:error:status should be sent,potentially with additional information about the nature of the problem.

It is possible for an nREPL server to send messages to a client that are not adirect response to a request (e.g. streaming content written toSystem/outmight be started/stopped by requests, but messages containing such contentcan't be considered responses to those requests).

If the handler being used by an nREPL server does not recognize or cannotperform the operation indicated by a request message's:op, then it shouldrespond with a message containing a:status of"unknown-op".

It is currently the case that the handler provided as the:handler toclojure.tools.nrepl.server/start-server is generally built up as a result ofcomposing multiple pieces of middleware.

Middleware

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

(require '[clojure.tools.nrepl.transport:as t])(use '[clojure.tools.nrepl.misc:only (response-for)])(defncurrent-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 haveimplemented Ring middleware before. Nearly all of the same patterns andexpectations associated with Ring middleware should be applicable to nREPLmiddleware.

All of nREPL's provided default functionality is implemented in terms ofmiddleware, even foundational bits like session and eval support. This defaultmiddleware "stack" aims to match and exceed the functionality offered by thestandard Clojure REPL, and is available atclojure.tools.nrepl.server/default-middlewares. Concretely, it consists of anumber of middleware functions' vars that are implicitly merged with anyuser-specified middleware provided toclojure.tools.nrepl.server/default-handler. To understand how that implicitmerge works, we'll first need to talk about middleware "descriptors".

[Other nREPL middlewares are provided by the community](https://github.com/clojure/tools.nrepl/wiki/Extensions).

(Seethis documentationlisting fordetails as to the operations implemented by nREPL's default middleware stack,what each operation expects in request messages, and what they emit forresponses.)

Middleware descriptors and nREPL server configuration

It is generally the case that most users of nREPL will expect some minimal REPLfunctionality to always be available: evaluation (and the ability to interruptevaluations), sessions, file loading, and so on. However, as with allmiddleware, the order in which nREPL middleware is applied to a base handler issignificant; e.g., the session middleware's handler must look up a user'ssession 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 theuser's dynamic evaluation context). If middleware were "just" functions, thenany customization of an nREPL middleware stack would need to explicitly repeatall of the defaults, except for the edge cases where middleware is to beappended or prepended to the default stack.

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

(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:clojure.tools.nrepl.middleware/descriptor key. Each descriptor can containany of three entries:

  • :requires, a set containing strings or vars identifying other middlewarethat must be applied at a higher level than the middleware being described.Var references indicate an implementation detail dependency; string valuesindicate a dependency onany middleware that handles the specified:op.
  • :expects, the same as:requires, except the referenced middleware mustexist in the final stack at a lower level than the middleware beingdescribed.
  • :handles, a map that documents the operations implemented by themiddleware. Each entry in this map must have as its key the string value ofthe 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 requestmessages with the indicated:op
    • :optional, a map of slots that the handled operation may utilize from therequest messages with the indicated:op
    • :returns, a map of slots that may be found in messages sent in responseto handling the indicated:op

The values in the:handles map is used to support the"describe" operation,which provides "a machine- and human-readable directory and documentation forthe operations supported by an nREPL endpoint" (seeclojure.tools.nrepl.middleware/describe-markdown, and the results of"describe" anddescribe-markdownhere).

The:requires and:expects entries control the order in whichmiddleware is applied to a base handler. In theadd-stdin example above,that middleware will be applied after any middleware that handles the"eval"operation, but before theclojure.tools.nrepl.middleware.session/sessionmiddleware. In the case ofadd-stdin, this ensures that incoming messageshit the session middleware (thus ensuring that the user's dynamic scope —including*in* — has been added to the message) before theadd-stdin'shandler sees them, so that it may append the providedstdin content to thebuffer underlying*in*. Additionally,add-stdin must be "above" anyevalmiddleware, as it takes responsibility for callingclojure.main/skip-if-eolon*in* prior to each evaluation (in order to ensure functional parity withClojure's default stream-based REPL implementation).

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

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

Thanks

Thanks to the following Clojure masters for their helpful feedback during theinitial design phases of nREPL:

  • Justin Balthrop
  • Meikel Brandmeyer
  • Hugo Duncan
  • Christophe Grand
  • Anthony Grimes
  • Phil Hagelberg
  • Rich Hickey
  • Chris Houser
  • Colin Jones
  • Laurent Petit
  • Eric Thorsen

License

Copyright © 2010 - 2013 Chas Emerick and contributors.

Licensed under the EPL. (See the file epl.html.)

About

A Clojure network REPL that provides a server and client, along with some common APIs of use to IDEs and other tools that may need to evaluate Clojure code in remote environments.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors19


[8]ページ先頭

©2009-2025 Movatter.jp