Fork me on GitHub
#immutant
<
2016-03-11
>
kardan06:03:37

I tried to hook up SSL with Immutant web but Chrome complains with “ERR_SSL_VERSION_OR_CIPHER_MISMATCH “. I can’t find a way to define suites. Am I overlooking something? (not done anything with ssl before …)

moizsj10:03:38

hey everyone, I am trying to use the immutant/web component 2.1.3. When I start up a repl and require the immutant.web ns, i get this error -- Exception namespace 'potemkin.utils' not found clojure.core/load-lib (core.clj:5380) anyone seen this? I've added org.immutant/web "2.1.3" in my project deps. also tried adding all of immutant to the deps but same error.

moizsj10:03:44

i know potemkin is a dep for immutant, but I shouldnt be needing to add it manually right? I ran lein deps dint help

moizsj10:03:07

found the reason - potemkin.utils compile was failing as it needed the vector symbol from the clj-tuple lib, and for some reason my project was pulling in a lower version of clj-tuple that dint have it. Fixed it by explictly adding the latest clj-tuple as a dep

moizsj11:03:31

does someone how i could check for a subprotocol before accepting a client websocket connection? (with immutant)

tcrawley14:03:19

@moizsj: we don't expose the subprotocol currently, but I think we could make it part of the request map for upgrades - would you mind filing an issue for that? https://jira.jboss.org/jira/secure/CreateIssue.jspa?issuetype=2&amp;pid=12311821

moizsj14:03:01

@tcrawley: thanks for the answer. as a workaround, I plan to write a custom version of the as-channel method that will inspect the headers from the request map to check the subprotocol. it'll pretty much be a copy except the header check. do you have any better ideas for a workaround?

moizsj14:03:41

@tcrawley: our front end currently sticks the subprotocol in the headers

tcrawley15:03:40

do you need a custom as-channel if you are inspecting headers? can't you just look at the headers in the request map, and decide to call as-channel or not based on those?

moizsj15:03:05

@tcrawley: what I need is a way to get in the middle of the handshake between the server and client. I need to check the header for subprotocol, then add the Sec-WebSocket-Protocol header to the response (and some other custom headers). And if it goes through, only then would i want the :on-open to be called

moizsj15:03:59

we are using http-kit at the moment and we had to write a similar custom with-subproto-channel macro (similar but not same) as the with-channel macro of http-kit. And now trying to port to immutant/web

tcrawley15:03:50

ah, that's tougher - in http-kit, you have more access to the upgrade process. In Immutant, that's hidden - we had to do that to be able to support WildFly. If you plan to just use Immutant outside of the container, you could probably do it with a custom Undertow HttpHandler

moizsj15:03:32

so here's the http-kit version

moizsj15:03:45

(defmacro with-channel
[request ch-name & body]
  `(let [~ch-name (:async-channel ~request)]
     (if (:websocket? ~request)
       (if-let [key# (get-in ~request [:headers "sec-websocket-key"])]
         (do (.sendHandshake ~(with-meta ch-name {:tag `AsyncChannel})
                             {"Upgrade"    "websocket"
                              "Connection" "Upgrade"
                              "Sec-WebSocket-Accept" (accept key#)})
             ~@body
             {:body ~ch-name})
         {:status 400 :body "Bad Sec-WebSocket-Key header"})
       (do ~@body
           {:body ~ch-name}))))

moizsj15:03:53

the custom part (which I cant post here) is checking headers for subprot, then augmenting the headers map handed out to the sendHandShake

moizsj15:03:05

@tcrawley: would you kindly elaborate a bit on your suggestion? what do you mean by Immutant without a container? And when you say using an Undertow Handler, is that something that'll need to written in Java? And i assume wont be Ring compatible?

tcrawley15:03:17

Immutant supports two modes of deployment: an uberjar (just like you do with http-kit), and as a special war file to a WildFly or EAP application server

tcrawley15:03:58

in the first case, we interact directly with the Undertow web server. in the container case, we have to interact with the servlet api, so have different implementations for websockets

tcrawley15:03:45

because of that, your ring handler isn't called during the ws handshake, but after it is completed (undertow would allow us to do it during the handshake, but the servlet api does not)

tcrawley15:03:45

so you would have to write a custom HttpHandler that handled the upgrade process. you could write it in clojure using proxy or reify

tcrawley15:03:10

but it may be difficult to have that work with our async api

tcrawley15:03:16

I'd have to investigate it

tcrawley15:03:50

the servlet websocket api is terrible, and I hate that it puts this limitation on us

moizsj15:03:44

so suppose I wrote the HttpHandler and the aync api dint work , could i still build some Compojure routes, pass the handler (of the routes) to the run, and one of those routes explicitly dealt with upgrades ("/xyz/websock")? would that work?

moizsj15:03:04

in that ws-handler, I'd need to callback into equivalents of :on-open, :on-message, :on-close

tcrawley15:03:06

you couldn't nest the HttpHandler route under compojure, but could do (run compojure-app :path "/") (run http-handler :path "/websock")

moizsj15:03:52

two separate servers?

tcrawley15:03:55

let me take a look at the code to see if it would be possible to insert a handler in the chain that would allow you to use one run call and still use async

tcrawley15:03:08

one server, with multiple handlers registered

tcrawley15:03:16

@moizsj: actually, hmm. outside of the container, we do call your ring handler before calling the upgrade handler, and only call the upgrade handler if your ring-handler returns the result of as-channel

tcrawley15:03:56

so you should be able to do `

tcrawley15:03:00

sorry, working on an example

moizsj15:03:08

take your time Toby..you've been super helpful

tcrawley15:03:42

in your above code, are you ensuring the sec-websocket-key is set before letting the upgrade continue?

tcrawley15:03:13

and returning a 400 if it is not there?

tcrawley15:03:33

or is there some back and forth between the client and server during the handshake there?

moizsj15:03:02

that part is the same as the above http-kit version (nothing custom)

tcrawley15:03:26

what does your custom code do, roughly?

moizsj15:03:46

adds an extra Sec-WebSocket-Protocol header to the above map passed to sendHandShake, and a bunch of extra headers

tcrawley16:03:21

and .sendHandshake then sends those headers back to the client as part of the upgrade negotiation?

moizsj16:03:27

and of course there's an extra if before calling the sendHandShake to check for the subprotocol in the request map

tcrawley16:03:15

I may have something for you

moizsj16:03:58

is waiting with baited breath

tcrawley16:03:03

the request map should have a :server-exchange in it. That is an HttpServerExchange object (http://undertow.io/javadoc/1.3.x/io/undertow/server/HttpServerExchange.html). From it, you can get the request headers (`.getRequestHeaders`). You can then mutate the headers, then call as-channel and return. That exchange is then passed to the upgrade handler, with the headers set. If the upgrade succeeds, your :on-open will get called

tcrawley16:03:37

checker is what calls your ring handler, and looks at the response to see if it has a channel body, and then calls the upgrade handler (`wsHandler` there) if it does

tcrawley16:03:04

I know that's confusing, working on an example ring-handler now

moizsj16:03:22

ok..so I simply inspect the exchange (after pulling it out from the request map), look for the subprot header, add my custom headers, and then return as-channel from the ring handler?

tcrawley16:03:35

yeah, basically

moizsj16:03:27

ok cool, I think i got the picture. the example would surely help.

tcrawley16:03:44

@moizsj: something like (untested):

(import 'io.undertow.util.HttpString)

(defn handler [req]
  (let [exchange-headers (-> :server-exchange .getRequestHeaders)]
    (when (check-subprotocol (:headers req))
      (.put exchange-headers (HttpString. "header-name") "header-value"))
    (as-channel req
      :on-open (fn [ch])
      ;; etc
      )))

tcrawley16:03:33

and you can use immutant.web.internal.headers/set-header instead of calling .put directly on exchange-headers to avoid that HttpString nonsense

moizsj16:03:49

got it. and you reckon this coupling to the exchange object is not risky? could it be removed/renamed since its part of the public contract?

tcrawley16:03:20

(set-header exchange-headers "foo" "bar")

tcrawley16:03:33

it's safe, as long as you never plan to use this inside a WildFly container

moizsj16:03:50

nah, standalone

tcrawley16:03:57

it's part of the undertow public api, so would only break if immutant moved away from undertow, which won't happen

tcrawley16:03:39

I'm not 100% this will work, but I am fairly confident

tcrawley16:03:58

and if it doesn't work, I think I can make it work with minor changes to immutant itself

moizsj16:03:16

super. thanks for taking so much time to help. I'll come back next week and let you know how this pans out.

tcrawley16:03:56

yes, please let me know how it goes

tcrawley16:03:00

and it's my pleasure!