-
-
Notifications
You must be signed in to change notification settings - Fork 97
/
server.clj
executable file
·149 lines (135 loc) · 5.79 KB
/
server.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(ns nrepl.server
"Default server implementations"
{:author "Chas Emerick"}
(:require clojure.pprint
[nrepl.ack :as ack]
[nrepl.middleware :as middleware]
nrepl.middleware.interruptible-eval
nrepl.middleware.load-file
nrepl.middleware.session
[nrepl.misc :refer [log response-for returning]]
[nrepl.transport :as t])
(:import [java.net InetAddress InetSocketAddress ServerSocket Socket SocketException]))
(defn handle*
[msg handler transport]
(try
(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
[^java.io.Closeable x]
(try
(.close x)
(catch java.io.IOException 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)]
(try
(swap! open-transports conj transport)
(when greeting (greeting transport))
(handle handler transport)
(finally
(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? java.io.Closeable t)
(do
(safe-close t)
(disj s t))
s))
% %))))
(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
[#'nrepl.middleware/wrap-describe
#'nrepl.middleware.interruptible-eval/interruptible-eval
#'nrepl.middleware.load-file/wrap-load-file
#'nrepl.middleware.session/add-stdin
#'nrepl.middleware.session/session])
(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 nrepl.middleware/set-descriptor!)."
[& additional-middlewares]
(let [stack (middleware/linearize-middleware-stack (concat default-middlewares
additional-middlewares))]
((apply comp (reverse stack)) unknown-op)))
;; TODO
#_(defn- output-subscriptions
[h]
(fn [{:keys [op sub unsub] :as msg}]
(case op
"sub" ;; TODO
"unsub"
(h msg))))
(defrecord Server [server-socket port open-transports transport greeting handler]
java.io.Closeable
(close [this] (stop-server this)))
(defn start-server
"Starts a socket-based nREPL server. Configuration options include:
* :port — defaults to 0, which autoselects an open port
* :bind — bind address, by default \"::\" (falling back to \"localhost\"
if \"::\" isn't resolved by the underlying network stack)
* :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 java.net.Socket corresponding
to an incoming connection, will return a value satisfying the
nrepl.Transport protocol for that Socket.
* :ack-port — if specified, the port of an already-running server
that will be connected to inform of the new server's port.
Useful only by Clojure tooling implementations.
Returns a (record) 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]}]
(let [port (or port 0)
addr (fn [^String bind ^Integer port] (InetSocketAddress. bind port))
make-ss #(doto (ServerSocket.)
(.setReuseAddress true)
(.bind %))
ss (if bind
(make-ss (addr bind port))
(let [address (addr "::" port)]
(if (.isUnresolved address)
(make-ss (addr "localhost" port))
(try
(make-ss address)
(catch SocketException e
(if (= "Protocol family unavailable" (.getMessage e))
(make-ss (addr "localhost" port))
(throw e)))))))
server (Server. ss
(.getLocalPort ss)
(atom #{})
(or transport-fn t/bencode)
greeting-fn
(or handler (default-handler)))]
(future (accept-connection server))
(when ack-port
(ack/send-ack (:port server) ack-port))
server))