├── .gitignore
├── static
├── bg-content.png
├── index.html
└── chartroom.css
├── run
├── README.md
├── project.clj
├── src-cljs
└── main.cljs
└── src
└── main.clj
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
--------------------------------------------------------------------------------
/static/bg-content.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/http-kit/chat-websocket/HEAD/static/bg-content.png
--------------------------------------------------------------------------------
/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | rm -rf classes
3 | mkdir classes
4 |
5 | CP="classes:$(lein classpath):examples/websocket/"
6 |
7 | java -cp "$CP" \
8 | clojure.main -m main
9 |
10 |
11 |
12 | # -Xdebug -Xrunjdwp:transport=dt_socket,address=9092,server=y,suspend=n \
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chartroom, websocket demo
2 |
3 | http-kit's websocket support is documented on [http-kit.org](http://http-kit.org/server.html#channel)
4 |
5 |
6 | ### How to run it
7 |
8 | ```sh
9 | lein cljsbuild once
10 | ./run
11 | # view it on http://127.0.0.1:9899
12 | ```
13 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | chartroom, http-kit's websocket demo
7 |
8 |
9 |
10 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject org.httpkit/chat-websocket "1.0"
2 | :description "Realtime chat by utilizing http-kit's websocket support"
3 | :dependencies [[org.clojure/clojure "1.4.0"]
4 | [ring/ring-core "1.1.6"]
5 | [compojure "1.0.2"]
6 | [org.clojure/data.json "0.1.2"]
7 | [org.clojure/tools.logging "0.2.3"]
8 | [ch.qos.logback/logback-classic "1.0.1"]
9 | [http-kit "2.1.5"]]
10 | :warn-on-reflection true
11 | :min-lein-version "2.0.0"
12 | :main main
13 | :test-paths ["test"]
14 | :plugins [[lein-swank "1.4.4"]
15 | [lein-cljsbuild "0.3.0"]]
16 | :cljsbuild {
17 | :builds [{:source-paths ["src-cljs"]
18 | :compiler {:output-to "static/main.js"
19 | :optimizations :whitespace
20 | :pretty-print true}}]}
21 | :license {:name "Apache License, Version 2.0"
22 | :url "http://www.apache.org/licenses/LICENSE-2.0.html"})
23 |
--------------------------------------------------------------------------------
/src-cljs/main.cljs:
--------------------------------------------------------------------------------
1 | (ns main)
2 |
3 | (def i (js/$ "#i"))
4 | (def history (js/$ "#history"))
5 |
6 | (defn- now []
7 | (quot (.getTime (js/Date.)) 1000))
8 |
9 | (def max-id (atom 0))
10 |
11 | (defn add-msg [msg]
12 | (let [t (str "" (- (now) (.-time msg)) "s ago")
13 | author (str "" (.-author msg) ": ")]
14 | (.append history (str "" author (.-msg msg) t ""))))
15 |
16 | (def conn
17 | (js/WebSocket. "ws://127.0.0.1:9899/ws"))
18 |
19 | (set! (.-onopen conn)
20 | (fn [e]
21 | (.send conn
22 | (.stringify js/JSON (js-obj "command" "getall")))))
23 |
24 | (set! (.-onerror conn)
25 | (fn []
26 | (js/alert "error")
27 | (.log js/console js/arguments)))
28 |
29 | (set! (.-onmessage conn)
30 | (fn [e]
31 | (let [msgs (.parse js/JSON (.-data e))]
32 | (doseq [msg msgs]
33 | (if (> (.-id msg) (.-state max-id))
34 | (do
35 | (add-msg msg)
36 | (swap! max-id #(.-id msg))))))))
37 |
38 | (defn send-to-server []
39 | (let [msg (.trim js/$ (.val i))
40 | author (.trim js/$ (.val (js/$ "#name")))]
41 | (if msg
42 | (do
43 | (.send conn (.stringify js/JSON (js-obj "msg" msg "author" author)))
44 | (.val i "")))))
45 |
46 | (.click (js/$ "#send") send-to-server)
47 |
48 | (.keyup (.focus i)
49 | (fn [e]
50 | (if (= (.-which e) 13) (send-to-server))))
51 |
--------------------------------------------------------------------------------
/static/chartroom.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | body {
7 | background: #F6F6F6 url("/bg-content.png") repeat center top;
8 | font-family: monospace;
9 | }
10 |
11 |
12 | #i {
13 | width: 600px;
14 | padding: 6px 3px;
15 | font-size: 14px;
16 | }
17 |
18 | #name {
19 | width: 100px;
20 | font-size: 14px;
21 | padding: 5px 3px;
22 | margin-right: 20px;
23 | }
24 |
25 | #links {
26 | position: absolute;
27 | right: 30px;
28 | bottom: 10px;
29 | }
30 |
31 | a {
32 | color: #369;
33 | text-decoration: none;
34 | }
35 |
36 | a:hover {
37 | padding: 2px;
38 | color: white;
39 | text-decoration: none;
40 | background: #039;
41 | }
42 |
43 | #links li {
44 | list-style: none;
45 | float: right;
46 | }
47 |
48 | .author {
49 | font-size: 13px;
50 | color: #333;
51 | }
52 |
53 | #history-wrap {
54 | overflow-y: auto;
55 | height: 500px;
56 | box-shadow: 0 0 4px #bbb;
57 | margin-bottom: 20px;
58 | }
59 |
60 | #history {
61 | margin: 0;
62 | padding: 0;
63 | /* border: 1px solid #ccc; */
64 | }
65 |
66 | #history li {
67 | list-style: none;
68 | padding: 7px;
69 | border-bottom: 1px solid #eee;
70 | }
71 |
72 | .time {
73 | color: #888;
74 | font-size: 12px;
75 | float: right;
76 | padding-right: 20px;
77 | }
78 |
79 | #page-wrap {
80 | margin: 0 auto;
81 | background: white;
82 | width: 900px;
83 | position: relative;
84 | height: 800px;
85 | padding: 30px;
86 | }
87 |
--------------------------------------------------------------------------------
/src/main.clj:
--------------------------------------------------------------------------------
1 | (ns main
2 | (:gen-class)
3 | (:use org.httpkit.server
4 | [ring.middleware.file-info :only [wrap-file-info]]
5 | [clojure.tools.logging :only [info]]
6 | [clojure.data.json :only [json-str read-json]]
7 | (compojure [core :only [defroutes GET POST]]
8 | [route :only [files not-found]]
9 | [handler :only [site]]
10 | [route :only [not-found]])))
11 |
12 | (defn- now [] (quot (System/currentTimeMillis) 1000))
13 |
14 | (def clients (atom {})) ; a hub, a map of client => sequence number
15 |
16 | (let [max-id (atom 0)]
17 | (defn next-id []
18 | (swap! max-id inc)))
19 |
20 | (defonce all-msgs (ref [{:id (next-id), ; all message, in a list
21 | :time (now)
22 | :msg "this is a live chatroom, have fun",
23 | :author "system"}]))
24 |
25 | (defn mesg-received [msg]
26 | (let [data (read-json msg)]
27 | (info "mesg received" data)
28 | (when (:msg data)
29 | (let [data (merge data {:time (now) :id (next-id)})]
30 | (dosync
31 | (let [all-msgs* (conj @all-msgs data)
32 | total (count all-msgs*)]
33 | (if (> total 100)
34 | (ref-set all-msgs (vec (drop (- total 100) all-msgs*)))
35 | (ref-set all-msgs all-msgs*))))))
36 | (doseq [client (keys @clients)]
37 | ;; send all, client will filter them
38 | (send! client (json-str @all-msgs)))))
39 |
40 | (defn chat-handler [req]
41 | (with-channel req channel
42 | (info channel "connected")
43 | (swap! clients assoc channel true)
44 | (on-receive channel #'mesg-received)
45 | (on-close channel (fn [status]
46 | (swap! clients dissoc channel)
47 | (info channel "closed, status" status)))))
48 |
49 | (defroutes chartrootm
50 | (GET "/ws" [] chat-handler)
51 | (files "" {:root "static"})
52 | (not-found "Page not found.
" ))
53 |
54 | (defn- wrap-request-logging [handler]
55 | (fn [{:keys [request-method uri] :as req}]
56 | (let [resp (handler req)]
57 | (info (name request-method) (:status resp)
58 | (if-let [qs (:query-string req)]
59 | (str uri "?" qs) uri))
60 | resp)))
61 |
62 | (defn -main [& args]
63 | (run-server (-> #'chartrootm site wrap-request-logging) {:port 9899})
64 | (info "server started. http://127.0.0.1:9899"))
65 |
--------------------------------------------------------------------------------