This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-08-07
Channels
- # announcements (10)
- # babashka (39)
- # beginners (230)
- # calva (16)
- # cider (20)
- # clara (4)
- # cljs-dev (16)
- # clojure (35)
- # clojure-europe (8)
- # clojure-filipino (5)
- # clojure-france (1)
- # clojure-nl (6)
- # clojure-uk (9)
- # clojuredesign-podcast (1)
- # clojurescript (55)
- # clojurewerkz (1)
- # core-async (13)
- # cursive (1)
- # data-science (1)
- # datomic (4)
- # events (1)
- # fulcro (26)
- # jobs-discuss (1)
- # kaocha (3)
- # malli (53)
- # observability (9)
- # off-topic (1)
- # project-updates (1)
- # re-frame (15)
- # reagent (1)
- # reitit (11)
- # rum (8)
- # sci (29)
- # shadow-cljs (7)
- # vim (12)
- # xtdb (13)
@steiner3044 What do you mean by "reset"? (there's a #cider channel for deep dives on that)
here in my src/book_clj
, there is three file
➜ book_clj ls
book.clj core.clj server.clj
in core.clj
, I want to require other two file, how do I do that?
(ns book-clj.core
(:require [book-clj.server :as server])
(:require [book-clj.book :as book]))
above content will throw an error(ns book-clj.core
(:require [book-clj.server :as server]
[book-clj.book :as book]))
That should work.What error are you getting?
here
clojure.lang.Compiler$CompilerException: java.lang.Exception: namespace 'book-clj.server' not found, compiling:(/home/steiner/workspace/school-work/book-clj/src/book_clj/core.clj:1:1)
eh, I seem to find the reason, here
(comment server
{:books {login-id-1 book-1
login-id-2 book-2
...}
:storge-path storge-path})
I can't believe it won't be evaluate, but this below will be
(comment server
{:books {login-id-1 book-1
login-id-2 book-2}
:storge-path storge-path})
expression in comment
isn't ignored@steiner3044 The contents of a comment
must be valid Clojure forms -- since they are read (but they are not evaluated).
...
is not legal for the Clojure reader.
If you want to write a placeholder for "more code goes here", you could use ,,,
instead -- comma is treated as whitespace in Clojure.
So you get the 'book-clj.server' not found
because that namespace cannot be read by Clojure (because the syntax is not legal), so it can't be compiled -- and therefore a require
of it elsewhere will fail with a compilation error.
Hope that explanation helps?
Hi guys, how do I tread my output in this such that I can use the apply str to join a resulting list of strings?
(defn sing
"Given a start and an optional end, returns all verses in this interval. If
end is not given, the whole song from start is sung."
([start] (sing start 0))
([start end] (for [v (range start (- end 1) -1)]
(apply str (verse v)))))
like (apply str '("test" "2test")) =>"test2test"@zackteo Using clojure.string/join
is probably more efficient than apply str
(and I'd say more idiomatic).
You can also provide a separator: (clojure.string/join ", " ...)
to provide a comma-separated list.
@seancorfield yeap i realised after looking at other solutions. I wanted to do (apply str ( interpose . Which I realise string/join does the same! Thanks tho :)
Hi - I'm trying to follow https://grison.me/2020/04/04/starting-with-clojure/ - but when I run jack-in (just choose leinigen option) - it fails with "could not parse project.clj"
and 0 hits on duckduckgo with: vscode calva "could not parse project.clj" - found google actually had hits.. trying those now 🙂
Can you describe the steps you take? When I follow the article, it works as described. And it is a deps.edn
project, which makes the error message strange.
@U0ETXRFEW The article talks about "jack-in" but never talks about setting up a Leiningen project and I suspect some of the IDEs' "jack-in" commands expect project.clj
?
(now I've read it, it seems like a fairly bad article to me -- it's rambling and vague and introduces all sorts of things it never talks about again)
Calva supports jack-in to CLI projects as well. I just tried to follow the article steps and it works as advertised.
Good to know. Still a terrible article in my opinion 🙂
But since there is only a deps.edn
file involved, Calva shouldn't be offering Leiningen jack-in. The error message is truly strange. Maybe if... I'll have to try a thing...
The article shows an error message from clj-fmt referring to project.clj
but not the same as the OP. That the article talks about Leiningen and shows its installation (twice!) and then doesn't use it, makes it garbage as far as I'm concerned.
No, that wasn't it. I tried renaming the deps.edn file to project.clj
(why @kl should have done that, even with the somewhat cofusing article, I don't know, haha).
That would be great - I'm trying to run an existing project here
so it works for everyone else (who uses emacs and repl typicly)
and I totally forgot to respond in thread 😞 -The article is the one http://clojure.org links to for beginners
Ah, so an existing project. Then you could have run into a limitation in Calva. Assuming it is a Leiningen project. Calva tries to parse the project.clj
file and for some files it fails. See if there is an eval reader macro in that file #=(...)
, iirc. I should fix that problem...
Here's the issue: https://github.com/BetterThanTomorrow/calva/issues/617
my project.clj - seems to not contain such a character
so some other parsing bug ?
can I get the jack-in process to show some debug output
Yeah, Calva jack-in is not prepared for that kind of project.clj. What I would do is to start the project using lein and then connect Calva.
Calva will need some dependencies loaded, that jack-in would have provided. When you start the project try using this command line:
lein update-in :dependencies conj '[nrepl"0.6.0"]' -- update-in :dependencies conj '[clj-kondo"2020.04.05"]' -- update-in :plugins conj '[cider/cider-nrepl"0.23.0"]' -- update-in '[:repl-options :nrepl-middleware]' conj '["cider.nrepl/cider-middleware"]' -- repl
Then when you get the nREPL server started on port ...
message, you can connect Calva using the Leiningen
project type.
I started it with: lein repl - and it started fine ?
and calva seems to have connected fine
I can't seem to run: (go) + enter inside calva though
Yeah, but things like goto definition probably won't work. And the debugger will not have what it needs.
You'll need to use alt+enter
(Evaluate top level form) with the cursor in or after the (go)
call.
Thank you - now I'm running 🙂
I still get a lot of reflection warnings from repl when starting though.. do I need to give repl more things to load or something?
I'm using java 8 here..
I'll poke them 🙂
not unlikely.. I'll kick them/shame them into fixing 🙂
Where on http://clojure.org does the guide you used get linked, btw?
No where it seems - I appearently remembered wrong in my confused state. Sorry 😞
I'm glad I ended up getting it working 🙂
@kl Sounds like your project.clj
file is not valid -- can you share it here?
That article doesn't seem to talk about project.clj
-- the only mention seems to be in an error message when the formatter is run?
Since it talks about CLI / deps.edn
, I'm a bit surprised it suggests "jack-in" since that's often associated with Leiningen which the article doesn't talk about setting up a Leiningen project at all...
...it seems like a very confusing article to me, to be honest.
its the one http://clojure.org links to for beginners
Can you point me to the page / link in question?
any better one for explaining how to startup - using an existing project with vscode+calva? 🙂
The code works for everyone else (who'se typicly using emacs)
I usually use vim - but figured between intellij and vscode (the 2 covered in the article - vscode would be fine - so I installed codium build)
So if I have a directory starter
and within that directory two files: deps.edn
and playground.clj
, with the following content:
deps.edn
{:paths ["resources" "src"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"}}}
playground.clj
(ns playground)
(+ 40 2)
Then with starter
opened in VS Code, I choose jack-in. It gives me a menu of project types to choose from. I choose Clojure CLI
. Then with the playground.clj
file opened, I choose to load it. Calva: Load current file and dependecises. Then I can evaluate the (+ 40 2)
form. With the cursor inside it, I press alt+enter
.
hey, how can I write a data into a binary file, and then read it as binary data? in other way, how to serialize a data?
You want to check out the
namespace. It interops with Java's readers and writers
https://clojuredocs.org/clojure.java.io
slurp
and barf
wrap the io/reader
and io/writer
fns respectively.
Is there an elegant way to dispatch within a function to an inline multi-function? Sorry that's confusing. What I want to do is the following:
(defmethod foo :some-key [& params]
(dispatch params
([] ... );; handle empty params
([a] ... ) ;; handle single arg, binding it to a
([a b] ...) ;; handle binary cases, binding them to a and b
...))
basically this is asking for some lower level interface to multi-functions interface which defn
uses.
perhaps something like the following would work, but is ugly.
(defmethod foo :some-key [& params]
(apply (fn
([] ... );; handle empty params
([a] ... ) ;; handle single arg, binding it to a
([a b] ...) ;; handle binary cases, binding them to a and b
...)
params)))
There's the pattern I'm using. But it seems like a lot of useless coding.
(defmulti rte-expand
"macro-like facility for rte" (fn [pattern _functions] (first pattern)))
(defmethod rte-expand :default [pattern functions]
(invalid-pattern pattern functions))
(defmethod rte-expand :? [pattern functions]
(apply (fn
([] (invalid-pattern pattern functions))
([operand] `(:or :epsilon ~operand))
([_ & _] (invalid-pattern pattern functions)))
(rest pattern)))
(defmethod rte-expand :exp [pattern functions]
(letfn [(expand [n m pattern]
(assert (>= n 0) (format "pattern %s is limited to n >= 0, not %s" pattern n))
(assert (<= n m) (format "pattern %s is limited to n <= m, got %s > %s" pattern n m))
(let [operand (traverse-pattern pattern functions)
repeated-operand (repeat n operand)
optional-operand (repeat (- m n) `(:? ~operand))
]
(traverse-pattern `(:cat ~@repeated-operand ~@optional-operand) functions)))]
(apply (fn
([] (invalid-pattern pattern functions))
([_] (invalid-pattern pattern functions))
([n pattern]
(expand n n pattern))
([n m pattern]
(expand n m pattern))
([_ _ _ & _]
(invalid-pattern pattern functions)))
(rest pattern))))
Isn't there a better idiom than (apply (fn (...) (...) (...)) args)
?
How about something like this?
(defmulti -expand (fn [_pattern _functions & args] (count args)))
(defmethod -expand :default [pattern functions & _args] (invalid-pattern pattern functions))
(defmethod -expand 2 [pattern functions n pattern'] (expand n n pattern'))
(defmethod -expand 3 [pattern functions n m pattern'] (expand n m pattern'))
you pass the outer pattern and functions as arguments instead of close over them, then dispatch on the length of the rest-args
hmm. the way I currently have it the defmulti returns a keyword such as :?
or :+
or :exp
or or or or. And each method (defined with defmethod) then might have a different function for each arity. some keys support multiple arities, some do not. as in my example above :exp
supports arity 2 and 3, while :?
supports only arity 1. some keys might support arbitrary arity. Currently it's up to each defmethod
to implement the arities that make sense for the semantics of the keyword it is implementing.
BTW, how do you get a variable named pattern'
? that's really cool? is it a unicode character?
The idea with -expand
is to use it inside rte-expand
instead of the multi-arity apply
I should have written it like this, though:
(defmethod -expand 3 [_ _ n m pattern] (expand n m pattern))
That's really cool that ' can be part of a symbol. that's very un-lispy, but I love it!
clojure-rte.core> (let [a' 100
a'' 200
a''' 300]
(+ a' a'' a'''))
600
clojure-rte.core>
just yesterday I wrote three local functions phi
, Phi
, and Phi-prime
. I'll go back and rename them to celebrate this coolness
I was implementing a math formula where the reference material used ψ, Φ, and Φ ′.
I'm not sure whether it is a good idea to name these local functions using unicode characters???
'
is an ascii character so it's not so bad, but I'd avoid it in general when naming functions or variables outside of lexical scope. phi
Phi
and Phi-
look good enough imo
Is there a recommended library for wrapping common math functions and constants (trig functions, pi, etc) for use in cljc
files ?
You might want to look at kixi.stats
. Depending on what exactly you want, the whole library might be overkill, but kixi.stats.math
might fit your needs.
Is there a better way to do this idiom which I have found myself doing several times in the past days?
(map (fn [_] operand) (range n))
I suppose (map (constantly operand) (range n))
is a bit better.great. Thanks @jason358, For some reason I had convinced myself that repeat
only worked for strings, and did n-many string concatenations.
When starting a repl using clj
is there a way to break a long running evaluation using some keyboard shortcut?
some evaluation environments like CIDER/NREPL stop threads preemptively using java's Thread.stop()
, which has been deprecated for decades
the official way to do stop something on the JVM is to use Thread.interrupt(), but that is cooperative with the running code
but, TLDR, no, nothing generic. If you eval something like (loop [] (recur))
it's going to do that forever
user=> (loop [] (recur))
^CExecution error (ThreadDeath) at java.lang.Thread/stop (Thread.java:853).
null
user=>
Ctrl-C works for me.Well, obviously it's still much more convenient to have it as an option than to have none
you could try https://clojure.github.io/clojure/clojure.repl-api.html#clojure.repl/set-break-handler!
can't say I've tried that in a long while but seems like it doesn't work anymore :)
I'd like to except one domain for cors (cross-origin CSRF) ... I'm trying to put the flags :access-control-allow-origin [#""] and :access-control-allow-methods [:post] on (wrap-cors ...) but it's not working...
still "invalid anti forgery token"
@sova CORS and CSRF are different things. That error is not coming from CORS and you won't fix it by changing your CORS configuration. It means the submitting form does not contain the appropriate hidden field.
Can anyone help with using namespace keywords as enums? I have a namespace and was thinking to use keywords as possible statuses of an operation. There is an option to use namespace keywords. But i want "status" to be a qualifier too. For eg, if my namespace was my-app.ops
, I plan to have possible status as :my-app.ops.status/status-1
, :my-app.ops.status/status-2
. This was possible if i had a separate namespace as my-app.ops.status
, but i don't have one such. Does anyone have any opinion as how to proceed?
you can just use those keywords - keyword qualifiers don't have to refer to an existing namespace
user=> :i.made.this/up
:i.made.this/up
Yes, agree to that. I was looking for a way to leverage ::
(something like ::status/status-1
)
you can't use :: unless you have an alias, which must refer to an actual namespace
yes. Just wanted to know what people normally do if they encounter this situation. I think using the full kw name is the way to go if this is required. Thanks for the response!
this is an area we might work on in the future, it's a common request particularly in relation to spec
you can kind of hack it by calling create-ns
then alias
but I find it's easier to just use the full kw name
Random noob question: how can I turn an int into a sequence of digits? If I were in Python I could cast a int to a string and iterate thru each character, parsing it as an int. Is there a way to do this in clojure? Context: a programming puzzle. Ppl rarely need to doo this normally 😄
str
will make a string from an int
and strings are treated as sequences of characters by the seq fns
@seancorfield is CORS a drop-in replacement for anti-forgery?
Thanks! 🙇 I did notice this in the Repl, but didn’t recognize that I was getting ascii chars which needed Character/digit
a 3rd-party API is hitting my server, there is no potential for a CSRF token ..I just have to whitelist one endpoint but it's not clear. seems like CORS would be the whitelisting for certain domains... still important to have anti forgery?
I usually do something like this --
(reverse (map #(mod % 10) (take-while pos? (iterate #(quot % 10) 12034))))
(no comment on efficiency or completeness, but worked well for me for advent-of-code)
if you use reduce
/ cons
instead of map
you get the reversal for free
(loop [result () pool 12034] (if-not (pos? pool) result (recur (cons (mod pool 10) result) (quot pool 10))))
actually loop fits better than reduce, as there's no input sequence
@sova, i think that particular endpoint shouldn't be wrapped with csrf then. Instead, some other authentication mechanism can be used
so far i agree 100% 😃
but i'm not sure how to split my routes with run-server
I am bumping into a rather puzzling error. I am trying to run a docker-compose
command via clojure.java.shell
to set up an admin user in a docker container automatically.
If I run the following from Rich comment block, it works fine.
(sh/with-sh-env {"COMPOSE_INTERACTIVE_NO_CLI" 1}
(sh/sh "docker-compose" "exec" "-T" "portal" "htpasswd" "-bc" "/etc/nginx/unryo/htpasswd" "admin" "a"))
However, when my script runs the same command it doesn't work. The bulk of my script is the following:
(let [cmds [["docker-compose" "pull"]
["docker-compose" "up" "-d"]
["docker-compose" "exec" "-T" "portal" "htpasswd" "-bc" "/etc/nginx/unryo/htpasswd" portal-username portal-password]]]
(doseq [cmd cmds]
(println cmd)
(sh/with-sh-env {"COMPOSE_INTERACTIVE_NO_CLI" 1}
(println (apply sh/sh cmd)))))
when I define them as "admin"
and "a"
as above (println cmd)
prints [docker-compose exec -T portal htpasswd -bc /etc/nginx/unryo/htpasswd admin a]
oh and the (println (apply sh/sh cmd))
outputs {:exit 2, :out <htpasswd usage message>, :err }
(readacted)
could this have to do with the docker-compose command returning before the service is fully up and ready on the "up" step? I've typically seen a littering of sleep
invocations in scripts that do multiple docker-compose commands for this reason
also ProcessBuilder is not much more complex than sh/sh but a lot more powerful, if you ever need an OutputStream to poll as lines are ready instead of a string when the process exits, or an InputStream that can send inputs to the process as it runs, etc.
the full output of the "up" step is printed before the exec command is printed, meaning sh
is done iiuc
right, but "up" can return before the services it started are ready
when I run docker-compose up -d
at the command line it blocks until it displays all containers being ready
especially with java / clojure things, the "up" command says "your program started, I can exit" but that doesn't mean your service is going to handle a request in the next few seconds
some containers (eg. kafka, clojure) will report being ready but not yet handle requests
right, so that rules out that issue
I guess I should readLine from Java (I forget exactly what it's called) rather than read
(def application-routes
(routes
#'service-routes
(wrap-routes #'userland-routes wrap-anti-forgery)))
did the trick.
(disable anti-forgery on everything, selectively re-enable on non-api routes)Hi I am trying to understand how I can use lazy sequences. I am writing a sudoku generator, where I generate 9 random rows at a time, I hope to filter
according to some properties and then take the first nine valid rows as my sudoku grid. However I get eval timed out
after few seconds, maybe lazy sequences are not meant for this problem?
who times out?
(defn sudoku-grid []
(->> (repeatedly nine-valid-rows)
(filter valid-columns?)
(filter valid-blocks?)
first))
if you aren't probing your search space well you could either be enumerating quite a few bad apples or even never getting into a valid grid
this could be a simple algorithmic complexity question - if your guesses are naiive enough, you could run out of time before finding anything useful
you could work out how many random guesses it would take to guess a valid sudoku, then see if there's anything about your guessing that could be informed by past results, or rule out whole subtrees of the problem that aren't worth trying
lazy-seqs are slower than the equivalent loop, but only by a constant factor, so they won't be the source of this problem
you could try adding (take 10000)
between the repeatedly and the filters
that should return very fast
and likely establish that your first 10k guesses were all wrong
So...I'm having trouble understanding a peculiar bit of code I wrote
I'm trying to implement a REPL-driven workflow in the browser (cljs + shadow) by mounting components on a div
(I know DevCards exists, but I kind of wanted to figure this out on my own)
Anyway, long story short, I wrote mount
so I can call it in the REPL:
(defn playground-dom [] (.getElementById js/document "playground"))
(defn mount
([element]
(d/render [element] (playground-dom)))
([head & elements]
(let [node (reduce #(conj %1 (%2)) [:div (head)] (vec elements))
f (fn [] node)]
(d/render [f] (playground-dom))
)))
My question is, why does this multi-arity definition work but this one gives me a not ISeqable
error?
(defn mount
([element]
(d/render [element] (playground-dom)))
([& elements] ;; I removed the head!
(let [node (reduce #(conj %1 (%2)) [:div] (vec elements))
f (fn [] node)]
(d/render [f] (playground-dom))
)))
REPL messages:
> (defn x [] [:p "try"])
#'clojure-playground.core/x
> (mount x x x)
#object[Error Error: function clojure_playground$core$x(){
return new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"p","p",151049309),"try"], null);
} is not ISeqable]
Sorry, I guess you did, I didn't realize that comment was referring to the earlier version
Ahhhh that makes sense
I guess I could remove the 1-arity definition and make it fully general
Hi guys. I have a project generated by lein new reagent <app_name>
. I’ve made an API call in the backend and have a handler to parse the resulting JSON.
(defn get-cards [req]
(let [resp @(client/get "" options)]
(if (= 200 (:status resp))
(def cards-object (json/read-value (:body resp) (json/object-mapper {:decode-key-fn true}))))))
What’s the best way to pass cards-object
to a cljs React component on the frontend?@njerigachoka hello! First of all, consider def as a const in js.. so you never want to use it in instances like that one. Instead, you want to directly swap your reagent atom and store the result of the handler in there. In other words you want to def somewhere in your code an atom, being (def state (reagent.core/atom nil))
check out "Managing state in Reagent" https://reagent-project.github.io/ it should be very similar to that
This should be clear enough @njerigachoka :
;; This is your function that I assume you want to call on-mount (or from an on-click handler)
(defn get-cards [req state]
(let [resp @(client/get "" options)]
(swap! state assoc :http-response
(if (= 200 (:status resp))
(json/read-value (:body resp) (json/object-mapper {:decode-key-fn true}))
"Something went wrong!!"))))
(defn main-panel
(let [;; This is a reagent atom. Every time a value in here changes, the views that
;; use it will re-render automatically (that's the react bit of it). Usually,
;; you want to store your data in a map with several keys, which looks similar
;; to a JSON object.
state (reagent.core/atom {:other-state-stuff "other stuff"
:http-response nil})]
;; Anything happening before the (fn []...) function will fire only once, so this
;; space is great for your on-mount logic.
;; pass state to your function along with your other params
(get-cards req state)
(fn []
[:div
[:h1 "This is your UI component!"]
[:p "To print the state values use @"]
[:p (get @state :other-state-stuff)]
;; when nil, nothing will show in the UI
[:p (get @state :http-response)]
[:h3 "this is h3"]
[:p "this is p"]])))
@lucio or anyone else: so, the get-cards
handler is in clj/app_name/handler.clj
and my atom definition is in cljs/app_name/state.cljs
. How do I reference the state
atom in handler.clj? :require
?
You can do in any namespace (def state (reagent.core/atom {}), and then just require it as you guessed ;)
Usually If you def your state, you want to use it for your whole app.. you DONT need to create other ones.. that’s the idea behind re-frame... but its also true that it depends on your use cases
I’m getting an error saying that state cannot be located on the classpath. How do I require a cljs file in a clj file?
You can't. ClojureScript is going to run in your browser. Clojure is going to run on your server.
Only the front end (ClojureScript, in the browser) can update that state atom.
You want to set the state in the cljs code. When the server responds to the client, you can manage the response in the cljs code and set the state there.
You have two separate "applications" really and they have to communicate via HTTP requests (or something similar). Does that make sense @njerigachoka?
I think so. So if I’m making the API request in the backend, and updating state in the frontend, how do I pass the API request response to the frontend if I can’t update the atom?
Thanks, this makes more sense. What does calling “on-mount” mean? (Sorry, I’m a real beginner 😅)
Glad you got there 🙂 No problem! @njerigachoka
btw thanks @hiredman kinda got distracted there for a sec
this community is awesome
@lucio or anyone else: so, the get-cards
handler is in clj/app_name/handler.clj
and my atom definition is in cljs/app_name/state.cljs
. How do I reference the state
atom in handler.clj? :require
?
What is the state of http-kit
? Is it production ready? I am seeing ptaoussanis has been maintaining it.
it's not broken, I don't think http-kit or aleph are in active development
What is the recommended nio web server for Clojure?? It seems Jetty still only supports a thread per connection model
I don't know if there's a great choice - both http-kit and aleph work right now
also, you can get very far before thread-per-connection is a problem
the http transport / thread layer is not the bottleneck in most clojure apps, and once it is you get more results with eg. nginx as a reverse proxy than you do with nio anyway
@lpan For what it's worth, we use Jetty very heavily in production (driving 40+ dating websites with a global customer base).
We swapped to http-kit at one point (due to a weird thread death bug in Jetty that later got fixed) but New Relic doesn't support http-kit so we swapped back to Jetty.
I am building an app that might handle potentially thousands of concurrent websocket connections. I thought maybe I should start with an nio-based HTTP server 😜
We have a real-time chat server based on Netty directly, using Java interop, that's all based on concurrent websockets. My colleague knows more about it than I do tho'...
I think Netty has the nio model 🙂 And irc alepth is the ring adaptor for Netty. Thanks a lot for sharing Sean!
I'm pretty sure we chose Netty because we had a requirement for http://Socket.IO support and we ending up using Netty simply because it was what the server-side Java implementation of http://socket.io was built on...
Yeah, my colleague just confirmed that: we had to support http://socket.io on the server and the most mature library appeared to be http://netty-socket.io so that's what we used, and that's why we use Netty 🙂