When working on the open source project enclojure-clojure-lib, we created a org.enclojure.repl library which have all the necessary server and client functions for creating a socket based clojure.main/repl in your application.
You can add the following code in your application which starts a socket REPL at the speicified port and wait for a connection.
;Use specified socket
(def server-socket (org.enclojure.repl.main/run-repl-server 11345))
;Use next available socket port
(def server-socket (org.enclojure.repl.main/run-repl-server 0))
(def server-port (.getLocalPort server-socket))
You can have your monitoring application - connect to the remote REPL as follows:
(def+ {:keys [repl-fn result-fn close-fn]} (create-repl-client host port))
create-repl-client returns a map:
{:repl-fn repl-fn
:result-fn result-fn
:close-fn close-fn}
You can listen to the incoming socket data using the result-fn in another thread:
(.start (Thread. #(write-to-window (result-fn))))
You can send the command to the remote REPL as follows:
(repl-fn "(give-me-status)")
To close this client connection you call
(close-fn)
You can use the following command to shutdown the remote REPL server
(repl-fn "(org.enclojure.repl.main/close-server)")
If you use Enclojure Plugin in Netbeans - You can create a REPL window connected to your REPL enabled application. Check out the Remote unmanaged REPLs.
You can add the following code in your application which starts a socket REPL at the speicified port and wait for a connection.
;Use specified socket
(def server-socket (org.enclojure.repl.main/run-repl-server 11345))
;Use next available socket port
(def server-socket (org.enclojure.repl.main/run-repl-server 0))
(def server-port (.getLocalPort server-socket))
You can have your monitoring application - connect to the remote REPL as follows:
(def+ {:keys [repl-fn result-fn close-fn]} (create-repl-client host port))
create-repl-client returns a map:
{:repl-fn repl-fn
:result-fn result-fn
:close-fn close-fn}
You can listen to the incoming socket data using the result-fn in another thread:
(.start (Thread. #(write-to-window (result-fn))))
You can send the command to the remote REPL as follows:
(repl-fn "(give-me-status)")
To close this client connection you call
(close-fn)
You can use the following command to shutdown the remote REPL server
(repl-fn "(org.enclojure.repl.main/close-server)")
If you use Enclojure Plugin in Netbeans - You can create a REPL window connected to your REPL enabled application. Check out the Remote unmanaged REPLs.
Embedding a Clojure REPL in your application for Local use (Non-socket)
Sometimes you also want to create a REPL for use with in your application. For example: Enclojure has a Netbeans IDE REPL which is running in Netbeans process and hence can manipulate the Netbeans IDE via the REPL. This helped us with the Enclojure plugin development. You can create a REPL for local use in Application as follows:
(def+ {:keys [repl-fn result-fn]} (create-clojure-repl))
You can use the repl-fn and result-fn as explained before. Following is the code snippet copied from the org.enclojure.repl.main. create-clojure-repl is wrapping the clojure.main/repl to start the REPL in a thread. This is the function used in both local and remote Socket based REPL.
(def *printStackTrace-on-error* false)
(defn is-eof-ex? [throwable]
(and (instance? clojure.lang.LispReader$ReaderException throwable)
(or
(.startsWith (.getMessage throwable) "java.lang.Exception: EOF while reading")
(.startsWith (.getMessage throwable) "java.io.IOException: Write end dead"))))
(defn create-clojure-repl []
"This function creates an instance of clojure repl using piped in and out.
It returns a map of two functions repl-fn and result-fn - first function
can be called with a valid clojure expression and the results are read using
the result-fn."
(let [cmd-wtr (PipedWriter.)
result-rdr (PipedReader.)
piped-in (clojure.lang.LineNumberingPushbackReader. (PipedReader. cmd-wtr))
piped-out (PrintWriter. (PipedWriter. result-rdr))
repl-thread-fn #(binding [*printStackTrace-on-error* *printStackTrace-on-error*
*in* piped-in
*out* piped-out
*err* *out*]
(try
(clojure.main/repl
:init (fn [] (in-ns 'user))
:read (fn [prompt exit]
(read))
:caught (fn [e]
(when (is-eof-ex? e)
(throw e))
(if *printStackTrace-on-error*
(.printStackTrace e *out*)
(prn (clojure.main/repl-exception e)))
(flush))
:need-prompt (constantly true))
(catch clojure.lang.LispReader$ReaderException ex
(prn "REPL closing"))
(catch java.lang.InterruptedException ex)
(catch java.nio.channels.ClosedByInterruptException ex)))]
(.start (Thread. repl-thread-fn))
{:repl-fn (fn [cmd]
(if (= cmd ":CLOSE-REPL")
(do
(.close cmd-wtr)
(.close result-rdr))
(do
(.write cmd-wtr cmd)
(.flush cmd-wtr))))
;//??Using CharArrayWriter to build the string from each read of one byte
;Once there is nothing to read than this function returns the string read.
;Using partial so that CharArrayWriter is only created and once and reused.
;There could be better way.
:result-fn (partial
(fn [wtr]
(.write wtr (.read result-rdr))
(if (.ready result-rdr)
(recur wtr)
(let [result (.toString wtr)]
(.reset wtr)
result)))
(CharArrayWriter.))}))