Hi, using SSE and I cannot figure out how to setup...
# ktor
r
Hi, using SSE and I cannot figure out how to setup CORS - web app (localhost:8081) deployed by the server running at localhost:8080. It works for my regular requests, but not for SSE. (works fine outside the browser as well)
Copy code
Access to fetch at '<http://localhost:8080/sse/company/1>' from origin '<http://localhost:8081>' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
What am I missing? Client:
Copy code
client.serverSentEventsSession( url, deserialize = { typeInfo, jsonString -> ... })
Server:
Copy code
install(CORS) {
        anyHost()
        allowMethod(HttpMethod.Options)
        allowMethod(HttpMethod.Get)
        allowMethod(HttpMethod.Post)  // Allow POST requests
        allowCredentials = true
        allowNonSimpleContentTypes = true
        allowHeader(HttpHeaders.Authorization)
        allowHeader(HttpHeaders.ContentType)
        allowHeader("X-Requested-With")
        allowHeader(HttpHeaders.AccessControlAllowOrigin)
        allowHeader(HEADER_APP_VERSION)
        allowHeader(HEADER_SERVER_VERSION)
        exposeHeader(HEADER_SERVER_VERSION)
 }
 install(SSE)
    routing {
        sse(url, serialize = { typeInfo, it ->...
        }) {...}
a
What is the response from the server if you send an
OPTIONS
request to http://localhost:8080/sse/company/1 with the appropriate
Origin
header?
r
It returns 403 when I pass the Origin, an 404 without it. However, in postman and curl, the GET on the same works without issue and receives the server events, even with this same Origin header.
a
Do you use Ktor's HttpClient to receive the SSE events? I cannot reproduce the problem when running the following JavaScript code:
Copy code
const evtSource = new EventSource("<http://localhost:8080/sse>");

evtSource.onmessage = (event) => {
    console.log(event.data)
}
u
Copy code
install(CORS) {
        anyHost()
        allowHeaders { true }
        anyMethod()
    }
only for debugging
r
Yes, both ktor jvm server and ktor wasm client but the error I can reproduce in postman or curl as well options call always returns 403
Copy code
curl --location --request OPTIONS '<http://localhost:8080/test_sse>' \
--header 'Accept: application/json' \
--header 'Origin: <http://localhost:8081>'
GET call works fine but my wasm client makes an options call before trying to connect to the SSE endpoint
a
The server responds as expected if the
Access-Control-Request-Method
header is included in the preflight request:
Copy code
--header 'Access-Control-Request-Method: GET'
r
Ah, yeah I was missing
Copy code
allowHeader("cache-control")
in the CORS block. (Chrome sends it in the preflight request)
s
I'm running into the exact same issue. But no matter what I allow in the CORS plugin, an OPTIONS request always returns 403, while all other requests, and even the SSE route, work fine with postman
Copy code
install(CORS) {
    allowMethod(HttpMethod.Get)
    allowMethod(<http://HttpMethod.Post|HttpMethod.Post>)
    allowMethod(HttpMethod.Options)
    allowMethod(HttpMethod.Put)
    allowMethod(HttpMethod.Delete)
    allowMethod(HttpMethod.Patch)
    allowHeader(HttpHeaders.Authorization)
    allowHeader(HttpHeaders.Upgrade)
    allowHeader(HttpHeaders.ContentType)
    allowHeader(HttpHeaders.Origin)
    allowHeader(HttpHeaders.AccessControlAllowOrigin)
    allowHeader(HttpHeaders.AccessControlRequestMethod)
    allowHeader(HttpHeaders.AccessControlRequestHeaders)
    allowHeader("Access-Control-Request-Private-Network")
    allowHeader(HttpHeaders.CacheControl)
    allowNonSimpleContentTypes = true
    allowCredentials = true
    allowSameOrigin = true
    allowXHttpMethodOverride()
    anyHost()
    anyMethod()
    allowHeaders { true }
    allowOrigins { true }
}
a
Can you share the relevant TRACE-level logs?
s
TRACE io.ktor.server.plugins.cors.CORS - Return Forbidden for /bla: CORS method doesn't match HttpMethod(value=OPTIONS)
also not sure what this exactly means. it's a request sent with OPTIONS and it's also allowed in my cors setup
Copy code
TRACE io.ktor.server.plugins.cors.CORS - Respond preflight on OPTIONS for /v1/annotations
TRACE io.ktor.server.plugins.cors.CORS - Return Forbidden for /v1/annotations: CORS method doesn't match HttpMethod(value=OPTIONS)
TRACE i.k.s.p.c.ContentNegotiation - Skipping response body transformation from HttpStatusCode to OutgoingContent for the OPTIONS /v1/annotations request because the HttpStatusCode type is ignored. See [ContentNegotiationConfig::ignoreType].
TRACE i.k.s.p.compression.Compression - Skip compression for /v1/annotations because no accept encoding provided.
INFO  io.ktor.server.Application - URL: /v1/annotations, Status: 403 Forbidden, HTTP method: OPTIONS, User agent: null
TRACE i.k.s.p.compression.Compression - Skip decompression for /v1/annotations because no content encoding provided.
TRACE io.ktor.server.routing.Routing - Trace for [v1, annotations]
a
Have a browser sent this preflight request, or did you send it via an HTTP client?
s
I sent this with postman now to debug, but I've originally noticed this issue in the webapp - browser
a
Then, you should add the
Access-Control-Request-Method: GET
header replacing the GET method with the original request method.
s
ah, yes now it works. and I just checked with the browser again, it also works. it was indeed that the cache-control header was missing in my original cors setup. thanks!
e
Could you share the final setup? I think we can have an extension for cache friendly CORS
s
of the cors plugin? I haven't changed anything else, neither double checked what's really necessary yet.
Copy code
install(CORS) {
    allowMethod(HttpMethod.Get)
    allowMethod(<http://HttpMethod.Post|HttpMethod.Post>)
    allowMethod(HttpMethod.Options)
    allowMethod(HttpMethod.Put)
    allowMethod(HttpMethod.Delete)
    allowMethod(HttpMethod.Patch)
    allowHeader(HttpHeaders.Authorization)
    allowHeader(HttpHeaders.Upgrade)
    allowHeader(HttpHeaders.ContentType)
    allowHeader(HttpHeaders.Origin)
    allowHeader(HttpHeaders.AccessControlAllowOrigin)
    allowHeader(HttpHeaders.AccessControlRequestMethod)
    allowHeader(HttpHeaders.AccessControlRequestHeaders)
    allowHeader("Access-Control-Request-Private-Network")
    allowHeader(HttpHeaders.CacheControl)
    allowNonSimpleContentTypes = true
    allowCredentials = true
    allowSameOrigin = true
    allowXHttpMethodOverride()
    anyHost()
}