├── .gitignore
├── dev
├── user.clj
└── dev.clj
├── resources
├── events.js
└── logback.xml
├── src
└── chatserver
│ ├── main.clj
│ ├── website.clj
│ ├── system.clj
│ └── events.clj
├── project.clj
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /classes
3 | /checkouts
4 | pom.xml
5 | pom.xml.asc
6 | *.jar
7 | *.class
8 | /.lein-*
9 | /.nrepl-port
10 | .hgignore
11 | .hg/
12 |
--------------------------------------------------------------------------------
/dev/user.clj:
--------------------------------------------------------------------------------
1 | (ns user)
2 |
3 | ;; This is an old trick from Pedestal. When system.clj doesn't compile,
4 | ;; it can prevent the REPL from starting, which makes debugging very
5 | ;; difficult. This extra step ensures the REPL starts, no matter what.
6 |
7 | (defn dev
8 | []
9 | (require 'dev)
10 | (in-ns 'dev))
11 |
12 |
13 | (defn go
14 | []
15 | (println "Don't you mean (dev) ?"))
16 |
--------------------------------------------------------------------------------
/resources/events.js:
--------------------------------------------------------------------------------
1 | // POST messages using AJAX
2 | $("#chat").click(function(ev) {
3 | console.log("posting message: " + $("#message").val())
4 | $.ajax({
5 | type: "POST",
6 | url: "/events/messages",
7 | data: $("#message").val(),
8 | contentType: "text/plain"
9 | })
10 | });
11 |
12 |
13 | // Listen for messages using HTML5 Server Sent Events (SSE)
14 | es = new EventSource("/events/events");
15 | es.addEventListener("message",
16 | function(ev) {
17 | console.log("message: " + ev.data)
18 | $("#messages").append("
"+ev.data+"");
19 | });
20 |
--------------------------------------------------------------------------------
/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
6 |
7 |
8 | DEBUG
9 |
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/chatserver/main.clj:
--------------------------------------------------------------------------------
1 | (ns chatserver.main
2 | "Main entry point"
3 | (:require clojure.pprint)
4 | (:gen-class))
5 |
6 | (defn -main [& args]
7 | ;; We eval so that we don't AOT anything beyond this class
8 | (eval '(do (require 'chatserver.system)
9 | (require 'chatserver.main)
10 | (require 'com.stuartsierra.component)
11 |
12 | (require 'clojure.java.browse)
13 |
14 | (println "Starting chatserver")
15 |
16 | (let [system (->
17 | (chatserver.system/new-production-system)
18 | com.stuartsierra.component/start)]
19 |
20 | (println "System started")
21 | (println "Ready...")
22 |
23 | (let [url (format "http://localhost:%d/" (-> system :http-listener-listener :port))]
24 | (println (format "Browsing at %s" url))
25 | (clojure.java.browse/browse-url url))))))
26 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject chatserver "0.1.0-SNAPSHOT"
2 | :description "A chatserver"
3 | :url "http://github.com/juxt/chatserver"
4 |
5 | :exclusions [com.stuartsierra/component]
6 |
7 | :dependencies
8 | [
9 | [hiccup "1.0.5"]
10 | [com.stuartsierra/component "0.2.2"]
11 | [juxt.modular/bidi "0.9.1"]
12 | #_[juxt.modular/http-kit "0.5.4"]
13 | [juxt.modular/aleph "0.0.8"]
14 | [yada "0.2.3"]
15 | [juxt.modular/maker "0.5.0"]
16 | [juxt.modular/wire-up "0.5.0"]
17 | [org.clojure/clojure "1.7.0-alpha4"]
18 | [org.clojure/core.async "0.1.346.0-17112a-alpha"]
19 | [org.clojure/tools.logging "0.2.6"]
20 | [org.clojure/tools.reader "0.8.13"]
21 | [org.slf4j/jcl-over-slf4j "1.7.2"]
22 | [org.slf4j/jul-to-slf4j "1.7.2"]
23 | [org.slf4j/log4j-over-slf4j "1.7.2"]
24 | [org.webjars/jquery "2.1.0"]
25 | [prismatic/plumbing "0.3.5"]
26 | [prismatic/schema "0.3.5"]
27 | [ch.qos.logback/logback-classic "1.0.7" :exclusions [org.slf4j/slf4j-api]]
28 | ]
29 |
30 | :main chatserver.main
31 |
32 | :repl-options {:init-ns user
33 | :welcome (println "Type (dev) to start")}
34 |
35 |
36 | :profiles {:dev {:dependencies [[org.clojure/tools.namespace "0.2.5"]]
37 | :source-paths ["dev"
38 | ]}})
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chatserver
2 |
3 | To demonstrate an asynchronous chatserver using yada's server-sent-events
4 |
5 | Also see https://github.com/juxt/chatclient
6 |
7 | ## Usage
8 |
9 | To run the application, install [Leiningen](http://leiningen.org/) and type
10 |
11 | ```
12 | lein run
13 | ```
14 |
15 | ## Development
16 |
17 | For rapid development, use the REPL from the command line or via your
18 | favorite code editor.
19 |
20 | ```
21 | lein repl
22 | user> (dev)
23 | dev> (go)
24 | ```
25 |
26 | After making code changes, reset your application's state (causing all
27 | your modifications to be reloaded too).
28 |
29 | ```
30 | dev> (reset)
31 | ```
32 |
33 | Rinse and repeat.
34 |
35 | ## Copyright and License
36 |
37 | The MIT License (MIT)
38 |
39 | Copyright © 2015 Malcolm Sparks
40 |
41 | Permission is hereby granted, free of charge, to any person obtaining a copy of
42 | this software and associated documentation files (the "Software"), to deal in
43 | the Software without restriction, including without limitation the rights to
44 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
45 | the Software, and to permit persons to whom the Software is furnished to do so,
46 | subject to the following conditions:
47 |
48 | The above copyright notice and this permission notice shall be included in all
49 | copies or substantial portions of the Software.
50 |
51 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
53 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
54 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
55 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
56 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
57 |
--------------------------------------------------------------------------------
/dev/dev.clj:
--------------------------------------------------------------------------------
1 | (ns dev
2 | (:require
3 | [clojure.pprint :refer (pprint)]
4 | [clojure.reflect :refer (reflect)]
5 | [clojure.repl :refer (apropos dir doc find-doc pst source)]
6 | [clojure.tools.namespace.repl :refer (refresh refresh-all)]
7 | [clojure.java.io :as io]
8 | [com.stuartsierra.component :as component]
9 | [chatserver.system :refer (config new-system-map new-dependency-map new-co-dependency-map)]
10 | [modular.maker :refer (make)]
11 | [modular.wire-up :refer (normalize-dependency-map)]
12 | [clojure.core.async :as a]))
13 |
14 | (def system nil)
15 |
16 | (defn new-dev-system
17 | "Create a development system"
18 | []
19 | (let [config (config)
20 | s-map (->
21 | (new-system-map config)
22 | #_(assoc
23 | ))]
24 | (-> s-map
25 | (component/system-using (new-dependency-map))
26 | )))
27 |
28 | (defn init
29 | "Constructs the current development system."
30 | []
31 | (alter-var-root #'system
32 | (constantly (new-dev-system))))
33 |
34 | (defn start
35 | "Starts the current development system."
36 | []
37 | (alter-var-root
38 | #'system
39 | component/start
40 | ))
41 |
42 | (defn stop
43 | "Shuts down and destroys the current development system."
44 | []
45 | (alter-var-root #'system
46 | (fn [s] (when s (component/stop s)))))
47 |
48 | (defn go
49 | "Initializes the current development system and starts it running."
50 | []
51 | (init)
52 | (start)
53 | :ok
54 | )
55 |
56 | (defn reset []
57 | (stop)
58 | (refresh :after 'dev/go))
59 |
60 | ;; REPL Convenience helpers
61 |
62 | (defn routes []
63 | (-> system :modular-bidi-router-webrouter :routes))
64 |
65 | (defn match-route [path]
66 | (bidi.bidi/match-route (routes) path))
67 |
68 | (defn path-for [path & args]
69 | (apply modular.bidi/path-for
70 | (-> system :modular-bidi-router-webrouter) path args))
71 |
72 | ;; ~/chatserver/dev/dev.clj
73 | (defn get-channel [] (-> system :sse-demo-events-events :channel))
74 |
--------------------------------------------------------------------------------
/src/chatserver/website.clj:
--------------------------------------------------------------------------------
1 | (ns chatserver.website
2 | (:require
3 | [clojure.core.async :refer (go >! ! ch (char (+ (int \A) n)))))
55 | (assoc component :channel ch)))
56 | (stop [component] component)
57 |
58 | RouteProvider
59 | ;; Return a bidi route structure, mapping routes to wrapped
60 | ;; handlers. This additional level of indirection means we can
61 | ;; generate hyperlinks from known keywords.
62 | (routes [component]
63 | ["/" {"index.html" (handler ::index index)
64 | "" (redirect ::index)
65 | "system.html" (handler ::show-system (show-system))
66 | "channel.html" (handler ::show-channel (show-channel (:channel component)))
67 | "drop" (handler ::drop (drop-from-channel (:channel component)))}]))
68 |
69 | ;; While not mandatory, it is common to use a function to construct an
70 | ;; instance of the component. This affords the opportunity to control
71 | ;; the construction with parameters, provide defaults and declare
72 | ;; dependency relationships with other components.
73 |
74 | (defn new-website []
75 | (-> (map->Website {})))
76 |
--------------------------------------------------------------------------------
/src/chatserver/system.clj:
--------------------------------------------------------------------------------
1 | (ns chatserver.system
2 | "Components and their dependency relationships"
3 | (:refer-clojure :exclude (read))
4 | (:require
5 | [clojure.java.io :as io]
6 | [clojure.tools.reader :refer (read)]
7 | [clojure.string :as str]
8 | [clojure.tools.reader.reader-types :refer (indexing-push-back-reader)]
9 | [com.stuartsierra.component :refer (system-map system-using using)]
10 | [modular.maker :refer (make)]
11 | [modular.bidi :refer (new-router new-web-resources)]
12 | [modular.aleph :refer (new-webserver)]
13 | [chatserver.events :refer (new-events-website)]
14 | [chatserver.website :refer (new-website)]))
15 |
16 | (defn ^:private read-file
17 | [f]
18 | (read
19 | ;; This indexing-push-back-reader gives better information if the
20 | ;; file is misconfigured.
21 | (indexing-push-back-reader
22 | (java.io.PushbackReader. (io/reader f)))))
23 |
24 | (defn ^:private config-from
25 | [f]
26 | (if (.exists f)
27 | (read-file f)
28 | {}))
29 |
30 | (defn ^:private user-config
31 | []
32 | (config-from (io/file (System/getProperty "user.home") ".chatserver.edn")))
33 |
34 | (defn ^:private config-from-classpath
35 | []
36 | (if-let [res (io/resource "chatserver.edn")]
37 | (config-from (io/file res))
38 | {}))
39 |
40 | (defn config
41 | "Return a map of the static configuration used in the component
42 | constructors."
43 | []
44 | (merge (config-from-classpath)
45 | (user-config)))
46 |
47 | (defn http-listener-components
48 | [system config]
49 | (assoc system
50 | :http-listener-listener
51 | (new-webserver :port 3001)))
52 |
53 | (defn modular-bidi-router-components
54 | [system config]
55 | (assoc system
56 | :modular-bidi-router-webrouter
57 | (make new-router config)))
58 |
59 | (defn jquery-components
60 | "Serve JQuery resources from a web-jar."
61 | [system config]
62 | (assoc system
63 | :jquery-resources
64 | (->
65 | (make new-web-resources config :uri-context "/jquery" :resource-prefix "META-INF/resources/webjars/jquery/2.1.0" :key :jquery-resources)
66 | (using []))))
67 |
68 | (defn sse-demo-website-components
69 | [system config]
70 | (assoc system
71 | :sse-demo-website-website
72 | (->
73 | (make new-website config)
74 | (using []))))
75 |
76 | (defn sse-demo-events-components
77 | [system config]
78 | (assoc system
79 | :sse-demo-events-events
80 | (->
81 | (make new-events-website config)
82 | (using []))))
83 |
84 | (defn new-system-map
85 | [config]
86 | (apply system-map
87 | (apply concat
88 | (-> {}
89 |
90 | (http-listener-components config)
91 | (modular-bidi-router-components config)
92 | (jquery-components config)
93 | (sse-demo-website-components config)
94 | (sse-demo-events-components config)))))
95 |
96 | (defn new-dependency-map
97 | []
98 | {:http-listener-listener {:request-handler :modular-bidi-router-webrouter},
99 | :modular-bidi-router-webrouter {:jquery :jquery-resources,
100 | :website :sse-demo-website-website,
101 | :events :sse-demo-events-events}})
102 |
103 | (defn new-co-dependency-map
104 | []
105 | {})
106 |
107 | (defn new-production-system
108 | "Create the production system"
109 | ([opts]
110 | (-> (new-system-map (merge (config) opts))
111 | (system-using (new-dependency-map))))
112 | ([] (new-production-system {})))
113 |
--------------------------------------------------------------------------------
/src/chatserver/events.clj:
--------------------------------------------------------------------------------
1 | (ns chatserver.events
2 | (:require
3 | [clojure.core.async :as async :refer (go go-loop >! ! ch "First message!"))
117 | ch)
118 | )))}]))
119 |
120 | ;; While not mandatory, it is common to use a function to construct an
121 | ;; instance of the component. This affords the opportunity to control
122 | ;; the construction with parameters, provide defaults and declare
123 | ;; dependency relationships with other components.
124 |
125 | (defn new-events-website []
126 | (-> (map->Website {})))
127 |
--------------------------------------------------------------------------------