server.clj
Branches > main > e26c58d > files / src / net / bowmap / server.clj
(ns net.bowmap.server
(:require
[aleph.tcp :as tcp]
[clojure.alpha.spec :as spec]
[clojure.core.async :refer [chan io-thread]]
[dev.grahp.spec :as S]
[manifold.stream :as s]
[net.bowmap.network.packet :as pk]
[net.bowmap.network.packet.read :as read :refer [->varint]])
(:import
[io.netty.bootstrap ServerBootstrap]
[io.netty.buffer ByteBuf Unpooled]
[io.netty.channel ChannelOption]))
(defn packet-framer-xf
"Transducer that frames bytes from incoming byte arrays and outputs ByteBufs of
framed serialized serverbound packets."
[rf]
(let [buf (Unpooled/buffer)]
(fn
([] (rf))
([res] (rf res))
([res input]
(ByteBuf/.writeBytes buf input)
(loop [res res]
(if (read/can-read-packet? buf)
(let [len (->varint buf)
res' (rf res (ByteBuf/.copy (ByteBuf/.readRetainedSlice buf len)))]
(ByteBuf/.discardReadBytes buf)
(recur res'))
res))))))
(defn logging-handler [handler logger-fn]
(fn [in out]
(try
(handler in out)
(catch Throwable t
(logger-fn t)))))
(defn- handle-client [handler logger-fn stream info]
(let [packets-in (chan 1 packet-framer-xf)
packets-out (chan)
handler (logging-handler handler logger-fn)]
(s/connect stream packets-in)
(s/connect packets-out stream)
(io-thread (handler packets-in packets-out))))
; Could return a channel of clients?
; Make sure this is just kept an interface, it really only needs to know about packets-in, packets-out, and then maybe how to close/disconnect?
(defn start-server!
"Starts a server at the port, calling 'handler' with an 'in' and 'out' channel of packets."
[& {:keys [handler port logger-fn]}]
(tcp/start-server (fn [stream info]
(handle-client handler logger-fn stream info))
{:port port
:bootstrap-transform #(ServerBootstrap/.childOption % ChannelOption/TCP_NODELAY true)}))