Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error during WebSocket handshake in Chrome #975

Closed
lucaro opened this issue May 20, 2020 · 9 comments
Closed

Error during WebSocket handshake in Chrome #975

lucaro opened this issue May 20, 2020 · 9 comments
Labels

Comments

@lucaro
Copy link
Contributor

lucaro commented May 20, 2020

Disclaimer: this might be an issue with how javalin uses jetty or it might be an unrelated jetty issue, but I couldn't come up with a way to tell the difference. The problem is that I'm unable to establish a wss connection to a javalin endpoint using Chrome. Wss works as expected in Firefox and http, https and ws connections work without any issue in both browsers. Chrome does however always return the following error message: Error during WebSocket handshake: Invalid status line

The error is independent of the used keystore, I tried it with a self-signed cert via localhost and via a cert validated through Let's Encrypt and a proper domain, both with the exact same behavior. Below is a minimal working example to reproduce the problem.

import io.javalin.Javalin
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory
import org.eclipse.jetty.http2.HTTP2Cipher
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory
import org.eclipse.jetty.server.*
import org.eclipse.jetty.util.ssl.SslContextFactory

object WSTest {

    @JvmStatic
    fun main(args: Array<String>) {

        Javalin.create {
            it.server { setupHttpServer() }
            it.enableCorsForAllOrigins()
        }.apply {
            get("/test") {ctx -> ctx.result("Test!") }
            ws("/ws") { ws ->
                ws.onMessage { ctx ->
                    ctx.send(ctx.message())
                }
            }
        }.start(80)

    }

    private fun setupHttpServer(): Server {

        val httpConfig = HttpConfiguration().apply {
            sendServerVersion = false
            sendXPoweredBy = false
            secureScheme = "https"
            securePort = 443
        }

        val httpsConfig = HttpConfiguration(httpConfig).apply {
            addCustomizer(SecureRequestCustomizer())
        }

        val alpn = ALPNServerConnectionFactory().apply {
            defaultProtocol = "h2"
        }

        val sslContextFactory = SslContextFactory.Server().apply {
            keyStorePath = "keystore.jks"
            setKeyStorePassword("password")
            cipherComparator = HTTP2Cipher.COMPARATOR
            provider = "Conscrypt"
        }

        val ssl = SslConnectionFactory(sslContextFactory, alpn.protocol)

        val http2 = HTTP2ServerConnectionFactory(httpsConfig)

        val fallback = HttpConnectionFactory(httpsConfig)


        return Server().apply {
            
            addConnector(ServerConnector(server, HttpConnectionFactory(httpConfig), HTTP2ServerConnectionFactory(httpConfig)).apply {
                port = 80
            })
            
            addConnector(ServerConnector(server, ssl, alpn, http2, fallback).apply {
                port = 443
            })
        }
    }
}

@tipsy tipsy added the QUESTION label May 20, 2020
@tipsy
Copy link
Member

tipsy commented May 20, 2020

Does it work fine without SSL? How about without HTTP2?

Can you try to recreate the error without Javalin by creating a minimal working example with raw Jetty? https://jansipke.nl/websocket-tutorial-with-java-server-jetty-and-javascript-client/

@lucaro
Copy link
Contributor Author

lucaro commented May 21, 2020

It works fine without SSL. It also does not work without HTTP2 and with SSL, the other way around is not testable since Chrome does not do HTTP2 over unencrypted connections. The error does haowever change to Connection closed before receiving a handshake response.

The problem does however to be on the jetty and not on the javalin side. I built a minimal example based on the tutorial you linked which does not use javalin, and the problem still persists.

import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory
import org.eclipse.jetty.http2.HTTP2Cipher
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory
import org.eclipse.jetty.server.*
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.eclipse.jetty.websocket.api.Session
import org.eclipse.jetty.websocket.api.annotations.*
import org.eclipse.jetty.websocket.server.WebSocketHandler
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory
import java.io.IOException


object WSTestJetty {

    @JvmStatic
    fun main(args: Array<String>) {

        val server = setupHttpServer()
        val wsHandler: WebSocketHandler = object : WebSocketHandler() {
            override fun configure(factory: WebSocketServletFactory) {
                factory.register(MyWebSocketHandler::class.java)
            }
        }
        server.handler = wsHandler
        server.start()
        server.join()

    }

    private fun setupHttpServer(): Server {

        val httpConfig = HttpConfiguration().apply {
            sendServerVersion = false
            sendXPoweredBy = false
            secureScheme = "https"
            securePort = 443
        }

        val httpsConfig = HttpConfiguration(httpConfig).apply {
            addCustomizer(SecureRequestCustomizer())
        }

        val alpn = ALPNServerConnectionFactory().apply {
            defaultProtocol = "http" //"h2"
        }

        val sslContextFactory = SslContextFactory.Server().apply {
            keyStorePath = "keystore.jks"
            setKeyStorePassword("password")
            //cipherComparator = HTTP2Cipher.COMPARATOR
            provider = "Conscrypt"
        }

        val ssl = SslConnectionFactory(sslContextFactory, alpn.protocol)

        val http2 = HTTP2ServerConnectionFactory(httpsConfig)

        val fallback = HttpConnectionFactory(httpsConfig)


        return Server().apply {

            addConnector(ServerConnector(server, HttpConnectionFactory(httpConfig), HTTP2ServerConnectionFactory(httpConfig)).apply {
                port = 80
            })

            addConnector(ServerConnector(server, ssl, alpn, http2, fallback).apply {
                port = 443
            })
        }
    }
}

@WebSocket
class MyWebSocketHandler {
    @OnWebSocketClose
    fun onClose(statusCode: Int, reason: String) {
        println("Close: statusCode=$statusCode, reason=$reason")
    }

    @OnWebSocketError
    fun onError(t: Throwable) {
        println("Error: " + t.message)
    }

    @OnWebSocketConnect
    fun onConnect(session: Session) {
        println("Connect: ${session.remoteAddress.address}")
        try {
            session.getRemote().sendString("Hello Webbrowser")
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    @OnWebSocketMessage
    fun onMessage(message: String) {
        println("Message: $message")
    }
}

@tipsy
Copy link
Member

tipsy commented May 21, 2020

Nice work on the debugging! You should open and issue in the Jetty project, they're usually very responsive.

@lucaro
Copy link
Contributor Author

lucaro commented May 21, 2020

Done, see jetty/jetty.project#4900 for future reference. This issue can probably be closed. Depending on the solution, it might however be good to update the Javalin HTTPS guide accordingly.

@tipsy
Copy link
Member

tipsy commented May 21, 2020

Great! Please reopen the issue if we need to take any action in Javalin.

@tipsy tipsy closed this as completed May 21, 2020
@lucaro
Copy link
Contributor Author

lucaro commented May 21, 2020

So, apparently, Websocket support for HTTP/2 will come with Jetty 10.0 and does not work with Jetty 9.4.25 as currently used by Javalin. Maybe a comment along these lines could be made to https://github.com/tipsy/javalin-http2-example ?

@joakime
Copy link

joakime commented May 21, 2020

You can try WebSocket over HTTP/2 now with Jetty 10.0.0.alpha2

@tipsy
Copy link
Member

tipsy commented May 21, 2020

Maybe a comment along these lines could be made to https://github.com/tipsy/javalin-http2-example ?

That would be great @lucaro, please create a PR :)

@lucaro
Copy link
Contributor Author

lucaro commented May 21, 2020

@tipsy I added a note, see tipsy/javalin-http2-example#1
@joakime Thanks, I'll look into it when I find the time. (The project I'm using it for needs to be deployed in a first version early next week, so it will be after that in any case.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants