Hi All, I've been trying to create a websocket server. I am new to this... but firstly there don't seem to be enough good examples which show exactly how to setup a websocket and secondly they seem to use a combination of libraries. Is there some monolithic library and an accompanying example which will show me how to create a websocket server? Many thanks for your help!
If you're looking for a server+client, then maybe Sente is the way to go. If you're looking at just the server, then maybe https://github.com/sunng87/ring-jetty9-adapter.
@abhishek.mazy I think the simplest would be to go with http-kit, which handles websockets already. Try this :
$ clj -Sdeps '{:deps {http-kit/http-kit {:mvn/version "2.8.1"}}}'
(require '[org.httpkit.server :as http])
(defn app [req]
(case (:uri req)
"/ws" (http/as-channel
req
{:on-receive (fn [ch message] (http/send! ch "Echoing back: " message))
:on-close (fn [ch status-code] (println "Closed with code:" status-code))
:on-open (fn [ch] (println "Got a connection!" ch))})
{:status 404
:body "Page not found"}))
(http/run-server app {:port 8080})you should be able to connect to <ws://localhost:8080/ws>
you can quickly try it using something like https://piehost.com/websocket-tester
How about this example? https://github.com/ring-clojure/ring-examples/tree/master/websocket-chat
@jpmonettas I'll give it a go
According to the Jetty adapter I've linked, it's pretty much just as easy:
(require '[ring.adapter.jetty9 :as jetty])
(def ws-handler {:on-open (fn [ws])
:on-error (fn [ws e])
:on-close (fn [ws status-code reason])
:on-message (fn [ws message])
:on-ping (fn [ws bytebuffer])
:on-pong (fn [ws bytebuffer])})
(defn app [req]
(if (jetty/ws-upgrade-request? req)
(jetty/ws-upgrade-response ws-handler)))
(run-jetty app)
With the added benefit of Jetty being a kind of a universal constant that reliably works with anything and everything.I'll try all these different approaches out. Many thanks all. This helps!
@p-himik I can't seem to find the jetty/ws-upgrade-response in the library
do you know where to find a response?
Hmm, it seems that the author has changed how the underlying functionality is supposed to work but forgot to update the README. Can you try this instead? https://github.com/sunng87/ring-jetty9-adapter/blob/master/examples/rj9a/websocket.clj
@p-himik when I try the code from https://github.com/sunng87/ring-jetty9-adapter/blob/master/examples/rj9a/websocket.clj I cant get past the require lines as I get the following error
; Execution error (ClassNotFoundException) at java.net.URLClassLoader/findClass (URLClassLoader.java:445).
; org.eclipse.jetty.server.handler.HandlerCollection
My deps.edn includes the following libraries
org.ring-clojure/ring-core-protocols {:mvn/version "1.14.2"}
org.ring-clojure/ring-websocket-protocols {:mvn/version "1.14.2"}
info.sunng/ring-jetty9-adapter {:mvn/version "0.7.0"}
org.eclipse.jetty/jetty-server {:mvn/version "12.1.0"}
org.eclipse.jetty/jetty-util {:mvn/version "12.1.0"}
org.eclipse.jetty.websocket/jetty-websocket-jetty-api {:mvn/version "12.1.0"}
org.eclipse.jetty.websocket/jetty-websocket-jetty-server {:mvn/version "12.1.0"}
I am not sure what I am doing wrong. This was the original problem that I faced which prompted the original question.I just tried it. Copied it verbatim, added just two dependencies:
info.sunng/ring-jetty9-adapter {:mvn/version "0.37.6"}
ring/ring-core {:mvn/version "1.14.2"}
And it worked like a charm without any changes.ok I'll try with your suggestions and let you know
thanks again!
No problem.
Does anyone get weird lints with clj-kondo?
(ns learning.LearnExample)
(defstruct car :type :price)
This block produces Unresolved symbol: car (clj-kondo) in car. Why?I now have a PR up for this which I will merge shortly. Turns out clojure itself still uses defstruct in clojure.pprint :)
https://github.com/clj-kondo/clj-kondo/pull/2620/files
i don't think clj-kondo supports defstruct
in general, you want to use records, not structs
Ya, structs are not used since a long time, as defrecord fully superset it. Of course, in Clojure nothing is ever removed, and they still work as promised. But I guess clj-kondo pretends like they don't exist.
I just switched to record and clj-kondo works fine. Thank you 🙂 I guess the only way to enforce types in a record is using metosin/malli ?
@borkdude Is this true?
Records create a real Java type of the same name.
I guess it depends what you mean by "enforce"? But a Car record is of type Car
I mean if I have a:
(defrecord Car [type price])
How do I make sure price is of type int, float, double or at the very least "number"?I'm placing this question in a weird way, but I'd like to have some sort of soft-type checking at least.
Since I can literally pass anything right now to price and Clojure will be like "sure"
you do that at creation time with any number of validation techniques or libraries
(defn make-car [price] (assert (int? price)) (->Car price))
(on my phone or i'd be more elaborate)
No worries I totally get it thank you Noah.
I see, so is this something common in Clojure? Sorry for the weird question but I'm coming from plain Java.
I don't mind validating input types if this is Clojure's design principle.
Ya, there's no baked in type validation, neither runtime nor statically. What people normally do is this:
(defn make-car [type price]
(validate ...)
(->Car type price))
How the "validate" happens is up to you. You can use Malli, Spec, you can manually do it, etc.I'm sure most responses will vary with this question, but I'm looking for an Industry standard (if there's one). What would you say is most used? Malli, Spec or manual validation? I'm sure there's always some level of manual validation even when using a library.
I'd say it's most common to just embrace the unknown of types 😛, and not validate. But for like the core entities of an app, I think it's common to have a form of spec (malli, spec, or other spec-like definition) and validate on creation and update to make sure that it maintains the invariants. These would often start to even embed business logic in them, like price needs to be between 1000$ and 1000000$ for example.
Plumatic Schema used to be the most popular lib for this, then it turned to Spec, and now Malli is gaining ground. There's probably a good mix of people still using all 3.
> I'd say it's most common to just embrace the unknown of types 😛, and not validate.
I feel super privileged here because while I programmed in a super statically typed language, I also work with the complete opposite everyday Python, so Clojure doesn't feel super weird to me in that front. I've learned to embrace the chaos, so I totally understand what you mean xD But at the same time, for the sake of good composition I like to see the types of each model property.
I guess, from what I just saw in Malli this aligns with what I'm looking for.
I don't want to go super hardcore on having everything typed, I want to embrace the Clojure paradigm.
Thank you 🙂 Your words do make me feel a bit more relaxed ^_^
Ya, so I think what's happening is you are trying to find what to use in Clojure to represent your "data model". And in Java, a class also acts as your schemas for your main entities and such. Definitely for that in Clojure the "official" thing would be spec. But it works differently, you basically define schemas, as specs. But they don't automagically tie themselves to a custom data structure that you can instantiate.
Forget types. They over-prescribe things.
Use maps instead of records.
Unless you really need the underlying Java type for dispatch.
> Unless you really need the underlying Java type for dispatch. I really don't.
But ya, I'd say start without specs as well. See how far the most simple thing takes you (don't even use records). You want to model a Car?
(ns my-app.car)
(defn make-car
[type price]
{:type type
:price price})yeah maybe i'm just really overcomplicating it
this looks a lot more digestable
And to be honest, people often don't bother having one-namespace per entity like that. Cause Clojure people, we like concise haha. We don't like having a million LOC, a million files. So you'll often see:
(ns my-app.entities)
;;;; Car
(defn make-car
[type price]
{:type type
:price price})
;;;; Truck
(defn make-truck
[type price num-of-wheels]
(assoc (make-car type price) :num-of-wheels num-of-wheels))i'm definitely following this, thank you, this is the kind of feedback and learning i've been looking for. i really appreciate your help 🙏
it makes a lot more sense seeing your examples
i see myself for sure still holding onto oop a lot
in this sense
Giving up types can be hard. They feel like a safety net, but they really can be a straitjacket 🙁 Same with OO - we're so used to it in the mainstream but FP is even longer-lived and works really well (and is simpler).
A lot simpler, most of the time IMHO
Clj-kondo doesnt support defstruct but since it has come up before I might implement it. Issue welcome
I re-opened this issue https://github.com/clj-kondo/clj-kondo/issues/1894