Quarkus Websocket Client Handshake 308 - Upgrade config?

68 Views Asked by At

I'm using Quarkus coming back at it, after some years and trying to configure for the first time a WS client, but I'm struggling to find a solution.

Related Dependencies in BoM version 3.7.1

  • io.quarkus:quarkus-resteasy
  • io.quarkus:quarkus-rest-client
  • io.quarkus:quarkus-websockets-client. <===
  • io.quarkus:quarkus-resteasy-jsonb

Configuring my @ClientEndpoint and starting the Websocket client with:

ContainerProvider.getWebSocketContainer().connectToServer(RomanWebsocketClient::class.java, URI)

I have 2 outcomes.

  1. Working URI: wss://socketsbay.com/wss/v2/1/demo/ (this is just to test the config)
  2. NOT working: https://myserverdomain/await/some-url-encoded-token (this is what I want)

The second gives me a 308 Permanent Redirect I have to say that using the same URIs in a ktor based BE works, so I think I'm missing some extra config, like upgrading or so.

What I've tried:

  • Declaring a ClientEndpointConfig.Configurator to inject the headers ("Connection", "Upgrade", etc.) but didn't work.
  • Trying to play with Quarkus application.properties config, like followRedirects, also didn't work, and I think this could be unrelated.

What I would like to know:

  • Do you have any ideas how can I properly define the upgrade (in case this is the issue) ?
  • Is it that undertow/netty does not support WS with https protocol ?
  • Any other ideas on how to make it work would be great.
1

There are 1 best solutions below

0
On

Ok, in the end I didn't find a solution, but rather a workaround in case anyone is interested, what I did was just using a java.net.http.WebSocket since from Java 11 has a better API.

I implemented the WebSocket.Listener interface and defined the Client in my case as a Bean register eagerly on startup.

@ApplicationScoped
@Startup
class WebsocketClient(
    @ConfigProperty(name = "quarkus.rest-client.api.url")
    private val baseUrl: String,
    @ConfigProperty(name = "quarkus.rest-client.api.key")
    private val apiKey: String
) : WebSocket.Listener {
    private val logger = LoggerFactory.getLogger(this::class.java)
    private val executorService: ExecutorService = Executors.newFixedThreadPool(5)

    private val httpClient: HttpClient = HttpClient.newBuilder()
        .followRedirects(HttpClient.Redirect.ALWAYS)
        .executor(executorService)
        .build()

    private val wsUri = run {
        URI(baseUrl) // in case is an https schema, replace it to wss, since internally gets converted to https in 
            .resolve("/await/" + URLEncoder.encode(apiKey, "utf-8"))
    }

    @PostConstruct
    fun init() {
        httpClient.newWebSocketBuilder()
            .buildAsync(wsUri, this)
            .join()
    }

    override fun onOpen(webSocket: WebSocket?) {
        super.onOpen(webSocket)
    }

    override fun onText(webSocket: WebSocket?, data: CharSequence?, last: Boolean): CompletionStage<*> {
        super.onText(webSocket, data, last)
        logger.info("Message received raw: $data")
        // process data...
        return CompletableFuture<Void>()
    }

    override fun onClose(webSocket: WebSocket?, statusCode: Int, reason: String?): CompletionStage<*> {
        super.onClose(webSocket, statusCode, reason)
        logger.info(">> Websocket close: ${reason ?: "no reason"}, reopening...")
        init()
        return CompletableFuture<Void>().also { it.complete(null) }
    }

    override fun onError(webSocket: WebSocket?, error: Throwable?) {
        super.onError(webSocket, error)
        logger.info(">> Websocket error: ${error?.message}, reopening...")
        init()
    }
}

I hope at some point to being able to use the Quarkus Websocket client, but for now, this solves the schema issue I had and I'm able to move on.