Hello, anyone played enough with SSE to have an id...
# http4k
r
Hello, anyone played enough with SSE to have an idea how to resolve a CORS error ? the browser error is:
Copy code
logs.html:1 Access to resource at '<http://localhost:8080/logsse>' from origin '<http://localhost:8880>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Couldn't figure out how to set that header...
d
Interesting - which engine are you using?
r
undertow
it works if on the same port but looks like there is also are CORS protected, others talked about this problem using another fk solved by cors header.
d
can you put together a reproducer and will take a look?
r
No problem I'm doing it and will share the repo asap. thanks, I tried to poke around see how the SseMessage is sent to the server but couldn't figure out how I could add a header to the response...
could reproduce it in a simple file:
Copy code
fun main() {
    val client = routes(
        "/test" bind Method.GET to { req: Request ->
            Response(Status.OK)
                .header("content-type", ContentType.TEXT_HTML.value)
                .body(html())
        }
    )

    val serversse = sse(
        "/hi" bind { sse: Sse ->
            sse.send(SseMessage.Data("Hello to you"))
        }
    )

    val serverWithSse = PolyHandler(client, sse = serversse)
        .asServer(Undertow(8080)).start()


    val serverClient = client.asServer(Undertow(8880)).start()
}

fun html() = """
        <html>
            <head>
                <script>
                    const SSE_URL = `<http://localhost:8080/hi>`;
                    let evtSrc = new EventSource(SSE_URL);
                    evtSrc.onmessage = (event) => { console.log(event.data)
                        document.getElementById("msg").innerText = event.data
                    }
                    evtSrc.onopen = (event) => console.log("SSE Opened")
                    evtSrc.onerror = (event) => console.log("SSE error")
                </script>
            </head>
            <body>
                <h3>Last message</h3>
                <div id="msg">(no msg received)</div>
            </body>
        </html>
    """.trimIndent()
running the main will start two servers: if you go to
<http://localhost:8080/test>
you will see no error and "hello to you" as last message but if you go to
<http://localhost:8880/test>
you don;t have the message and if you check the browser console you'll see the error:
Copy code
Access to resource at '<http://localhost:8080/hi>' from origin '<http://localhost:8880>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
d
Thanks: it's interesting because the SSE does work absolutely fine in - say- the hotwire demo form the examples repo
And in the webinar we also served the time via SSE. Might be worth having a dig into those examples to see why they are different
Will also check from this end when I get a chance.
r
As long as it's one server there's no problem, but when a different server try to call a sse on another server (on different port), that's when CORS strikes
d
Sorry - bit confused. You've got 2 servers there running on the same port
r
and it's understandable you don't want any other site serving pages that uses underneth your services.
d
If you try to call across ports from the browser, you will get a cors error. You need a Cors filter on the route is the origin isn't the same
r
No, 2 servers on different ports (can't be on the same unless you use a proxy on one to route a path to another).
Yes but how to set a cors filter on a SSE router ?
d
Good question! Wondering about the use case though.
r
Having the app than emits logs or metrics to a web client (served by another server) without adding the logs visualization on the real application
Other usecase by someone reporting the problem on jobby-netty https://github.com/jooby-project/jooby/issues/1067
d
well we definitely don't set the response headers on the request so that is a thing - we don't even have a way to set them with the current object model - we can look at maybe adding them
but am trying at the undertow level and it still seems to be not playing nice
r
I see that in
Http4kUndertowSseCallback
the connected function receive as param a
ServerSentEventConnection
and that object has a
getResponseHeaders
i guess is not read only, although but the comment is a bit confusing
Copy code
Returns:
The response headers from the initial request that opened this connection
d
yeah - I've tried mutating them but it doesn't seem to have any effect
r
it's too late if the first response have already been sent
d
yep - but I don't actually see where we could intercept them.
all the way down in the ServerSentEventHandler from Undertow in handleConnect, I can see that it calls through to "connected". it seems like it shouldn't be too late there..
r
Too deep the rabbit hole I'm getting lost... looks like
exchange
is the key but you can't seem to access it directly and if getResponseHeaders is too late, I can't see how htt4k's adapter could set it... and don't count on undertow.io documentation
d
I'm confused with it - the undertow handler just calls straight through to the exchange but it seems like that is too late to set the headers
I'm not convinced that it works in undertow tbh
r
thanks for the investigations, quite surprised is an edge case... are there any other servers with sse support planned ?
d
not so far, but if you want to add support then we'll accept a PR... 🙂
r
it kinda obsessed me so poked around., adding logs around, and start to wander if it's the server or navigator problem as I see the SseConsumer is called
Copy code
val serversse = sse(
        "/hi" bind { sse: Sse ->
            println(sse.connectRequest)
            sse.send(SseMessage.Data("Hello to you"))
            println("Data sent")
        }
    )
the lines are well printed on each call, I guess if it was the server that blocked it shouldn't allow to run the heandler for nothing. also logging the
connectRequest
I see in the request
Sec-Fetch-Site: same-site
so is it the JS that only accept connection to the same site ? couldn't find any info on how I could change that... Well I guess I should try a JS SSE server to check that... 🙂 Anyway, just to share the last updates.
tried with a JS backend and got the same cors problem, even trying to set the server CORS headers to allow all does not fix it so i guess it's a browser limitation. I could try to use a server side sse client to talk between servers and the browser only talk to its server... or use another protocol (rsocket?).
👍 1
FWIW WebSockets does not have this limitation so no probem with replacing sse with ws. Might be some infrastructural ones as it's a different protocol than sse's http, but regarding cors, it fixes the problem. Really sorry David for wasting your time 😄
d
@Razvan np. it's not wasting time if we learn something 🙂
👍 1