Ktor CIO wss socket closes immediately - websocket

When using ktor CIO ws it works as expected, but when using wss it gets closed immediately. Any help is greatly appreciated. been stuck for a day now.
This is the stack trace I'm getting for wss
kotlinx.coroutines.experimental.channels.ClosedReceiveChannelException: Channel was closed
at kotlinx.coroutines.experimental.channels.Closed.getReceiveException(AbstractChannel.kt:1067)
at kotlinx.coroutines.experimental.channels.AbstractChannel$ReceiveElement.resumeReceiveClosed(AbstractChannel.kt:907)
at kotlinx.coroutines.experimental.channels.AbstractSendChannel.helpClose(AbstractChannel.kt:317)
at kotlinx.coroutines.experimental.channels.AbstractSendChannel.close(AbstractChannel.kt:254)
at kotlinx.coroutines.experimental.channels.ChannelCoroutine.close(ChannelCoroutine.kt)
at kotlinx.coroutines.experimental.channels.SendChannel$DefaultImpls.close$default(Channel.kt:84)
at io.ktor.network.tls.TLSClientHandshake$input$1.doResume(TLSClientHandshake.kt:96)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resumeWithException(CoroutineImpl.kt:48)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resumeWithException(CoroutineImpl.kt:47)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:41)
at kotlinx.coroutines.experimental.DispatchedTask$DefaultImpls.run(Dispatched.kt:149)
at kotlinx.coroutines.experimental.io.internal.MutableDelegateContinuation.run(MutableDelegateContinuation.kt:14)
at io.ktor.network.util.IOCoroutineDispatcher$IODispatchedTask.run(IOCoroutineDispatcher.kt)
at io.ktor.network.util.IOCoroutineDispatcher$IOThread$run$1.doResume(IOCoroutineDispatcher.kt:73)
at kotlin.coroutines.experimental.jvm.internal.CoroutineImpl.resume(CoroutineImpl.kt:42)
at kotlinx.coroutines.experimental.DispatchedTask$DefaultImpls.run(Dispatched.kt:149)
at kotlinx.coroutines.experimental.DispatchedContinuation.run(Dispatched.kt:13)
at kotlinx.coroutines.experimental.EventLoopBase.processNextEvent(EventLoop.kt:140)
at kotlinx.coroutines.experimental.BlockingCoroutine.joinBlocking(Builders.kt:70)
at kotlinx.coroutines.experimental.BuildersKt__BuildersKt.runBlocking(Builders.kt:46)
at kotlinx.coroutines.experimental.BuildersKt.runBlocking(Unknown Source)
at io.ktor.network.util.IOCoroutineDispatcher$IOThread.run(IOCoroutineDispatcher.kt:68)
here's the code:
httpClient.ws(host = "echo.websocket.org") {
send(Frame.Text("Hello World"))
for (message in incoming.map { it as? Frame.Text }.filterNotNull()) {
println(message.readText())
}
}
httpClient.wss(host = "echo.websocket.org") {
send(Frame.Text("Hello World"))
for (message in incoming.map { it as? Frame.Text }.filterNotNull()) {
println(message.readText())
}
}

I there was an issue with secure WebSockets and it is fixed in ktor 1.3.2.
I also want to mention that echo.websocket.org doesn't close connection, so the for loop will be suspended forever(expecting the next pong message).
To avoid that you can rewrite this
for (message in incoming.map { it as? Frame.Text }.filterNotNull()) {
println(message.readText())
}
as
val pong = incoming.receive() as Frame.Text
println(pong.readText())
It also should be ok to call close() explicitly after sending the first message, but it's unsupported for now. I filed the separate issue for that: https://github.com/ktorio/ktor/issues/1946

Related

Vertx amqp client doesnt reconnect on broker down

I am trying to write a program to pull messages from a message broker via Vert.x AMQP client. I want to make the program try to reconnect on broker down. Currently if I turn off the broker container, the program doesn't react. Below is my code.. What do I miss ?
public class BrokerConnector {
public void consumeEventsQueue() {
AmqpClientOptions options = new AmqpClientOptions()
.setHost("localhost")
.setPort(5672)
.setUsername("")
.setPassword("");
AmqpClient amqpClient = AmqpClient.create(options);
amqpClient.connect(con -> {
if (con.failed()) {
System.out.println("Unable to connect to the broker");
} else {
System.out.println("Connection succeeded");
}
});
amqpClient.createReceiver("MY_QUEUE",
done -> {
if (done.failed()) {
System.out.println("Unable to create receiver");
} else {
AmqpReceiver receiver = done.result();
receiver.handler(msg -> {
System.out.println("Received " + msg.bodyAsString());
});
}
}
);
}
}
To my knowledge (and from peeking at the source) the vertx AMQP client doesn't have automatic client reconnect so it seems quite normal that on loss of connection you application is failing. The client exposes an exception handler that you can hook and recreate your client resources from when the connection drops. There are some clients for AMQP that do have automatic reconnect built in like Qpid JMS or the Qpid protonj2 client.

UnknownHostException when trying to connect using websocket

I have a use case where I need to send 2 requests to the server. The output of first request is used in second request so the calls have to be synchronous. I am using ktor (OkHttp)client websocket for this. I am failing at first attempt to even connect to the server with this error
Exception in thread "main" java.net.UnknownHostException: https: nodename nor servname provided, or not known
I suspect I haven't split my url properly and thats why its not able to connect to host.
Couple of qns
Is there any benefit to using websocket instead of using 2 separate Http requests?
Is there a way I can just pass URL to the websocket request?
Best and easiest way to get response and send another request?
I have been able to find very limited documentation on ktor client websocket.
const val HOST = "https://sample.com"
const val PATH1 = "/path/to/config?val1=<val1>&val2=<val2>"
const val PATH2 = "/path/to/config?val=<response_from_first_req>"
fun useSocket() {
val client = HttpClient() {
install(WebSockets)
}
runBlocking {
client.webSocket(method = HttpMethod.Get, host = HOST, path = PATH1) {
val othersMessage = incoming.receive() as? Frame.Text
println(othersMessage?.readText())
println("Testing")
}
}
client.close()
}
Thanks in advance.

SSE connection keeps failing every 5 minutes

I'm exposing a simple SSE endpoint using the SseEmitter Spring API, persisting all the emitters in a ConcurrentHashMap. The timeout for each emitter is set to 24 hours. Every 10 seconds I'm sending a message to all the clients. Clients are subscribed with native EventSource implementation, listening for events of particular name.
Unfortunately, I've noticed that every 5 minutes the connection is lost and reestablished again - even though timeout of emitter was explicitly set to 24 hours. Client's part does log it as an error, however on server side there's nothing. The issue occurs on both Tomcat and Jetty. I'd like to keep the session open without any interruptions, so resetting the connection every 5 minutes is unacceptable. Any ideas why this could be happening?
#RestController
#RequestMapping("api/v1/sse")
class SseController {
private val emitters = ConcurrentHashMap<String, SseEmitter>()
#GetMapping
fun initConnection(#RequestParam token: String): SseEmitter {
logger.info { "Init connection from $token" }
val emitter = SseEmitter(24 * 60 * 60 * 1000)
emitter.onCompletion {
logger.info { "Completion" }
emitters.remove(token)
}
emitter.onTimeout { logger.info { "Timeout " } }
emitter.onError { logger.error(it) { "Error" } }
emitters[token] = emitter
return emitter
}
#Scheduled(fixedRate = 10000)
fun send() {
emitters.forEach { (k, v) ->
logger.info { "Sending message to $k" }
v.send(
SseEmitter.event()
.id(UUID.randomUUID().toString())
.name("randomEvent")
.data("some data")
)
}
}
}
const eventSource = new EventSource(url);
eventSource.addEventListener('randomEvent', (e) =>
console.log(e.data)
);
eventSource.onerror = (e) => console.log(e);
Alright, seems it was an issue with Stackblitz's service worker. I've just implemented the same client-side solution in Chrome's plain console and the disconnecting is no longer happening.

Vapor 3 Websocket with Sessions

In Vapor 2 it was possible to access a session when connecting a new websocket.
For example:
setupRoutes(){
socket("ws") { request, websocket in
let session = try request.assertSession()
guard let userId = session.data["user_id"]?.string else {
..
}
}
In Vapor 3 configure.swift:
let wss = NIOWebSocketServer.default()
wss.get("ws") { websocket, request in
--get session information--
websocket.onText { websocket, text in
websocket.send(text)
}
}
services.register(wss, as: WebSocketServer.self)
With Vapor 3 the SessionMiddleware will not be invoked before passing the HTTP upgrade request to the WebsocketServer.
So how can I access session information?
So, I'm super aware that this thread is old and the OP probably found an answer or gave up months ago. Just in case anyone comes across this still looking, can't you use websocket.session to access the session?
This would make the Vapor 3 code
let wss = NIOWebSocketServer.default()
wss.get("ws") { websocket, request in
guard let userID = (try? websocket.session).data["user_id"]?.string else {
...
}
websocket.onText { websocket, text in
websocket.send(text)
}
}
services.register(wss, as: WebSocketServer.self)

Akka HTTP WebSocket client equivalent of this node.js

I have some user documentation that expresses how to use a websocket with this node snippet:
var socket = io(“HOST:PORT”);
socket.on('request-server', function() {
socket.emit('server-type', 'red')
});
What would the equivalent client code be in Akka HTTP?
I have derived the following from the example in the Akka documentation. It isn't quite what I'd like to write, because
I think I need to connect and wait for the request-server event before sending any events & I don't know how to do that
I don't know how to format the TextMessages in the Source to be equivalent to `socket.emit('server-type', 'red').
It only prints "closed"
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
val incoming: Sink[Message, Future[Done]] = Sink.foreach[Message] {
case message: TextMessage.Strict => println(message.text)
case z => println(z)
}
val outgoing = Source(List(TextMessage("'server-type': 'red'")))
val webSocketFlow = Http().webSocketClientFlow(
WebSocketRequest("ws://localhost:3000/socket.io"))
val (upgradeResponse, closed) =
outgoing
.viaMat(webSocketFlow)(Keep.right)
.toMat(incoming)(Keep.both)
.run()
val connected = upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}
connected.onComplete(println)
closed.foreach(_ => println("closed"))
What is the Akka client equivalent to the given socket.io code?
Your connection is getting closed immediately after sending message "outgoing".
Check out Half-Closed Websockets here http://doc.akka.io/docs/akka-http/10.0.0/scala/http/client-side/websocket-support.html#half-closed-websockets

Resources