mirror of
https://github.com/penpot/penpot.git
synced 2025-01-09 08:20:45 -05:00
197 lines
5.8 KiB
Clojure
197 lines
5.8 KiB
Clojure
|
(ns uxbox.impl.routing
|
||
|
(:require [clojure.core.async :as a]
|
||
|
[cats.monad.exception :as exc]
|
||
|
[cats.core :as m]
|
||
|
[promissum.core :as p]
|
||
|
[catacumba.core :as ct]
|
||
|
[catacumba.serializers :as sz]
|
||
|
[catacumba.impl.websocket :as ws]
|
||
|
[catacumba.impl.handlers :as hs]))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Helpers
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defn encode
|
||
|
[data]
|
||
|
(sz/bytes->str (sz/encode data :transit+json)))
|
||
|
|
||
|
(defn decode
|
||
|
[data]
|
||
|
(sz/decode (sz/str->bytes data) :transit+json))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Protocol Definition
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defprotocol IHandlerResponse
|
||
|
(-handle-response [_ context frameid options] "Handle the response."))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Implementation
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(declare response)
|
||
|
|
||
|
(defn- generic-error-handler
|
||
|
[context error]
|
||
|
(if (instance? clojure.lang.ExceptionInfo error)
|
||
|
(response :error {:message (.getMessage error)
|
||
|
:data (ex-data error)})
|
||
|
(response :error {:message (str error)})))
|
||
|
|
||
|
(defn- handle-error
|
||
|
[error frameid context options]
|
||
|
(let [on-error (::on-error options generic-error-handler)
|
||
|
response (on-error context error)]
|
||
|
(-handle-response response context frameid options)))
|
||
|
|
||
|
(defmulti handle-frame
|
||
|
(fn [frame handler context options]
|
||
|
(:cmd frame)))
|
||
|
|
||
|
(defmethod handle-frame :default
|
||
|
[frame handler context options]
|
||
|
(let [frameid (:id frame)
|
||
|
response (exc/try-on (handler context frame))]
|
||
|
(if (exc/success? response)
|
||
|
(-handle-response @response context frameid options)
|
||
|
(let [error (m/extract response)]
|
||
|
(handle-error error frameid context options)))))
|
||
|
|
||
|
(defmethod handle-frame :pong
|
||
|
[frame _ context options]
|
||
|
(let [state (:state context)]
|
||
|
|
||
|
(defn- send-decode-error
|
||
|
[{:keys [out]}]
|
||
|
(let [frame {:cmd :error :id nil :data "Error on deserializing frame."}]
|
||
|
(a/go (a/>! out (encode frame)))))
|
||
|
|
||
|
(defn- initialize
|
||
|
[{:keys [in out ctrl] :as context} options]
|
||
|
(a/go
|
||
|
(let [received (a/<! in)
|
||
|
result (exc/try-on (decode received))]
|
||
|
(if (and (exc/success? result)
|
||
|
(= (:cmd @result) :hello))
|
||
|
(a/>! out (encode {:cmd :hello}))
|
||
|
(do
|
||
|
(a/<! (send-decode-error context))
|
||
|
(a/close! out))))))
|
||
|
|
||
|
(def ^:private
|
||
|
pong-frames-xform
|
||
|
(comp
|
||
|
(map decode)
|
||
|
(filter #(= (:cmd %) :pong))))
|
||
|
|
||
|
(def ^:private
|
||
|
user-frames-xform
|
||
|
(comp
|
||
|
(map decode)
|
||
|
(filter #(not= (:cmd %) :pong))))
|
||
|
|
||
|
(defn- pong-frames
|
||
|
[mult context]
|
||
|
(let [ch (a/chan 1 pong-frames-xform (fn [_] (send-decode-error context)))]
|
||
|
(a/tap mult ch true)))
|
||
|
|
||
|
(defn- user-frames
|
||
|
[mult context]
|
||
|
(let [ch (a/chan 1 user-frames-xform (fn [_] (send-decode-error context)))]
|
||
|
(a/tap mult ch true)))
|
||
|
|
||
|
(defn- keepalive-loop
|
||
|
[{:keys [out] :as context} mult]
|
||
|
(let [in (ping-frames mult context)]
|
||
|
(a/go-loop []
|
||
|
(let [frame {:cmd :ping :id (random-uuid)}]
|
||
|
(a/>! out (encode frame))
|
||
|
(let [[v ch] (a/alts! [in (a/timeout 1000)])]
|
||
|
(if (= ch in)
|
||
|
(do
|
||
|
(a/<! (a/timeout 1000))
|
||
|
(recur))
|
||
|
(a/close! out)))))))
|
||
|
|
||
|
(defn- messages-loop
|
||
|
[{:keys [out] :as context} mult]
|
||
|
(let [in (user-frames mult context)]
|
||
|
(a/go-loop []
|
||
|
(when-let [frame (a/<! in)]
|
||
|
(let [result (exc/try-on (decode frame))]
|
||
|
(if (exc/success? result)
|
||
|
(handle-frame @result handler context options)
|
||
|
(a/<! (send-decode-error context))))
|
||
|
(recur)))))
|
||
|
|
||
|
(defn- dispatcher-loop
|
||
|
[{:keys [in] :as context} handler options]
|
||
|
(let [mult (a/mult in)]
|
||
|
(keepalive-loop context mult)
|
||
|
(messages-loop context mult)))
|
||
|
|
||
|
(defn- dispatcher
|
||
|
[{:keys [in out ctrl] :as context} handler options]
|
||
|
(a/go-loop []
|
||
|
(when-let [msg (a/<! ctrl)]
|
||
|
(if (= msg :close)
|
||
|
(let [container (::on-close-handlers context)]
|
||
|
(doseq [item @container]
|
||
|
(exc/try-on (item)))
|
||
|
(reset! container []))
|
||
|
(recur))))
|
||
|
(a/go
|
||
|
(a/<! (initialize context options))
|
||
|
|
||
|
;; Start dispatcher loop
|
||
|
(a/<! (dispatcher-loop context handler options))
|
||
|
|
||
|
;; closing in any case
|
||
|
(a/close! out)))
|
||
|
|
||
|
(extend-type java.util.concurrent.CompletableFuture
|
||
|
IHandlerResponse
|
||
|
(-handle-response [response context frameid options]
|
||
|
(letfn [(on-resolve [response]
|
||
|
(-handle-response response context frameid options))
|
||
|
(on-reject [error]
|
||
|
(handle-error error frameid context options))]
|
||
|
(-> response
|
||
|
(p/then on-resolve)
|
||
|
(p/catch on-reject)))))
|
||
|
|
||
|
(defrecord Response [data cmd]
|
||
|
IHandlerResponse
|
||
|
(-handle-response [this context frameid options]
|
||
|
(let [frame (into {:id frameid} this)
|
||
|
output (:out context)]
|
||
|
(a/put! output (encode frame)))))
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
;; Public Api
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
(defn response
|
||
|
([data]
|
||
|
(response :response data))
|
||
|
([type data]
|
||
|
(map->Response {:data data :cmd type})))
|
||
|
|
||
|
(defn on-close
|
||
|
[context handler]
|
||
|
{:pre [(fn? handler) (::on-close-handlers context)]}
|
||
|
(let [container (::on-close-handlers context)]
|
||
|
(swap! container conj handler)))
|
||
|
|
||
|
(defn router
|
||
|
([handler]
|
||
|
(router handler {}))
|
||
|
([handler options]
|
||
|
(fn [context]
|
||
|
(let [context (assoc context
|
||
|
::on-close-handlers (atom [])
|
||
|
:state (atom {}))]
|
||
|
(ws/websocket context #(dispatcher % handler options))))))
|