Возиться с сервером 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 вместе с десятками других пакетов.