├── .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 |
11 |

chartroom, http-kit's websocket demo

12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | 24 |
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 | --------------------------------------------------------------------------------