Skip to content

Instantly share code, notes, and snippets.

@muraiki
Created October 8, 2014 22:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save muraiki/11831288662a0c07b7d4 to your computer and use it in GitHub Desktop.
Save muraiki/11831288662a0c07b7d4 to your computer and use it in GitHub Desktop.
ajax-to-websocket
(ns ajax-to-websocket.core
(:require [org.httpkit.server :as server]
[org.httpkit.client :as client]
[clojure.data.json :as json]
[clojure.data :as data]
[clojure.core.async :refer [chan <! >! go timeout onto-chan]]))
(defn channel-closed
"called when websocket channel is closed"
[status]
(println "channel closed: " status))
(defn parse-url
"gets the url from the json sent by the client"
[data]
(:url (json/read-str data :key-fn keyword)))
(defn make-req
"grabs the url the client requested. returns name and body json response as map"
[url-map]
(let [url (:url url-map)
res @(client/get url {:as :text})]
{:name (:name url-map),
:body (json/read-str (:body res) :key-fn keyword)}))
(defn is-diff? [a-diff]
"given the output vector from data/diff, return if different in any way"
(if (and (nil? (first a-diff))
(nil? (second a-diff)))
false
true))
(defn send-message
"Sends a message to the websocket channel, converting it to json"
[websocket message]
(server/send! websocket (json/write-str message)))
(defn send-message-if-diff
"Sends a message to the websocket channel if the new data is different from the most recent state"
[websocket json-res last-state]
(let [url-name (keyword (:name json-res))
res (:body json-res)]
(when (is-diff? (data/diff res (url-name @last-state)))
(swap! last-state assoc-in [url-name] res)
(send-message websocket
{:name (:name json-res),
:res (:body json-res)}))))
(defn socket-handler
"main websocket handler"
[request]
(server/with-channel request channel
(server/on-close channel channel-closed)
(server/on-receive channel
(fn [data]
(let [url-maps (json/read-str data :key-fn keyword)
poll-channel (chan)
last-state (atom {})]
(go (while (server/open? channel)
(send-message-if-diff channel (<! poll-channel) last-state)))
; parallel map, polling the requested urls
; sending results to poll-channel one at a time
(go (while (server/open? channel)
(onto-chan poll-channel (doall (pmap make-req url-maps)))
(<! (timeout 10000)))))))))
(defn -main [& args]
"start the server"
(server/run-server socket-handler {:port 8080}))
<html>
<head>
</head>
<body>
<script>
var socket = new WebSocket("ws://localhost:8080/ws");
socket.onopen = function (event) {
var toSend = [
{
name: "jsontest",
url: "http://ip.jsontest.com/"
}
];
socket.send(JSON.stringify(toSend));
console.log("sent");
};
socket.onmessage = function (event) {
var res = JSON.parse(event.data);
console.log(res);
var newDiv = document.createElement("div");
var newContent = document.createTextNode(res.ip);
newDiv.appendChild(newContent);
document.body.appendChild(newDiv);
}
</script>
</body>
</html>
(defproject ajax-to-websocket "0.1.0-SNAPSHOT"
:description "Server that helps translate a json api that relies on ajax polling to websockets"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
[org.clojure/data.json "0.2.5"]
[http-kit "2.1.16"]]
:main ^:skip-aot ajax-to-websocket.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})
@muraiki
Copy link
Author

muraiki commented Oct 8, 2014

Here's the error:

Exception in thread "async-dispatch-4" java.lang.NullPointerException
    at ajax_to_websocket.core$send_message_if_diff.invoke(core.clj:43)
    at ajax_to_websocket.core$socket_handler$fn__7110$fn__7127$state_machine__5198__auto____7128$fn__7130.invoke(core.clj:59)
    at ajax_to_websocket.core$socket_handler$fn__7110$fn__7127$state_machine__5198__auto____7128.invoke(core.clj:59)
    at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:940)
    at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:944)
    at clojure.core.async.impl.ioc_macros$take_BANG_$fn__5214.invoke(ioc_macros.clj:953)
    at clojure.core.async.impl.channels.ManyToManyChannel$fn__902.invoke(channels.clj:133)
    at clojure.lang.AFn.run(AFn.java:22)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment