Возиться с сервером Push
Хорошо … Я злодей. Я серверный толчок. Я сделал несколько удивительных семантики и реализации для push сервера в Lift .
Я начал работать с Clojure через Project Plugh . Первый шаг в моем создании набора инструментов для использования в Clojure-land.
Отличная вещь, которую я создал до сих пор, заключается в том, что вы можете запустить приложение чата, просто используя каналы core.async .
Приложение чата
Таким образом, это приложение чат с помощью AngularJS , модифицированный лязг код, и некоторые вещи , я был пивоварения в Plugh.
Вид
Во-первых, давайте посмотрим на представление … это стандартный код Angular:
<h2>Chat</h2>
<div ng-controller="Chatter">
<ul>
<li ng-repeat="n in chats">{{n}}</li>
</ul>
<input ng-model="line"><button ng-click="send()">Send</button>
</div>
Довольно просто
Клиентская сторона
Вот контроллер AngularJS, написанный на ClojureScript:
(def server-chan (pc/server-chan "The Chat Server"))
Определите канал, который на стороне сервера содержит наш чат-сервер.
(def.controller pc/m Chatter [$scope] (assoc! $scope :chats (clj->js []))
Определите массив JavaScript для chats.
(assoc! $scope :line "")
И line переменная.
(defn.scope send []
(let [msg (:line $scope)]
(go
(>! server-chan {:msg msg})))
(assoc! $scope :line ""))
Когда пользователь нажимает Send кнопку, возьмите текущее значение line и отправьте его как сообщение на сервер.
(go
(let [rc (chan)]
(>! server-chan {:add rc})
(while true
(let [chats (<! rc)]
(in-scope (doseq [m chats] (.push (:chats $scope) m)))
))))
)
И зарегистрируйте себя в качестве слушателя на сервере, создав локальный канал chan и отправив его в :add сообщении на сервер.
Затем дождитесь сообщения от сервера и добавьте их в chats массив в рамках Angular (чтобы Angular взял изменения и отобразил их).
Серверная сторона
Вот код на стороне сервера:
(go (let [server-chan (make-server-chan "The Chat Server")]
Определите названный канал на стороне сервера.
(while true (let [v (<! server-chan)]
Получите следующее сообщение.
(when-let [n (:add v)] (swap! listeners #(conj % n)) (>! n @chats))
Добавьте к слушателям.
(when-let [chat-msg (:msg v)] (swap! chats #(conj % chat-msg)) (doseq [ch @listeners] (>! ch [chat-msg])))))))
обработайте сообщение чата и повторно отправьте его всем слушателям.
Boom. Вот и все.
Я хочу свой HTTP
Итак, как мы отправляем сообщения через HTTP и имеем дело с вещами?
Автоканалы на стороне клиента
Первое, что мы делаем, это меняем сериализатор для канала:
(extend-protocol IPrintWithWriter cljs.core.async.impl.channels/ManyToManyChannel (-pr-writer [chan writer opts] (let [guid (find-guid-for-chan chan)] (-write writer "#guid-chan\"") (-write writer guid) (-write writer "\""))))
Приведенный выше код ищет guid канал. Если нет guid для канала, один создан. Итак, у нас есть текстовое представление для канала для отправки на сервер.
И когда мы читаем каналы назад, мы используем:
(defn ^:private guid-chan-reader [s] (find-chan-for-guid (str s))) (cr/register-tag-parser! "guid-chan" guid-chan-reader)
Читатель канала находит соответствующий канал для guid. Но если guid не было замечено, мы создаем прокси обратно на сервер:
(defn find-chan-for-guid [guid]
(or (get @guid-to-chan guid)
(let [nc (chan)]
(register-chan nc guid)
(go
(while true
(let [msg (<! nc)]
(send-to-server (pr-str {:chan nc :msg msg}))
)))
nc)
))
И этот прокси ждет сообщений и затем отправляет сообщение на сервер с каналом guid.
Наконец, когда мы получаем сообщение от сервера обратно клиенту, процесс переворачивается:
(fn [me] (do (let [info (.-data me) parsed (cr/read-string info) chan (:chan parsed) msg (:msg parsed)] (if (and chan msg) (do (go (>! chan msg))) )))))
Мы ищем канал из сообщения :chan guid и отправляем сообщение этому каналу.
Серверная сторона
На стороне сервера почти то же самое, за исключением того, что заклинания сериализации Clojure незначительно отличаются от заклинаний ClojureScript.
Вот наш писатель:
(defmethod print-method clojure.core.async.impl.channels.ManyToManyChannel [chan, ^java.io.Writer w] (let [guid (find-guid-for-chan chan)] (.write w "#guid-chan\"") (.write w guid) (.write w "\"")))
И наш читатель (мы используем edn ) код для чтения, потому что он не исполняет код с провода:
(edn/read-string {:readers {'guid-chan find-chan-for-guid}} data)
А вот полный код «читать с провода и отправить сообщение»:
(binding [*websocket* ch]
(let [thing (edn/read-string
{:readers {'guid-chan find-chan-for-guid}} data)
chan (:chan thing)
msg (:msg thing)]
(if (and chan msg)
(go (>! chan msg))))))))
Наконец, наш прокси-код для клиента:
(go
(loop []
(let [msg (<! nc)]
(hs/send! socket {:body (pr-str {:chan nc :msg msg})})
)
(recur)))
Вот и все.
Делать
В коде еще многое предстоит сделать, включая работу с временными отбрасываниями веб-сокетов, распаковкой сообщений, необязательными сеансами, сборкой мусора прокси каналов при закрытии канала на другой стороне.
Но ключевые части кросс-адресного пространства core.async через HTTP находятся в Plugh.
Ура!
благодаря
Сообщество Clojure довольно удивительно. Выражаем благодарность Дэвиду Нолену ( David Nolen), который поддерживает материал ClojureScript core.async вместе с десятками других пакетов.