server.clj

Branches > refactor/use-spec-1 > 7014976 > 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)}))