Ktor Websocket session closes by itself. Is there a way to keep the opened session active?

35 views Asked by At

I've been delving into Ktor to learn about Websockets, but despite spending numerous hours researching similar questions, I'm still unable to pinpoint the root of my issue. I'm trying to open multiple websocket sessions using Ktor to be able to send and receive messages between the users. However, once I create a websocket session, it closes itself. I'm initializing the websocket session once the main login page of my application opens up.

I've also tried understanding the answer to this same problem, but they're using a html file. Is there another solution to this problem?

Here is the code where I create the websocket session:


// In CommManager object
    suspend fun receiveMessage(session: DefaultWebSocketSession) {
        var text = ""
        while (connected.get() == true) {
            try {
                for (othersMessages in session.incoming) {
                    othersMessages as? Frame.Text ?: continue
                    text = othersMessages.readText()
                    println(text)
                }
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

// In the login page 
    suspend fun createWebsocketSession() {
        val session = client.webSocketSession(method = HttpMethod.Get, host = "hosturl", port = 8080, path = "/webSocketSession")
        CommManager.connected.set(true)
        CommManager.receiveMessage(session)
    }


    init
    {
        // Initialize Websocket session here
        coroutine.launch()
        {
            createWebsocketSession()

        }

    }

As for the server side:

routing()
    {
        webSocket("/webSocketSession") {
            ConnectionsHandler.connectWebSocketSession(this)
        }
    }

// ConnectionsHandler object
    fun connectWebSocketSession(session: DefaultWebSocketSession){
        println("adding new user")
        val thisConnection = Connection(session)
        webSocketUserList += thisConnection
        println("${thisConnection.name} is connected!")
    }

After running the server and then the client, this is what I get as output from the server:

Route resolve result:
  SUCCESS @ /webSocketSession/(method:GET)/(header:Connection = Upgrade)/(header:Upgrade = websocket)

2024-03-19 09:35:25.142 [eventLoopGroupProxy-4-1] TRACE i.k.s.p.c.ContentNegotiation - Skipping because body is already converted.

2024-03-19 09:35:25.165 [eventLoopGroupProxy-3-5] TRACE io.ktor.websocket.WebSocket - Starting default WebSocketSession(io.ktor.websocket.DefaultWebSocketSessionImpl@49a741b5) with negotiated extensions: 

2024-03-19 09:35:25.169 [eventLoopGroupProxy-3-5] TRACE io.ktor.server.websocket.WebSockets - Starting websocket session for /webSocketSession

adding new user
user0 is connected!

2024-03-19 09:35:25.218 [eventLoopGroupProxy-3-5] TRACE io.ktor.websocket.WebSocket - Sending Frame CLOSE (fin=true, buffer len = 2) from session io.ktor.websocket.DefaultWebSocketSessionImpl@49a741b5

2024-03-19 09:35:25.222 [eventLoopGroupProxy-3-5] TRACE io.ktor.websocket.WebSocket - Sending Close Sequence for session io.ktor.websocket.DefaultWebSocketSessionImpl@49a741b5 with reason CloseReason(reason=NORMAL, message=) and exception null

2024-03-19 09:35:25.251 [eventLoopGroupProxy-3-3] TRACE io.ktor.websocket.WebSocket - WebSocketSession(StandaloneCoroutine{Active}@2cea43ee) receiving frame Frame CLOSE (fin=true, buffer len = 2)

From my understanding, "sending Frame CLOSE" means that it is closing the websocket session, but I haven't specified any command to do so in the client or the server. Am I missing something in the server side to keep the session running? If you need more information about the problem, please tell me and I'll edit the post.

2

There are 2 answers

0
Aleksei Tirman On

The WebSocket session is closed as soon as the last statement in the WebSocket handler is executed. To keep the session running, you can keep receiving the messages from the other end:

routing {
    webSocket("/websocket") {
        for (message in incoming) {
            // Process incoming messages
        }
    }
}

Also, you can use a while loop to prevent the session from closing:

routing {
    webSocket("/websocket") {
        while (true) {
            delay(500)
            outgoing.send(Frame.Text("Message"))
        }
    }
}
0
Simon Jacobs On

The server handler function connectWebSocketSession() must not return or the socket will be closed: then the server has nothing else to do. You need to set up a loop of some kind to keep the server alive.

In the Ktor examples they use the construct:

for (frame in incoming) {
    // handle each frame
}

incoming is a ReceiveChannel for incoming Frames and, as explained in the Coroutines Guide, you can use a for loop to iterate over the elements sent over a Channel and it will keep processing each element until it the Channel is closed. Therefore this approach keeps the server alive and processing incoming messages.

Another alternative to work with Channels is to use select expressions, which makes it easier to work with more than one Channel (for example you might also want to send outgoing messages from another source). Use these inside of a while loop so you keep selecting from the Channels and keep the server alive, eg:

while (coroutineContext.isActive) {
    selectUnbiased {
        // write select clauses eg `onReceive`
    }
}