Git Product home page Git Product logo

chord's Introduction

Chord

A lightweight Clojure/ClojureScript library designed to bridge the gap between the triad of CLJ/CLJS, web-sockets and core.async.

Usage

Include the following in your project.clj:

[jarohen/chord "0.8.1"]

Chord now supports EDN, JSON, Transit and Fressian out of the box - please remove dependencies to ‘chord-fressian’ and ‘chord-transit’ if you have them. Thanks to Luke Snape, Jeff Rose and Thomas Getgood for their help supporting these formats!

Example project

There is a simple example server/client project under the example-project directory. The client sends websocket messages to the server, that get echoed back to the client and written on the page.

You can run it with lein dev - an alias that starts up an http-kit server using Frodo and automatically re-compiles the CLJS.

Once it is running - navigate to http://localhost:3000/ and you should see Send a message to the server:

ClojureScript

Chord only has one function, chord.client/ws-ch, which takes a web-socket URL and returns a map, containing either :ws-channel or :error. When the connection opens successfully, this channel then returns a two-way channel that you can use to communicate with the web-socket server:

(:require [chord.client :refer [ws-ch]]
          [cljs.core.async :refer [<! >! put! close!]])
(:require-macros [cljs.core.async.macros :refer [go]])

(go
  (let [{:keys [ws-channel error]} (<! (ws-ch "ws://localhost:3000/ws"))]
    (if-not error
      (>! ws-channel "Hello server from client!")
      (js/console.log "Error:" (pr-str error)))))

Messages that come from the server are received as a map with a :message key:

(go
  (let [{:keys [ws-channel]} (<! (ws-ch "ws://localhost:3000/ws"))
        {:keys [message]} (<! ws-channel)]
    (js/console.log "Got message from server:" (pr-str message))))

Errors in the web-socket channel (i.e. if the server goes away) are returned as a map with an :error key:

(go
  (let [{:keys [ws-channel]} (<! (ws-ch "ws://localhost:3000/ws"))
        {:keys [message error]} (<! ws-channel)]
    (if error
      (js/console.log "Uh oh:" error)
      (js/console.log "Hooray! Message:" (pr-str message)))))

As of 0.3.0, you can pass a :format option, to pass messages over the channel as EDN (default), as raw strings, or JSON (0.3.1). Valid formats are #{:edn :json :json-kw :str :fressian :transit-json}, defaulting to :edn.

(If you do use fressian, you’ll need to require chord.format.fressian, in addition to the usual Chord namespaces)

(:require [cljs.core.async :as a])
(ws-ch "ws://localhost:3000/ws"
       {:format :json-kw})

As of 0.2.1, you can configure the buffering of the channel by (optionally) passing custom read/write channels, as follows:

(:require [cljs.core.async :as a])
(ws-ch "ws://localhost:3000/ws"
       {:read-ch (a/chan (a/sliding-buffer 10))
        :write-ch (a/chan 5)})

By default, Chord uses unbuffered channels, like core.async itself.

Clojure

Chord wraps the websocket support provided by http-kit, a fast Clojure web server compatible with Ring.

N.B. Currently, Ring’s standard Jetty adapter ~does not~ support Websockets. http-kit is a Ring-compatible alternative.

Again, there’s only one entry point to remember here: a wrapper around http-kit’s with-channel macro. The only difference is that, rather than using http-kit’s functions to interface with the channel, you can use core.async’s primitives.

Chord’s with-channel is used as follows:

(:require [chord.http-kit :refer [with-channel]]
          [org.httpkit.server :refer [run-server]]
          [clojure.core.async :refer [<! >! put! close! go]])

(defn your-handler [req]
  (with-channel req ws-ch
    (go
      (let [{:keys [message]} (<! ws-ch)]
        (prn "Message received:" message)
        (>! ws-ch "Hello client from server!")
        (close! ws-ch)))))

This can take a :format option, and custom buffered read/write channels as well:

(require '[clojure.core.async :as a])

(defn your-handler [req]
  (with-channel req ws-ch
    {:read-ch (a/chan (a/dropping-buffer 10))
         :format :str} ; again, :edn is default
    (go
      (let [{:keys [message]} (<! ws-ch)]
        (prn "Message received:" message)
        (>! ws-ch "Hello client from server!")
        (close! ws-ch)))))

You can also use the wrap-websocket-handler middleware, which will put a :ws-channel key in the request map:

(require '[chord.http-kit :refer [wrap-websocket-handler]]
         '[org.httpkit.server :refer [run-server]]
         '[clojure.core.async :as a])

(defn your-handler [{:keys [ws-channel] :as req}]
  (go
    (let [{:keys [message]} (<! ws-channel)]
      (println "Message received:" message)
      (>! ws-channel "Hello client from server!")
      (close! ws-channel))))

(run-server (-> #'your-handler wrap-websocket-handler) {:port 3000})

You can pass custom channels to wrap-websocket-handler as a second (optional) parameter:

(run-server (-> #'your-handler
              (wrap-websocket-handler {:read-ch ...}))
            {:port 3000})

Bug reports/pull requests/comments/suggestions etc?

Yes please! Please submit these in the traditional GitHub manner.

Contributors

Chord’s contributors are listed in the ChangeLog - thank you all for your help!

License

Copyright © 2013-2015 James Henderson

Distributed under the Eclipse Public License, the same as Clojure.

chord's People

Contributors

ageneau avatar charles-dyfis-net avatar cjohansen avatar giuliano108 avatar hadronzoo avatar jarohen avatar juliangamble avatar rosejn avatar rrichardson avatar tgetgood avatar timgluz avatar tlight avatar weavejester avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

chord's Issues

Can't run with clojure 1.9.0

Trying to run a project with chord (server-side) gives me the following error:

$ lein run
Exception in thread "main" clojure.lang.ExceptionInfo: Call to clojure.core/refer-clojure did not conform to spec:
In: [2 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [2 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [2 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [2] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [2] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [2] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}
 #:clojure.spec.alpha{:problems ({:path [:args :exclude :op :spec], :pred #{:exclude}, :val (quote :as), :via [], :in [2]} {:path [:args :exclude :op :quoted-spec :spec], :pred #{:exclude}, :val :as, :via [], :in [2 1]} {:path [:args :only :op :spec], :pred #{:only}, :val (quote :as), :via [], :in [2]} {:path [:args :only :op :quoted-spec :spec], :pred #{:only}, :val :as, :via [], :in [2 1]} {:path [:args :rename :op :spec], :pred #{:rename}, :val (quote :as), :via [], :in [2]} {:path [:args :rename :op :quoted-spec :spec], :pred #{:rename}, :val :as, :via [], :in [2 1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x17d76ebb "clojure.spec.alpha$regex_spec_impl$reify__2436@17d76ebb"], :value ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core)), :args ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core))}, compiling:(clojure/core/async.clj:9:1)
	at clojure.lang.Compiler.checkSpecs(Compiler.java:6891)
	at clojure.lang.Compiler.macroexpand1(Compiler.java:6907)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:6989)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.analyze(Compiler.java:6729)
	at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6100)
	at clojure.lang.Compiler$TryExpr$Parser.parse(Compiler.java:2307)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:7003)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.analyze(Compiler.java:6729)
	at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6100)
	at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5460)
	at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4022)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:7001)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:6991)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.analyze(Compiler.java:6729)
	at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3813)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:7005)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.analyze(Compiler.java:6729)
	at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:6100)
	at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5460)
	at clojure.lang.Compiler$FnExpr.parse(Compiler.java:4022)
	at clojure.lang.Compiler.analyzeSeq(Compiler.java:7001)
	at clojure.lang.Compiler.analyze(Compiler.java:6773)
	at clojure.lang.Compiler.eval(Compiler.java:7059)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:457)
	at chord.http_kit$eval2678$loading__6434__auto____2679.invoke(http_kit.clj:1)
	at chord.http_kit$eval2678.invokeStatic(http_kit.clj:1)
	at chord.http_kit$eval2678.invoke(http_kit.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:619)
	at oeillade.core$eval166$loading__6434__auto____167.invoke(core.clj:1)
	at oeillade.core$eval166.invokeStatic(core.clj:1)
	at oeillade.core$eval166.invoke(core.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at user$eval149$fn__153.invoke(form-init4136363307982951547.clj:1)
	at user$eval149.invokeStatic(form-init4136363307982951547.clj:1)
	at user$eval149.invoke(form-init4136363307982951547.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7052)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.Compiler.loadFile(Compiler.java:7452)
	at clojure.main$load_script.invokeStatic(main.clj:278)
	at clojure.main$init_opt.invokeStatic(main.clj:280)
	at clojure.main$init_opt.invoke(main.clj:280)
	at clojure.main$initialize.invokeStatic(main.clj:311)
	at clojure.main$null_opt.invokeStatic(main.clj:345)
	at clojure.main$null_opt.invoke(main.clj:342)
	at clojure.main$main.invokeStatic(main.clj:424)
	at clojure.main$main.doInvoke(main.clj:387)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.lang.Var.applyTo(Var.java:702)
	at clojure.main.main(main.java:37)
Caused by: clojure.lang.ExceptionInfo: Call to clojure.core/refer-clojure did not conform to spec:
In: [2 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [2 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [2 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [2] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [2] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [2] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}
 {:clojure.spec.alpha/problems ({:path [:args :exclude :op :spec], :pred #{:exclude}, :val (quote :as), :via [], :in [2]} {:path [:args :exclude :op :quoted-spec :spec], :pred #{:exclude}, :val :as, :via [], :in [2 1]} {:path [:args :only :op :spec], :pred #{:only}, :val (quote :as), :via [], :in [2]} {:path [:args :only :op :quoted-spec :spec], :pred #{:only}, :val :as, :via [], :in [2 1]} {:path [:args :rename :op :spec], :pred #{:rename}, :val (quote :as), :via [], :in [2]} {:path [:args :rename :op :quoted-spec :spec], :pred #{:rename}, :val :as, :via [], :in [2 1]}), :clojure.spec.alpha/spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x17d76ebb "clojure.spec.alpha$regex_spec_impl$reify__2436@17d76ebb"], :clojure.spec.alpha/value ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core)), :clojure.spec.alpha/args ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core))}
	at clojure.core$ex_info.invokeStatic(core.clj:4739)
	at clojure.core$ex_info.invoke(core.clj:4739)
	at clojure.spec.alpha$macroexpand_check.invokeStatic(alpha.clj:689)
	at clojure.spec.alpha$macroexpand_check.invoke(alpha.clj:681)
	at clojure.lang.AFn.applyToHelper(AFn.java:156)
	at clojure.lang.AFn.applyTo(AFn.java:144)
	at clojure.lang.Var.applyTo(Var.java:702)
	at clojure.lang.Compiler.checkSpecs(Compiler.java:6889)
	... 125 more

What helped was this comment that suggested downgrading Clojure, so I'm sticking with 1.8.0 for now.

Strange exception while having :optimizations :none in cljsbuild

I get the following exception while building with :optimizations :none:

java.lang.IllegalArgumentException: character to be escaped is missing
at java.util.regex.Matcher.appendReplacement(Matcher.java:809)
at java.util.regex.Matcher.replaceAll(Matcher.java:955)
at clojure.string$replace.invoke(string.clj:104)
at cljs.closure$lib_rel_path.invoke(closure.clj:1202)
at cljs.closure$rel_output_path.invoke(closure.clj:1221)
at cljs.closure$write_javascript.invoke(closure.clj:1342)
at cljs.closure$source_on_disk.invoke(closure.clj:1375)
at cljs.closure$output_unoptimized$fn__3879.invoke(closure.clj:1409)
at clojure.core$map$fn__4553.invoke(core.clj:2624)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4128.invoke(core.clj:137)
at clojure.core$filter$fn__4580.invoke(core.clj:2679)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4128.invoke(core.clj:137)
at clojure.core$map$fn__4553.invoke(core.clj:2616)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.Cons.next(Cons.java:39)
at clojure.lang.RT.next(RT.java:674)
at clojure.core$next__4112.invoke(core.clj:64)
at clojure.core$str$fn__4188.invoke(core.clj:530)
at clojure.core$str.doInvoke(core.clj:528)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:630)
at cljs.closure$deps_file.invoke(closure.clj:1102)
at cljs.closure$output_deps_file.invoke(closure.clj:1122)
at cljs.closure$output_unoptimized.doInvoke(closure.clj:1417)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:632)
at cljs.closure$build.invoke(closure.clj:1714)
at cljs.closure$build.invoke(closure.clj:1627)
at cljsbuild.compiler$compile_cljs$fn__4085.invoke(compiler.clj:81)
at cljsbuild.compiler$compile_cljs.invoke(compiler.clj:80)
at cljsbuild.compiler$run_compiler.invoke(compiler.clj:187)
at user$eval4219$iter__4255__4259$fn__4260$fn__4278.invoke(form-init2688236187975739560.clj:1)
at user$eval4219$iter__4255__4259$fn__4260.invoke(form-init2688236187975739560.clj:1)
at clojure.lang.LazySeq.sval(LazySeq.java:40)
at clojure.lang.LazySeq.seq(LazySeq.java:49)
at clojure.lang.RT.seq(RT.java:507)
at clojure.core$seq__4128.invoke(core.clj:137)
at clojure.core$dorun.invoke(core.clj:3009)
at clojure.core$doall.invoke(core.clj:3025)
at user$eval4219.invoke(form-init2688236187975739560.clj:1)
at clojure.lang.Compiler.eval(Compiler.java:6782)
at clojure.lang.Compiler.eval(Compiler.java:6772)
at clojure.lang.Compiler.load(Compiler.java:7227)
at clojure.lang.Compiler.loadFile(Compiler.java:7165)
at clojure.main$load_script.invoke(main.clj:275)
at clojure.main$init_opt.invoke(main.clj:280)
at clojure.main$initialize.invoke(main.clj:308)
at clojure.main$null_opt.invoke(main.clj:343)
at clojure.main$main.doInvoke(main.clj:421)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at clojure.lang.Var.invoke(Var.java:383)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.Var.applyTo(Var.java:700)
at clojure.main.main(main.java:37)
Subprocess failed

Importing only [chord.client :refer [ws-ch]] procs this issue. However, with :optimizations :whitespace it successfully compiles. What am I missing?

transducers on write-ch receive formatted value

I want to apply a transformation to everything I send up my websocket, so I thought I could pass a channel with a mapping transducer as write-ch.

This doesn't work as I'd hoped because my transducer sees messages after formatting, not before. So I have to thaw, apply the transformation, and freeze again in my transducer.

Ideally, I think the formatting would occur as the last thing before writing to and the first thing after reading from the websocket connection - so read-from-ws! and write-from-ws! would take a formatter, and apply thaw and freeze respectively, rather than involving core.async at all.

That way the user-supplied channels will always be dealing with unformatted values, which I suspect will be what people want/expect almost all the time, and we lose a bit of core.async overhead inside of chord. This would break backwards compatibility in that the change could break anyone passing a channel with a transducer as write-ch. However I suspect most uses of read-ch and write-ch are to apply buffering as all mine were until today :)

Happy to make this pull request if you like the idea!

Server miss first message

Hey, James!

I catch error -
When server get first web socket (after restart) and this message really fast one like:

socket.onopen = e => { socket.send("hi"); };

than server side miss it for some reason.

Sorry, I didn't spend time to investigate it. I didn't use ClojureScript for project, so I rewrite everything just with http-kit with plain on-receive.

Maybe you have a clue about it. If not, I'll spend some time this or next weekend.

Problem on Ubuntu

Hi,

I have trouble running chord on Ubuntu (seems to work fine on mac). When running the example project i get the following stack trace when accessing the /ws service.

java.lang.IllegalArgumentException: No implementation of method: :render of protocol: #'compojure.response/Renderable found for class: clojure.core.async.impl.channels.ManyToManyChannel
at clojure.core$_cache_protocol_fn.invoke(core_deftype.clj:544)
at compojure.response$eval313$fn__314$G__304__321.invoke(response.clj:10)
at compojure.response$eval352$fn__353.invoke(response.clj:27)
at compojure.response$eval313$fn__314$G__304__321.invoke(response.clj:10)
at compojure.core$make_route$fn__488.invoke(core.clj:93)
at compojure.core$if_route$fn__472.invoke(core.clj:39)
at compojure.core$if_method$fn__465.invoke(core.clj:24)
at compojure.core$routing$fn__494.invoke(core.clj:106)
at clojure.core$some.invoke(core.clj:2515)
at compojure.core$routing.doInvoke(core.clj:106)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invoke(core.clj:626)
at compojure.core$routes$fn__498.invoke(core.clj:111)
at org.httpkit.server.HttpHandler.run(RingHandler.java:91)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
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)

<IllegalArgumentException java.lang.IllegalArgumentException: No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for class: nil>

Do you have any idea whats wrong and what I can do to fix it?

Clojure client?

I realize (looking at #20) that Clojure clients are not currently supported, but it would be a nice feature for the client API to work on Clojure as well. And just for the record (in regard to #20 again), http-kit does not have client-side websocket support, only server support.

Support immutant 2?

http-kit seems to be failing out of maintenance, whereas immutant is actively supported. Could chord support immutant, like sente did?

with-channel without options doesn't work in 0.4.1

There's a problem with the with-channel logic, specifically on lines 59 to 61:

        opts (when opts? opts)
        body (cond->> body
               (not opts?) (cons opts))

If opts? is false, then opts is bound to nil, and body is bound to (cons nil body).

To fix this, the opts rebinding should come after the body rebinding.

Node.js

Would it be possible to use Node.js as server somehow? Or is my best bet to use something Node-native, like Socket.io?

Remove dependencyon com.cemerick/urls

Hi James,

I can't see that chord actually uses anything from com.cemerick/urls, and it adds about 14MB of dependencies to uberjars built with a dependency on chord:

alexh@box:~/chord$ lein uberjar
Created /home/alexh/chord/target/chord-0.7.0.jar
Created /home/alexh/chord/target/chord-0.7.0-standalone.jar
alexh@box:~/chord$ ls -lh target/chord-0.7.0-standalone.jar 
-rw-rw-r-- 1 alexh alexh 21M Feb 17 11:12 target/chord-0.7.0-standalone.jar
alexh@box:~/chord$ vim project.clj # REMOVE DEPENDENCY
alexh@box:~/chord$ lein uberjar
Created /home/alexh/chord/target/chord-0.7.0.jar
Created /home/alexh/chord/target/chord-0.7.0-standalone.jar
alexh@box:~/chord$ ls -lh target/chord-0.7.0-standalone.jar 
-rw-rw-r-- 1 alexh alexh 7.2M Feb 17 11:13 target/chord-0.7.0-standalone.jar

Would be great to cut this down!

Thanks,
Alex

An obvious point in retrospect, but:

As a general public service, it might be worth mentioning this here: until tonight, I've been using the jetty ring adapter for serving http -- as, I suspect many other folk do. (The Heroku docs all assume this, for instance). But: ring-jetty-adapter is based on jetty... 7.

Which doesn't have websockets.

The errors one gets are comically unhelpful. Compojure barfs Render errors. All your sockets are mysteriously nil. Cool story, Jetty 7.

Anywho, a word to people using websockets, and by extention, chord: http-kit.

Opening a single socket to pass messages through

Hello! This is a "how does your awesome library work" question, not a bug or an issue. So:

I have single-repo Clojure server with a ClojureScript front-end. All the routes are Compojure in the server; the server, then, needs to be able to respond to a set of routes by passing data in to the client so it can respond correctly.

What I imagine doing, then, is having a single route for the client to hit to open its socket, then a handler on the server for routes that need to pass messages. Something like;

;; Server
(defroutes app-routes
  (GET "/ws" [] somehow-return-a-web-socket)
  (GET "/api/:foo [foo]" (pass-message-through-socket foo))
  (GET "/api/auth/:bar" [bar] (pass-message-through-socket bar)))

The client opens a go-loop on "/ws", then listens to whatever is dispatched through it. What I can't tell is: does chord support this? If so, what would somehow-return-a-web-socket look like? (The other option I see is to have every route that needs to talk to the client open a different socket, and on the client listen to all of them using alts! -- but somehow I find that less... pleasing.)

Close received after close

Hi!

When my server closes the websocket channel, the client throws an error:

WebSocket connection to 'ws://localhost:9009/gateways/0200000100000205/progress.ws' failed: Close received after close

and the ws-channel returns {:message nil}.

I've boiled the server code down to:

(with-channel request ws-ch
    {:format :json-kw}
    (go
      (>! ws-ch "testing")
      (<! (timeout 1000))
      (close! ws-ch)))

On the client I then first get {:message "testing"} and after 1 second, I get the above error, then {:message nil}.

My client code looks like this pared down:

(defn connect [uri]
  (let [c (chan)]
    (go
      (let [{:keys [ws-channel error]} (<! (ws-ch (str "ws://" (.-host js/location) uri) {:format :json}))]
        (when-not error
          (loop []
            (let [v (<! ws-channel)]
              (prn v)
              (if-let [{:keys [message error]} v]
                (cond
                  error (do
                          (prn error)
                          (recur))
                  message (do
                            (>! c message)
                            (recur))
                  :else (close! c))
                (close! c)))))))
    c))

Any idea what's going on?

Thanks!

Meeting user expectations at first encounter

Thank you for writing this library.

I ran the example project to explore chord, a first encounter if you will.

Anything I type in the text box yields the following message:

{:error :invalid-json, :invalid-msg "{:received \"You passed: '\\\"Hi there\\\"' at Wed Mar 26 16:28:23 IST 2014.\"}"}

Granted, I can see that a bidirectional communication channel is established, which is the most important thing I expect from a websockets library. I suppose you will explain that data needs to be passed a certain way. Still, I'm not expecting to see format errors in the demo. Alternatively, please provide instructions on the example page.

How do I clean up after a client disconnects?

Thanks for making chord - websockets and core.async is a great match. :-)

One issue: Are there any notifications or callbacks for when a client disconnects? In particular for this websocket endpoint that only sends data, and doesn't receive any. Or do I have to create some manual ACK from the client along with timeouts?

WARNING: Use of undeclared Var chord/chord at line 45

I'm getting a warning when compiling chord with ClojureScript 0.0-1913 and core.async 0.1.242.0-44b1e3-alpha:

WARNING: Use of undeclared Var chord/chord at line 45 file:/[...]/jarohen/chord/0.1.1/chord-0.1.1.jar!/chord.cljs

Attempting to execute the following ClojureScript:

(ns chord-test.client
  (:require [chord :refer [ws-ch]]
            [cljs.core.async :refer [<! >! put! close!]])
  (:require-macros [cljs.core.async.macros :refer [go]]))

(go
 (let [ws (<! (ws-ch "ws://localhost:8080/ws"))
       {:keys [message error]} (<! ws)]
   (if error
     (js/console.log "Uh oh:" error)
     (js/console.log "Hooray! Message:" message))))

Causes a JS exception:

Uncaught TypeError: Cannot read property 't10254' of undefined main.js:28472
combine_chs main.js:28472
ws_ch main.js:28524
(anonymous function)

(ws-ch "ws://localhost:8080/ws") is failing. The exception occurs because chord.chord is undefined in the generated Javascript:

chord.combine_chs = function combine_chs(ws, read_ch, write_ch) {
  if(typeof chord.chord.t10254 !== "undefined") {
  }else {
    // ...
    };

How do I use {:format :fression} on server side? (or: no method in multimethod for fression connection)

Got a system going with {:format :transit-json}, but now when I try to switch to fression with code like this:

(defn ws-handler [{:keys [ws-channel] :as req}]
  (go
    (println (<! ws-channel))
    (>! ws-channel {1 ["recieving you"]})))

...

(GET "/ws" []
       (wrap-websocket-handler ws-handler {:format :fression}))

The wrap-socket-handler fails with:

java.lang.IllegalArgumentException: No method in multimethod 'formatter*' for dispatch value: :fression

Are there any docs or examples of working fression client/server connections?

Error with first time use

Here's the error I got, similar to an earlier report:

Opened channel from 127.0.0.1
Exception in thread "async-dispatch-1" java.lang.IllegalArgumentException: No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for
class: nil
at clojure.core$cache_protocol_fn.invoke(core_deftype.clj:544)
at clojure.core.async.impl.protocols$eval9453$fn__9454$G__9444__9461.invoke(protocols.clj:15)
at clojure.core.async.impl.ioc_macros$take_BANG
.invoke(ioc_macros.clj:955)
at pts.server$ws_handler$fn__12878$state_machine__10798__auto____12879$fn__12881.invoke(server.clj:346)
at pts.server$ws_handler$fn__12878$state_machine__10798__auto____12879.invoke(server.clj:346)
at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:945)
at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:949)
at pts.server$ws_handler$fn__12878.invoke(server.clj:346)
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)
java.lang.IllegalArgumentException: No implementation of method: :render of protocol: #'compojure.response/Renderable found for class: clojure.core.async.impl.channels.ManyToMany
Channel

Unfortunately, that user said he had no problems on Mac, which is what I'm using.
Any ideas?

tools.reader dependency

I see that Chord seems to directly depend on tools.reader but it is not listed in dependencies. Shouldn't it be? If EDN support is optional, I recommend moving it to chord.edn to make that clearer.

Running lein dev on the examples gives an exception

When running lein dev on the example-project, I get the following exception.

SEVERE: Error compiling CLJS...
clojure.lang.ExceptionInfo: failed compiling file:ui-src/chord/example/front_end.cljs {:file #object[java.io.File 0x44e4c991 "ui-src/chord/example/front_end.cljs"]}
    at clojure.core$ex_info.invoke(core.clj:4593)
    at cljs.compiler$compile_file$fn__11304.invoke(compiler.cljc:1146)
    at cljs.compiler$compile_file.invoke(compiler.cljc:1109)
    at cljs.compiler$compile_root.invoke(compiler.cljc:1181)
    at cljs.closure$compile_dir.invoke(closure.clj:385)
    at cljs.closure$eval11670$fn__11671.invoke(closure.clj:425)
    at cljs.closure$eval11623$fn__11624$G__11614__11631.invoke(closure.clj:331)
    at cljs.closure$eval11683$fn__11684.invoke(closure.clj:439)
    at cljs.closure$eval11623$fn__11624$G__11614__11631.invoke(closure.clj:331)
    at yoyo.cljs$compile_cljs_BANG_$reify__12087$fn__12088.invoke(cljs.clj:55)
    at clojure.core$map$fn__4553.invoke(core.clj:2622)
    at clojure.lang.LazySeq.sval(LazySeq.java:40)
    at clojure.lang.LazySeq.seq(LazySeq.java:49)
    at clojure.lang.RT.seq(RT.java:507)
    at clojure.core$seq__4128.invoke(core.clj:137)
    at clojure.core$apply.invoke(core.clj:630)
    at clojure.core$mapcat.doInvoke(core.clj:2660)
    at clojure.lang.RestFn.invoke(RestFn.java:423)
    at yoyo.cljs$compile_cljs_BANG_$reify__12087._compile(cljs.clj:55)
    at cljs.closure$build.invoke(closure.clj:1476)
    at yoyo.cljs$compile_cljs_BANG_$fn__12091.invoke(cljs.clj:61)
    at yoyo.cljs$compile_cljs_BANG_.invoke(cljs.clj:60)
    at yoyo.cljs$watch_cljs_BANG_.invoke(cljs.clj:90)
    at yoyo.cljs$with_cljs_compiler.invoke(cljs.clj:142)
    at chord.example.main$with_cljs_compiler.invoke(main.clj:31)
    at clojure.lang.AFn.applyToHelper(AFn.java:156)
    at clojure.lang.AFn.applyTo(AFn.java:144)
    at clojure.lang.AFunction$1.doInvoke(AFunction.java:29)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at yoyo.system$make_system$fn__30294$fn__30295$fn__30296.invoke(system.clj:37)
    at yoyo.system$make_system$fn__30294$fn__30295$fn__30296$fn__30300.invoke(system.clj:39)
    at yoyo.system$constant_value$fn__30262.invoke(system.clj:8)
    at yoyo.system$make_system$fn__30294$fn__30295$fn__30296.invoke(system.clj:37)
    at yoyo.system$make_system$fn__30294.invoke(system.clj:44)
    at yoyo.system$with_system_put_to$fn__30330.invoke(system.clj:57)
    at clojure.lang.Var.invoke(Var.java:379)
    at yoyo$run_system_BANG_$fn__28808.invoke(yoyo.clj:46)
    at clojure.core$binding_conveyor_fn$fn__4444.invoke(core.clj:1916)
    at clojure.lang.AFn.call(AFn.java:18)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: clojure.lang.ExceptionInfo: No such namespace: chord.http, could not locate chord/http.cljs, chord/http.cljc, or Closure namespace "chord.http" at line 1 ui-src/chord/example/front_end.cljs {:file "ui-src/chord/example/front_end.cljs", :line 1, :column 1, :tag :cljs/analysis-error}
    at clojure.core$ex_info.invoke(core.clj:4593)
    at cljs.analyzer$error.invoke(analyzer.cljc:384)
    at cljs.analyzer$error.invoke(analyzer.cljc:382)
    at cljs.analyzer$analyze_deps.invoke(analyzer.cljc:1293)
    at cljs.analyzer$eval9926$fn__9928.invoke(analyzer.cljc:1545)
    at clojure.lang.MultiFn.invoke(MultiFn.java:251)
    at cljs.analyzer$analyze_seq.invoke(analyzer.cljc:1900)
    at cljs.analyzer$analyze$fn__10176.invoke(analyzer.cljc:1992)
    at cljs.analyzer$analyze.invoke(analyzer.cljc:1985)
    at cljs.compiler$compile_file_STAR_$fn__11272.invoke(compiler.cljc:1027)
    at cljs.compiler$with_core_cljs.invoke(compiler.cljc:968)
    at cljs.compiler$compile_file_STAR_.invoke(compiler.cljc:988)
    at cljs.compiler$compile_file$fn__11304.invoke(compiler.cljc:1129)
    ... 41 more

I'll submit a pull request for a fix now.

Getting this error ``ERR_DISALLOWED_URL_SCHEME``

So I have cloned your project and everything is same. When I run lein figwheel also it doesn't give me any error also and it runs perfectly on http://localhost:3449/ but when I try to run on ws://localhost:3449/ws I'm getting this error
image

How to handle connection attempts when server is not responding

Hey again, James!

I'm experimenting with trying to get the client to reconnect to the server upon connection failure. My results are so-so, no doubt due to my inexperience with core.async.

If the server is not running, it seems that (<! (ws-ch address)) will wait indefinitely, even after server is running. What's the appropriate way to handle this? Can I provide a timeout to the initial channel, forcing it to close if it hasn't delivered the 2-way websocket-channel within x seconds? If placed in, for example, a go-loop block, then connection attempts could be handled in this manner.

Error when hitting ws endpoint from cljs

I'm getting the same error seen in issues #23 #21 when I hit my /ws endpoint from cljs. Here is what my project look likes:

Server:

https://github.com/colinkahn/foo/blob/8a40c80eba76797be7c94b6f4bcaded359fe75e7/src/clj/foo/core.clj

Client:

https://github.com/colinkahn/foo/blob/8a40c80eba76797be7c94b6f4bcaded359fe75e7/src/cljs/foo/core.cljs#L53-L55

Project:

https://github.com/colinkahn/foo/blob/8a40c80eba76797be7c94b6f4bcaded359fe75e7/project.clj

I'm running it using lein cljsbuild auto dev in one terminal and lein ring server in another.

Example suggestion

It would be great if there were a minimal working example using chord with http-kit. The current example uses a lot of tools from several libraries, which is fine, but makes it difficult to understand at a basic level how to use this library. I'd be happy to send you a pull request of such an example but I'm having difficulty hooking it up. 😉

Unable to compile example project

I'm unable to compile the example project. Tried compiling in two different environments.

$ lein dev
Compiling ClojureScript.
Compiling "target/resources/js/chord-example.js" from ["src" "checkouts/chord/src" "checkouts/chord/target/generated/cljs"]...
Started nREPL server, port 7888
WARNING: Use of undeclared Var cljs.core.async/do-alts at line 62 file:/home/thomas/.m2/repository/org/clojure/core.async/0.1.301.0-deb34a-alpha/core.async-0.1.301.0-deb34a-alpha.jar!/cljs/core/async/impl/ioc_helpers.cljs
WARNING: Bad method signature in protocol implementation, impl/Handler does not declare method called lock-id at line 214 file:/home/thomas/.m2/repository/org/clojure/core.async/0.1.301.0-deb34a-alpha/core.async-0.1.301.0-deb34a-alpha.jar!/cljs/core/async.cljs
WARNING: Use of undeclared Var cljs.core.async.impl.protocols/lock-id at line 217 file:/home/thomas/.m2/repository/org/clojure/core.async/0.1.301.0-deb34a-alpha/core.async-0.1.301.0-deb34a-alpha.jar!/cljs/core/async.cljs
WARNING: Referred var fressian-cljs.fns/lookup does not exist at line 1 file:/home/thomas/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/reader.cljs
WARNING: Referred var fressian-cljs.fns/lookup does not exist at line 1 file:/home/thomas/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/writer.cljs
WARNING: No such namespace: str at line 12 file:/home/thomas/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/core.cljs
WARNING: Use of undeclared Var str/replace at line 12 file:/home/thomas/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/core.cljs
WARNING: Use of undeclared Var fressian-cljs.core/load-string at line 24 file:/home/thomas/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/core.cljs
Compiling "target/resources/js/chord-example.js" failed.
java.io.FileNotFoundException: checkouts/chord/src (No such file or directory)
            (Unknown Source) java.io.FileInputStream.open
    FileInputStream.java:138 java.io.FileInputStream.<init>
                  io.clj:229 clojure.java.io/fn
                   io.clj:69 clojure.java.io/fn[fn]
                  io.clj:165 clojure.java.io/fn
                   io.clj:69 clojure.java.io/fn[fn]
                  io.clj:102 clojure.java.io/reader
             RestFn.java:410 clojure.lang.RestFn.invoke
           analyzer.clj:1611 cljs.analyzer/forms-seq
           analyzer.clj:1609 cljs.analyzer/forms-seq
             closure.clj:335 cljs.closure/compile-file
             closure.clj:384 cljs.closure/eval3247[fn]
             closure.clj:293 cljs.closure/eval3182[fn]
             closure.clj:397 cljs.closure/eval3234[fn]
             closure.clj:293 cljs.closure/eval3182[fn]
             compiler.clj:44 cljsbuild.compiler.SourcePaths/fn
               core.clj:2612 clojure.core/map[fn]
             LazySeq.java:40 clojure.lang.LazySeq.sval
             LazySeq.java:49 clojure.lang.LazySeq.seq
                 RT.java:485 clojure.lang.RT.seq
                core.clj:135 clojure.core/seq
                core.clj:626 clojure.core/apply
               core.clj:2650 clojure.core/mapcat
             RestFn.java:423 clojure.lang.RestFn.invoke
             compiler.clj:44 cljsbuild.compiler/cljsbuild.compiler.SourcePaths
             closure.clj:962 cljs.closure/build
             closure.clj:928 cljs.closure/build
             compiler.clj:58 cljsbuild.compiler/compile-cljs[fn]
             compiler.clj:57 cljsbuild.compiler/compile-cljs
            compiler.clj:159 cljsbuild.compiler/run-compiler
form-init3789115033671463737.clj:1 user/eval4204[fn]
form-init3789115033671463737.clj:1 user/eval4204[fn]
             LazySeq.java:40 clojure.lang.LazySeq.sval
             LazySeq.java:49 clojure.lang.LazySeq.seq
                 RT.java:485 clojure.lang.RT.seq
                core.clj:135 clojure.core/seq
               core.clj:3004 clojure.core/dorun
               core.clj:3020 clojure.core/doall
form-init3789115033671463737.clj:1 user/eval4204
          Compiler.java:6768 clojure.lang.Compiler.eval
          Compiler.java:6758 clojure.lang.Compiler.eval
          Compiler.java:6758 clojure.lang.Compiler.eval
          Compiler.java:7195 clojure.lang.Compiler.load
          Compiler.java:7151 clojure.lang.Compiler.loadFile
                main.clj:274 clojure.main/load-script
                main.clj:279 clojure.main/init-opt
                main.clj:307 clojure.main/initialize
                main.clj:342 clojure.main/null-opt
                main.clj:420 clojure.main/main
             RestFn.java:421 clojure.lang.RestFn.invoke
                Var.java:383 clojure.lang.Var.invoke
                AFn.java:156 clojure.lang.AFn.applyToHelper
                Var.java:700 clojure.lang.Var.applyTo
                main.java:37 clojure.main.main
Starting web server, port 3000

Example project - OutOfMemory error

I'm attempting to run the lein dev target on the example-project.

I get the following:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space, compiling:(deps/toolsanalyzerjvm/v0v6v6/clojure/tools/analyzer/passes/jvm/validate.clj:227:1)

Tried increasing the JVM mem options, but didn't solve it : /

Full stacktrace below:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space, compiling:(deps/toolsanalyzerjvm/v0v6v6/clojure/tools/analyzer/passes/jvm/validate.clj:217:1)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6730)
    at clojure.lang.Compiler.analyze(Compiler.java:6524)
    at clojure.lang.Compiler.eval(Compiler.java:6779)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5753)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:436)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.passes.jvm.box$eval30149$loading__5340__auto____30150.invoke(box.clj:9)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.passes.jvm.box$eval30149.invoke(box.clj:9)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5753)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:703)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.jvm$eval29107$loading__5340__auto____29108.invoke(jvm.clj:9)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.jvm$eval29107.invoke(jvm.clj:9)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5749)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:619)
    at refactor_nrepl.analyzer$eval28873$loading__5340__auto____28874.invoke(analyzer.clj:1)
    at refactor_nrepl.analyzer$eval28873.invoke(analyzer.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5753)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:436)
    at refactor_nrepl.find_symbol$eval28700$loading__5340__auto____28701.invoke(find_symbol.clj:1)
    at refactor_nrepl.find_symbol$eval28700.invoke(find_symbol.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5753)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:457)
    at refactor_nrepl.middleware$eval26865$loading__5340__auto____26866.invoke(middleware.clj:1)
    at refactor_nrepl.middleware$eval26865.invoke(middleware.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5749)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:457)
    at nrepl.embed$eval18670$loading__5340__auto____18671.invoke(embed.clj:1)
    at nrepl.embed$eval18670.invoke(embed.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5749)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:512)
    at chord.example.main$eval1296$loading__5340__auto____1297.invoke(main.clj:1)
    at chord.example.main$eval1296.invoke(main.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5749)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at user$eval1281$fn__1283.invoke(form-init7123732214773336505.clj:1)
    at user$eval1281.invoke(form-init7123732214773336505.clj:1)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6772)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.Compiler.loadFile(Compiler.java:7165)
    at clojure.main$load_script.invoke(main.clj:275)
    at clojure.main$init_opt.invoke(main.clj:280)
    at clojure.main$initialize.invoke(main.clj:308)
    at clojure.main$null_opt.invoke(main.clj:343)
    at clojure.main$main.doInvoke(main.clj:421)
    at clojure.lang.RestFn.invoke(RestFn.java:421)
    at clojure.lang.Var.invoke(Var.java:383)
    at clojure.lang.AFn.applyToHelper(AFn.java:156)
    at clojure.lang.Var.applyTo(Var.java:700)
    at clojure.main.main(main.java:37)
Caused by: java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
    at clojure.lang.DynamicClassLoader.defineClass(DynamicClassLoader.java:46)
    at clojure.lang.Compiler$ObjExpr.getCompiledClass(Compiler.java:4833)
    at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3993)
    at clojure.lang.Compiler.analyzeSeq(Compiler.java:6721)
    at clojure.lang.Compiler.analyze(Compiler.java:6524)
    at clojure.lang.Compiler.eval(Compiler.java:6779)
    at clojure.lang.Compiler.load(Compiler.java:7227)
    at clojure.lang.RT.loadResourceScript(RT.java:371)
    at clojure.lang.RT.loadResourceScript(RT.java:362)
    at clojure.lang.RT.load(RT.java:446)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.core$load$fn__5448.invoke(core.clj:5866)
    at clojure.core$load.doInvoke(core.clj:5865)
    at clojure.lang.RestFn.invoke(RestFn.java:408)
    at clojure.core$load_one.invoke(core.clj:5671)
    at clojure.core$load_lib$fn__5397.invoke(core.clj:5711)
    at clojure.core$load_lib.doInvoke(core.clj:5710)
    at clojure.lang.RestFn.applyTo(RestFn.java:142)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$load_libs.doInvoke(core.clj:5753)
    at clojure.lang.RestFn.applyTo(RestFn.java:137)
    at clojure.core$apply.invoke(core.clj:632)
    at clojure.core$require.doInvoke(core.clj:5832)
    at clojure.lang.RestFn.invoke(RestFn.java:436)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.passes.jvm.box$eval30149$loading__5340__auto____30150.invoke(box.clj:9)
    at deps.toolsanalyzerjvm.v0v6v6.clojure.tools.analyzer.passes.jvm.box$eval30149.invoke(box.clj:9)
    at clojure.lang.Compiler.eval(Compiler.java:6782)
    at clojure.lang.Compiler.eval(Compiler.java:6771)
    at clojure.lang.Compiler.load(Compiler.java:7227)

Equivalent of http-kit's on-close?

Hi James,

I currently have a small web service, using http-kit's websocket support, which broadcasts messages to a number of clients.

My connection handler looks like this:

(defn update-handler [request]
  (with-channel request channel
                (swap! clients conj channel)
                (on-close channel (fn [status]
                                    (swap! clients disj channel)
                                    (println "channel closed: " status)))))

I want to convert my program to use chord so I don't have to do my own transit handling, but I'm not sure how to handle client disconnections to remove dead channels from clients. As far as I can see on-close isn't exposed. Am I just thinking about this wrong? Is there some better way of doing what I want?

Sorry if this is dumb, I'm very new.

Cheers,
Alex

Tab hangs the tab when connection is broken

I have an issue with the lib. There's a simple code handling websocket messages from server.

; if response is neither nil nor error - put it into another chan
(defn handle-server-msg [{:keys [error message] :as resp} handler-ch]
  (when resp
    (println "Received from server" resp)
    (if error
      (println "Got error from server" error)
      (let [{:keys [messages]} message]
        (doall (map #(put! handler-ch %) messages))))))

; install receiver which just calls function above
(defn install! [handler-ch]
  (go
    (let [ws-url (str "ws://" (.-host js/location) "/sync")
          {:keys [ws-channel error] :as r} (<! (ws-ch ws-url {:format :json}))]
      (if error
        (println "Error opening websocket" error)
        (do
          (set! *ws-chan* ws-channel)
          (go-loop []
            (handle-server-msg (<! *ws-chan*) handler-ch)
            (recur)))))))

It works fine but when i turn down the server - chrome tab just hangs. Chrome task manager shows CPU consumption for this tab around 100% (Mac). It looks like ws-channel when connection breaks infinitely returns nil (tried println in first line of handle-server-msg) and it causes tab to hang.

How should i handle cases like this? Maybe introduce some timer on receiving nil and query chan on timer tick? Thanks in advance!

Serializing large message fails with {:format :edn}

Dispatching a large payload (~800KB) from websocket server using {:format :edn} results in {:error :invalid-format} on the ws-channel on the client side. I don't have this issue with a smaller payload.

I was able to work around this by changing to {:format :transit-json} on client and server.

Including chord triggers core.typed initialization error

When trying to use core.typed, I get the following initialization error:

CompilerException java.io.FileNotFoundException: Could not locate cljs/env__init.class or cljs/env.clj on classpath., compiling:(cljs/jvm/tools/analyzer.clj:1:1)

I've fiddled with my project.clj file, and I by process of elimination, I determined that the error only occurs when I include a dependency to jarhohen/chord.

Do you know what is causing this?

Bidirectional sockets complect sockets with channels.

Hey,
I really like the library, but bidirectional .async channels seem like a contradiction to me. As this basically means bidirectional queue.
To me it would be cleaner to expose the read-ch and write-ch in a {:in ch, :out ch, :error ch} map. This way they would be means of communicating with the websocket (as a separate concept), instead of becoming the websocket. This would also separate data reading from error handling.

Thoughts?

Messages appear on server as TaggedObject rather than clojure value.

Hi there again!

So after getting fressian transport working, what I've found is when you send a message from the server to the client, the message appears on the client close to what was sent (a vector turns into a sequence), but when sending from the client to the server, instead of getting either, I seem to get an org.fressian.TaggedObject Object instead of a clj collection.

example project here: https://github.com/retrogradeorbit/multiplayer

After client connects it sends [:login :test] down the channel. On receipt, the server prints what it gets and the following appears:

message was org.fressian.TaggedObject@e272218

Note, this is embedded inside the hashmap response. Printing the whole message pulled from the channel:

{:message #object[org.fressian.TaggedObject 0x2f5285c "org.fressian.TaggedObject@2f5285c"]}

0.5.0 fails to compile.

As soon as I require [chord.client :refer [ws-ch]] I get the following error

clojure.lang.ExceptionInfo : Referred var fressian-cljs.fns/lookup does not exist at line 1
file:/Users/ianp/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/reader.cljs

Which is annoying as I don't plan on using Fressian. I think that keeping the chord-fressian and chord-transit formats as separate dependencies was a better approach that lumping everything together in one project, this would also make it easier to add additional formats later on.

Problem with routing compojure

I am trying to create a chat-room websocket services with the help of example code in this https://github.com/jarohen/chord , the problem i don't able to create a different channel with respect to id. and i don't have good knowledge on clojure.core.async here is my code

(ns cljs-lein-project.mychat
  (:require [compojure.core :refer [GET defroutes]]
            [compojure.handler :refer [site]]
            [chord.http-kit :refer [wrap-websocket-handler]]
            [org.httpkit.server :refer [run-server]]
            [clojure.core.async :as a])
  (:gen-class))

(defn ws-handler
  [id {:keys [ws-channel] :as req} {:keys [chat-ch chat-mult]}]
  (let [tapped-ch (a/chan)
        room-id id]
    (a/tap chat-mult tapped-ch)
    (a/go
      (a/>! chat-ch {:type :user-joined :room-id room-id})
      (loop []
        (a/alt!
          tapped-ch ([message]
                     (if message
                       (do
                         (a/>! ws-channel message)
                         (recur))
                       (a/close! ws-channel)))

          ws-channel ([ws-message]
                      (if ws-message
                        (do
                          (println ws-message)
                          (a/>! chat-ch
                                {:type :message
                                 :message (:message ws-message)
                                 :room-id room-id})
                          (recur))
                        (do
                          (a/untap chat-mult tapped-ch)
                          (a/>! chat-ch
                                {:type :user-left
                                 :room-id room-id})))))))))

(def with-chat-chs1
  (let [chat-ch (a/chan)
        chat-mult (a/mult chat-ch)]
    {:chat-ch chat-ch
     :chat-mult chat-mult}))

(def with-chat-chs2
  (let [chat-ch (a/chan)
        chat-mult (a/mult chat-ch)]
    {:chat-ch chat-ch
     :chat-mult chat-mult}))

(defroutes chat-routes
  (GET "/user-chat/:id" [id]
       (-> #(ws-handler id  % with-chat-chs1)
           (wrap-websocket-handler {:format :transit-json})))
  (GET "/admin-chat/:id" [id]
       (-> #(ws-handler id % with-chat-chs2)
           (wrap-websocket-handler {:format :transit-json}))))

(defn run-chat []
  (run-server
   (site #'chat-routes)
   {:port 3008}))

If i connect to ws://localhost:3008/user-chat/235 and ws://localhost:3008/user-chat/236 Here the both routes are different right but massages are visible in both uri so please give me any idea to overtake this issue. I think the problem with my def's

No tests

Some recent changes in #8 caused compilation to fail. There are no tests or CI that
should have caught this.

Chord tests can start a temporary http-kit server and talking to it over a Java WebSocket client (e.g. Jetty has one). Not sure how CLJS can be tested in the same suite but CLJS compilation can be part of the CI nonetheless.

Error when using `chord.http` in ClojureScript

Requiring chord.http in my ClojureScript application causes:

Caused by: clojure.lang.ExceptionInfo: Referred var fressian-cljs.fns/lookup does not exist at line 1 file:/Users/me/.m2/repository/net/unit8/fressian-cljs/0.1.0/fressian-cljs-0.1.0.jar!/fressian_cljs/reader.cljs

This is using version 0.5.0

EDIT: This also happens when I require chord.client as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.