Ktor websockets creates the server at `<http://0.0...
# getting-started
r
Ktor websockets creates the server at
<http://0.0.0.0:8080>
. But on javascript frontend websocket clients expect the url to begin with
wss://
. How can I change ktor to use wss?
r
I think
wss://
is secure, have you tried using
ws://
? My recollection is that a
ws://
will start with an
http://
request and seek to upgrade the protocol to
ws
, and
wss://
will do the same with
https://
r
I don't think javascript's websocket implementation lets me specify which protocol I attempt
I attempt this in my js
Copy code
const websocket = new WebSocket('<http://0.0.0.0:8080/ws>');
because my kotlin server has
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        install(CORS) {
            anyHost()
        }
js browser shows console log
Copy code
Firefox can't establish a connection to the server at <ws://0.0.0.0:8080/ws>.
g
If you use
webSocket("/ws")
, you get requests at
<ws://localhost:8080/ws>
. See more in docs: https://ktor.io/docs/server-websockets.html#api-overview
r
@Gleb Minaev yeah I'm doing that. plugins/Sockets.kt
Copy code
package com.example.plugins

import com.example.Connection
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.websocket.*
import io.ktor.websocket.*
import java.time.Duration

fun Application.configureSockets() {
    install(WebSockets) {
        pingPeriod = Duration.ofSeconds(15)
        timeout = Duration.ofSeconds(15)
        maxFrameSize = Long.MAX_VALUE
        masking = false
    }
    routing {
        val connections = mutableListOf<Connection>()

        webSocket("/ws") {
            println("Adding user!")
            val thisConnection = Connection(this, "user-1")
            connections += thisConnection
            try {
                send("You are connected! There are ${connections.count()} users here.")
                for (frame in incoming) {
                    frame as? Frame.Text ?: continue
                    val receivedText = frame.readText()
                    val textWithUsername = "[${thisConnection.username}]: $receivedText"
                    connections.forEach {
                        it.session.send(textWithUsername)
                    }
                }
            } catch (e: Exception) {
                println(e.localizedMessage)
            } finally {
                println("Removing $thisConnection!")
                connections -= thisConnection
            }
        }
    }
}
Application.kt
Copy code
package com.example

import com.example.plugins.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.cors.CORS
import io.ktor.server.plugins.cors.routing.*

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        install(CORS) {
            anyHost()
        }
    }.start(wait = true)
}

fun Application.module() {
    configureSockets()
    configureRouting()
}
when i gradle run it also says this
[DefaultDispatcher-worker-2] INFO  ktor.application - Responding at <http://0.0.0.0:8080>
OK it works if I change it from
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        install(CORS) {
            anyHost()
        }
    }.start(wait = true)
}
to
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
        .start(wait = true)
}
not sure how I am supposed to install CORS but it apparently is not caring about cors anymore either.
g
I guess you should not put
anyHost()
(am I correct that it contains routings above?) inside
install(CORS) { ... }
, but put it right after it. I'll check it right now. P.S. Sorry, I did not find declaration of
module
.
Ah! Have you applied your
module
function in
embeddedServer
? I don't see in the first listing of your last message, but it is explicitly applied with
Application::module
in the second listing.
r
I had not in the first. When I tried installing CORS it would not work unless i removed the module thing
the generated project comes with the module = Application::module
but that overload does not support install(CORS)
g
But if you remove the
module
function,
configureSockets
won't be applied as well. Becausse
configureSockets
is placed only in
module
.
r
yeah I noticed that
so, not sure how i am supposed to do the module thing and also install cors
h
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
        install(CORS) {
            anyHost()
        }
        module()
    }.start(wait = true)
}
j
> the generated project comes with the module = Application::module > but that overload does not support install(CORS) This is not really a different overload, just 2 ways to pass the same
module
argument. Here is some background to clarify how you should understand that you need Philip's approach. First, there is a lot of useful information about function types, lambdas, and function references in the docs. If you check the docs of embeddedServer, you'll notice that its last parameter (
module
) is a function of type
Application.() -> Unit
. This means it's a function that has a receiver of type
Application
, no parameters, and return type
Unit
(meaning it doesn't return anything useful). Instances of functions can be "created" in several ways. One of them is to use a lambda expression with the code directly within braces (
{ a, b -> a + b }
), another is using a function reference (
::functionName
,
SomeType::methodName
, ...), and there are other ways too. In your case, you can pass the
module
parameter of
embeddedServer
as a lambda or with a function reference. When a lambda argument is the last argument of a function call, it can be passed outside the parenthese of the function call. This is why all of these are the same thing:
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module) // reference
        .start(wait = true)

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = { module() }) // lambda
        .start(wait = true)
}

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") { module() } // lambda outside parens
        .start(wait = true)
}

fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0") { // lambda outside parens, but with new lines
        module()
    }.start(wait = true)
}
Since the
module()
function is defined (by you) as an extension on
Application
, you can use it directly in the lambda because
embeddedServer
declares that the function you pass will get an
Application
instance as a receiver. If you use a lambda, you can call
module()
but you can also write any code there, including calling other functions that can work with a receiver of type
Application
, such as
install()
.
When you want to add a call to
install()
and you start from the first form, you should first swith to the last form, and then you can add
install()
next to the call to
module()
. Another option is to keep the function reference to
Application::module
, but add the call
install()
directly inside `module()`:
Copy code
fun main() {
    embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
        .start(wait = true)
}

fun Application.module() {
    install(CORS) {
        ...
    }
    configureSockets()
    configureRouting()
}