danneu / kog

🌶 A simple Kotlin web framework inspired by Clojure's Ring.
43 stars 4 forks source link

Websocket keepalive #21

Closed ThraaxSession closed 7 years ago

ThraaxSession commented 7 years ago

Hi :)

it's a nice framework. But I've got a little issue with the connection timeout on websocket.

[70294c19-6a40-4f0e-93a2-84ef22679498] onError: Timeout on Read
[70294c19-6a40-4f0e-93a2-84ef22679498] onClose: 1001 Idle Timeout

Do you know how to do a keepalive on websocket? Didn't find anything on Jetty side. Or how to do a ping/pong at least. (With JS side)

Thanks and best regards Gino

danneu commented 7 years ago

Thanks.

I was thinking of removing the websocket stuff to minimize the feature set for a 1.0 release, so I've neglected that bit of kog. My implementation is kinda awkward so that websocket routes could be mounted behind other kog middleware, which is a feature I want to keep after finding a better impl.

I'll look into it because I'm curious as well now.

danneu commented 7 years ago

@GinoHereIam Oh, you can set kog's idle timeout when constructing the server.

import com.danneu.kog.Server
import java.time.Duration

fun main(args: Array<String>) {
    Server(handler, idleTimeout = Duration.ofMinutes(5)).listen(3000)
}

kog passes it through to Jetty. Jetty (and thus kog) defaults to 30 seconds.

Edit: Let me test that this actually works.

danneu commented 7 years ago

Alright, it works, but after some quick tests, Jetty seems to only support a max idle timeout of 5 minutes. Passing in a duration over 5 minutes seems to result in an idle timeout of 5 minutes, no more.

Also, reverse proxies like nginx or Heroku's load balancer or Cloudflare often have their own idle timeouts which may be shorter than kog's. IIRC Heroku will timeout idle connects after 30 seconds (which is Jetty's default). So even if kog has an idle timeout of 5 minutes, Heroku would terminate connections long before that.

I think this is one of the reasons why websocket libraries like https://socket.io/ implement their own ping/pong -- to avoid idle timeout.

I'll update the websockets section of the readme.

Thanks for being a guinea pig and bringing this issue up! 😄

So I'm pretty sure you have to make your clients ping the server to keep connections alive. Heroku confirms: https://devcenter.heroku.com/articles/websockets#timeouts

danneu commented 7 years ago

https://github.com/danneu/kog#idle-timeout

ThraaxSession commented 7 years ago

Hi @danneu :) sorry for late reply, I am busy now.

I solved this issuse with this little piece of code :D

session.idleTimeout = 0

Here a bit more code to clarify the location in code:

        // This endpoint will open a websocket connection that echos back any text the client sends it.
        get("/ws/test", fun(): Handler = {
            // Current limitation: The first argument to Response.websocket() must be a static url path.
            // It does *not* accept route patterns like "/ws/<num>". (#willfix)
            Response.websocket("/ws/test", fun(_: Request, session: Session): WebSocketHandler {
                // Upon each websocket connection at this endpoint, generate a random id for it
                val id = java.util.UUID.randomUUID()
                println("Started websocket connection ${DateTime.now().toLocalDateTime()}")

                // hopefully this is unlimited
                // the connection should never be closed!
                session.idleTimeout = 0

                return object : WebSocketHandler {
                    override fun onOpen() {
                        println("[$id] a client connected")
                    }
                 ...

I do this at the begin of the websocket connection. After setting the idleTimeout to 0, the connection stays alive. Without any ping/pong implementation. (For luck) :D

I hope this helps you. And please don't remove it. :O I like it.

Best regards Gino