Search code examples
clojureclojure-repl

custom accept function in start-server clojure


I'm trying to run clojure.core.server/start-server but instead of using the repl I want a custom function as accept option. I'm following this post where a repl server is executed as

clojure -X clojure.core.server/start-server :name '"server"' :port 5555 :accept clojure.core.server/repl :server-daemon false

What requirements do I need to pass a function to accept opt? What if I only want to print the request from any connection? Using something like clojure.core/println didn't work

clojure -X clojure.core.server/start-server :name '"server"' :port 5555 :accept clojure.core/println :server-daemon false

btw, I can't even run start-server on the repl itself, I've got the error everytime a made a request. Is it possible to run it from the repl?

(clojure.core.server/start-server {:name "server" :port 9000 :accept clojure.core.server/repl :server-daemon false})
#object[java.net.ServerSocket 0x25b865b5 "ServerSocket[addr=localhost/127.0.0.1,localport=9000]"]
user=> Exception in thread "Clojure Connection server 1" java.lang.ClassCastException: class clojure.core.server$repl cannot be cast to class clojure.lang.Named (clojure.core.server$repl and clojure.lang.Named are in unnamed module of loader 'app')
        at clojure.core$namespace.invokeStatic(core.clj:1612)
        at clojure.core.server$accept_connection.invokeStatic(server.clj:73)
        at clojure.core.server$start_server$fn__8998$fn__8999$fn__9001.invoke(server.clj:117)
        at clojure.lang.AFn.run(AFn.java:22)
        at java.base/java.lang.Thread.run(Thread.java:833)


Solution

  • The start-server function states, that :accept must be a namespaced symbol (so this is the reason, why you see the error in your REPL (you are passing a variable; use 'clojure.core/println instead -- note the leading ').

    As for passing your own function, it helps to take a look at the function clojure.core.server/accept-connection, what is actually going on. So basically your :accept function gets called with *in*, *out*, *err* redirected (and also a binding of *session* with some book-keeping) and the :args. So you want to read/write on stdin/stdout in your function and parameterize with the given arguments.

    E.g. create a file src/so.clj with the following content:

    (ns so)
    
    (defn accept
      [prefix]
      (loop []
        (println prefix (read-line))
        (recur)))
    

    Then start the server from the same root:

    clojure -X clojure.core.server/start-server :name '"server"' :port 5555 \
      :accept so/accept \
      :args '[">>>"]' \
      :server-daemon false
    

    Note the :accept symbol (it's coming from the shell, so Clojure reads that as a symbol) points to the namespace and the function within. Make sure, things match up with the class-path (src is a default source path for the Clojure CLI).

    Also note the passing of the :args represented as an EDN array. Inside accept-connection the call to your function is then (apply accept args), so you can assume regular arguments in your accept-fn.

    Then test your server with e.g. telnet:

    % telnet localhost 5555
    Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    hello
    >>> hello
    world
    >>> world