Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

148 lines (132 sloc) 6.054 kb
(ns ^{:doc "Default server implementations"
:author "Chas Emerick"}
(:require [ :as repl]
( [ack :as ack]
[transport :as t]
[middleware :as middleware])
( interruptible-eval
(:use [ :only (returning response-for log)])
(:import ( Socket ServerSocket InetSocketAddress InetAddress)))
(defn handle*
[msg handler transport]
(handler (assoc msg :transport transport))
(catch Throwable t
(log t "Unhandled REPL handler exception processing message" msg))))
(defn handle
"Handles requests received via [transport] using [handler].
Returns nil when [recv] returns nil for the given transport."
[handler transport]
(when-let [msg (t/recv transport)]
(future (handle* msg handler transport))
(recur handler transport)))
(defn- safe-close
[^ x]
(.close x)
(catch e
(log e "Failed to close " x))))
(defn- accept-connection
[{:keys [^ServerSocket server-socket open-transports transport greeting handler]
:as server}]
(when-not (.isClosed server-socket)
(let [sock (.accept server-socket)]
(future (let [transport (transport sock)]
(swap! open-transports conj transport)
(when greeting (greeting transport))
(handle handler transport)
(swap! open-transports disj transport)
(safe-close transport)))))
(future (accept-connection server)))))
(defn stop-server
"Stops a server started via `start-server`."
[{:keys [open-transports ^ServerSocket server-socket] :as server}]
(returning server
(.close server-socket)
(swap! open-transports #(reduce
(fn [s t]
; should always be true for the socket server...
(if (instance? t)
(safe-close t)
(disj s t))
% %))))
(defn unknown-op
"Sends an :unknown-op :error for the given message."
[{:keys [op transport] :as msg}]
(t/send transport (response-for msg :status #{:error :unknown-op :done} :op op)))
(def default-middlewares
(defn default-handler
"A default handler supporting interruptible evaluation, stdin, sessions, and
readable representations of evaluated expressions via `pr`.
Additional middlewares to mix into the default stack may be provided; these
should all be values (usually vars) that have an nREPL middleware descriptor
in their metadata (see!)."
[& additional-middlewares]
(let [stack (middleware/linearize-middleware-stack (concat default-middlewares
((apply comp (reverse stack)) unknown-op)))
#_(defn- output-subscriptions
(fn [{:keys [op sub unsub] :as msg}]
(case op
"sub" ;; TODO
(h msg))))
(defrecord Server [server-socket port open-transports transport greeting handler]
(close [this] (stop-server this))
;; TODO here for backward compat with 0.2.x; drop eventually
(deref [this] this))
; IRecord not available in 1.2.0
(eval '(defmethod print-method Server
[s w]
((get-method print-method clojure.lang.IRecord) s w)))
(catch Throwable _))
(defn start-server
"Starts a socket-based nREPL server. Configuration options include:
* :port — defaults to 0, which autoselects an open port on localhost
* :bind — bind address, by default any (
* :handler — the nREPL message handler to use for each incoming connection;
defaults to the result of `(default-handler)`
* :transport-fn — a function that, given a corresponding
to an incoming connection, will return an value satisfying the protocol for that Socket.
* :ack-port — if specified, the port of an already-running server
that will be connected to to inform of the new server's port.
Useful only by Clojure tooling implementations.
Returns a (map) handle to the server that is started, which may be stopped
either via `stop-server`, (.close server), or automatically via `with-open`.
The port that the server is open on is available in the :port slot of the
server map (useful if the :port option is 0 or was left unspecified."
[& {:keys [port bind transport-fn handler ack-port greeting-fn] :or {port 0}}]
(let [bind-addr (if bind (InetSocketAddress. bind port) (InetSocketAddress. port))
ss (ServerSocket. port 0 (.getAddress bind-addr))
server (assoc
(Server. ss
(.getLocalPort ss)
(atom #{})
(or transport-fn t/bencode)
(or handler (default-handler)))
;; TODO here for backward compat with 0.2.x; drop eventually
:ss ss)]
(future (accept-connection server))
(when ack-port
(ack/send-ack (:port server) ack-port))
Jump to Line
Something went wrong with that request. Please try again.