question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

No proper way to specify subprotocol in WebSocket client without breaking install(Auth)

See original GitHub issue

Ktor Version

1.1.2

Ktor Engine Used(client or server and name)

CIO (currently the only available WebSocket client provided by Ktor)

JVM Version, Operating System and Relevant Context

Java 11, Linux

Feedback

Currently, as far as I could find, there is apparently no convenient way in the Ktor WebSocket client API to specify the subprotocol(s) to connect with. I’m talking about the value of the “Sec-WebSocket-Protocol” header. Ktor provides a way to specify this when you set up a WebSocket server, through the protocol argument in Route.webSocket(). On the client side however, there is no such argument. I would expect HttpClient.ws and HttpClient.wss to have a similar “protocol” argument as well, but they don’t.

As a workaround you can explicitly pass on an HttpRequestBuilder instance to those functions as the “request” argument, in which you then explicitly set the “Sec-WebSocket-Protocol” header (available as the value HttpHeaders.SecWebSocketProtocol in the Ktor API), but as soon as you do so, this overrides any HTTP authentication that you installed in the HttpClient by invoking install(Auth). So subsequently, you then also have to manually encode the username and password in the case of basic authentication. I expect that you’d have to jump through even more hoops in the case of HTTP Digest authentication.

Ideally, I would like to see an optional protocol (String?) argument added to the HttpClient.ws and HttpClient.wss functions to take the “Sec-WebSocket-Protocol” stuff out of our hands, while still being compatible with the install(Auth) function when configuring the HttpClient.

Below is code that reproduces the issue I’m describing here. If you comment out the “header(HttpHeaders.Authorization, …)” part, you’ll see that the “install(Auth) { basic { … } }” stuff will be ignored when performing a WebSocket upgrade. Be sure to change the “host” and “path” argument to point to some actually running WebSocket server (and if necessary change “client.wss” to “client.ws”) to run this code.

import io.ktor.client.HttpClient
import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.websocket.WebSockets
import io.ktor.client.features.websocket.wss
import io.ktor.client.request.header
import io.ktor.http.HttpHeaders
import io.ktor.http.HttpMethod
import io.ktor.http.cio.websocket.Frame
import io.ktor.http.cio.websocket.readText
import kotlinx.coroutines.channels.filterNotNull
import kotlinx.coroutines.channels.map
import kotlinx.coroutines.runBlocking
import java.util.Base64

val httpBasicAuthName = "username"
val httpBasicPassword = "password"

private val client = HttpClient().config {

    // NOTE: install(Auth) doesn't actually work when passing on a custom request argument to client.wss().
    install(Auth) {
        basic {
            username = httpBasicAuthName
            password = httpBasicPassword
        }

        install(WebSockets)
    }

}

runBlocking {
    client.wss(
        method = HttpMethod.Get,
        host = "somedomain.com",
        path = "/some-endpoint",
        request = {

            // See https://www.iana.org/assignments/websocket/websocket.xml#subprotocol-name
            header(HttpHeaders.SecWebSocketProtocol, "ocpp2.0,ocpp1.6")

            /* NOTE: Using install(Auth) in HttpClient().config doesn't work here for HTTP Basic Authentication,
             * since we're passing in this explicit request lambda argument, because we need to specify the
             * "Sec-WebSocket-Version" header.
             */
            header(
                HttpHeaders.Authorization,
                "Basic ${Base64.getEncoder().encodeToString(
                    "$httpBasicAuthName:$httpBasicPassword".toByteArray(
                        Charsets.UTF_8
                    )
                )}"
            )

        }) {
        // this: DefaultClientWebSocketSession
        send(Frame.Text("Hello World"))

        for (message in incoming.map { it as? Frame.Text }.filterNotNull()) {
            println(message.readText())
        }
    }
}

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
e5lcommented, Feb 12, 2019

Hi @volkert-fastned, thanks for the report. I’m investigating the problem, the fix is planned for 1.2.0

0reactions
Stexxecommented, Jul 15, 2021

I cannot reproduce this problem with Ktor 1.6.1 when having sendWithoutRequest { true } in a basic authentication config.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Writing WebSocket servers - Web APIs | MDN
This is not a tutorial in any specific language, but serves as a guide to facilitate writing your own server. This article assumes...
Read more >
HTTP headers in Websockets client API - Stack Overflow
I think a good solution is to allow the WebSocket to connect without authorization, but then block and wait on the server to...
Read more >
Sec-WebSocket-Protocol (Subprotocol) header support
During the handshake, the client would ask the server what kind of subprotocols are supported by sending Sec-WebSocket-Protocol header. GET / ...
Read more >
26. WebSocket Support - Spring
The use of a sub-protocol is not required, but even if not used, applications will still need to choose a message format that...
Read more >
Chapter 4. STOMP over WebSocket - O'Reilly
As we'll discuss in Chapter 8, you are not required to use a registered subprotocol with WebSocket. The subprotocol does need to be...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found