How to trigger a websockets message from outside the routing() block in Ktor? - kotlin

If I have an Kotlin application that wants to trigger outgoing Websocket messages in Ktor, I usually do this from within the relevant routing block. If I have a process outside of the routing block that wants to send a Websocket message, how can I trigger that?

You need to store the session provided by the web socket connection and then you can send messages in that session:
var session: WebSocketSession? = null
try {
client.ws {
session = this
}
} finally {
// clear the session both when the socket is closed normally
// and when an error occurs, because it is no longer valid
session = null
}
// other coroutine
session?.send(/*...*/)

You can use coroutines' channels to send a Websocket session and receive it in a different place:
import io.ktor.application.*
import io.ktor.http.cio.websocket.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.websocket.*
import kotlinx.coroutines.channels.Channel
suspend fun main() {
val sessions = Channel<WebSocketSession>()
val server = embeddedServer(Netty, 5555, host = "0.0.0.0") {
install(WebSockets) {}
routing {
webSocket("/socket") {
sessions.send(this)
}
}
}
server.start()
for (session in sessions) {
session.outgoing.send(Frame.Text("From outside of the routing"))
}
}

Related

Ktor modify request on retry not working as expected

I have a custom retry policy on receiving 5XX errors from the server. The idea is to retry until I get a non-5XX error with an exponential delay between each retry request also I would like to update the request body on every retry.
Here is my code
import io.ktor.client.*
import io.ktor.client.engine.java.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.request.*
import io.ktor.server.routing.*
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.seconds
suspend fun main() {
val serverJob = CoroutineScope(Dispatchers.Default).launch { startServer() }
val client = HttpClient(Java) {
install(HttpTimeout) {
connectTimeoutMillis = 5.seconds.inWholeMilliseconds
}
install(HttpRequestRetry)
}
client.post {
url("http://127.0.0.1:8080/")
setBody("Hello")
retry {
retryOnServerErrors(maxRetries = Int.MAX_VALUE)
exponentialDelay(maxDelayMs = 128.seconds.inWholeMilliseconds)
modifyRequest { it.setBody("With Different body ...") } // It's not working! if I comment this out then my retry logic works as expected
}
}
client.close()
serverJob.cancelAndJoin()
}
suspend fun startServer() {
embeddedServer(Netty, port = 8080) {
routing {
post("/") {
val text = call.receiveText()
println("Retrying exponentially... $text")
call.response.status(HttpStatusCode(500, "internal server error"))
}
}
}.start(wait = true)
}
As you can see, if I comment out modifyRequest { it.setBody("With Different body ...") } line from retry logic then everything works fine. If I include that line it only tries once and stuck there, what I'm doing wrong here? how to change the request body for every retry?
The problem is that rendering (transformation to an OutgoingContent) of a request body happens during the execution of the HttpRequestPipeline, which takes place only once after making an initial request. The HTTP request retrying happens after in the HttpSendPipeline.
Since you pass a String as a request body it needs to be transformed before the actual sending. To solve this problem, you can manually wrap your String into the TextContent instance and pass it to the setBody method:
retry {
retryOnServerErrors(maxRetries = Int.MAX_VALUE)
exponentialDelay(maxDelayMs = 128.seconds.inWholeMilliseconds)
modifyRequest {
it.setBody(TextContent("With Different body ...", ContentType.Text.Plain))
}
}

Is there any way to make a fake call from Ktor to itself, to make request pass through all pipeline?

I have an ktor web server that successfully responds on http requests. Now there is a need to read data from kafka's topic and process it.
Is there any way send the data I've read to ktor, like this data came from outside, to make it pass through all pipeline, like ContentNegotiation and other features?
Application class has method execute(), which takes ApplicationCall, but I've found zero examples - how can I fill my implementation of this class properly. Especially route - do I need the real one? Would be nice if this route would be private and would be unavailable from the outside.
You can use the withTestApplication function to test your application's modules without making an actual network connection. Here is an example:
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class SimpleTest {
#Test
fun test() = withTestApplication {
application.module()
// more modules to test here
handleRequest(HttpMethod.Post, "/post") {
setBody("kafka data")
}.response.let { response ->
assertEquals("I get kafka data", response.content)
}
}
}
fun Application.module() {
routing {
post("/post") {
call.respondText { "I get ${call.receiveText()}" }
}
}
}
I think that #AlekseiTirman answer is great and most probably you should go for it
But I have to mention that it's easy to do it even in "real life" run. Your local machine ip is 0.0.0.0, you can get port from the env variable, so you just can create a simple HttpClient and send a request:
CoroutineScope(Dispatchers.IO).launch {
delay(1000)
val client = HttpClient {
defaultRequest {
// actually this is already a default value so no need to setting it
host = "0.0.0.0"
port = environment.config.property("ktor.deployment.port").getString().toInt()
}
}
val result = client.get<String>("good")
println("local response $result")
}
routing {
get("good") {
call.respond("hello world")
}
}

Generate a random port numbers everytime a new server instance is created

I am trying to create a Ktor Server which currently runs on localhost. When i run only one instance of the server on terminal it is working fine, but when i open a new tab on terminal and run another instance of the same server i get this Warning/Error:
Exception in thread "main" java.net.BindException: Address already in use
at sun.nio.ch.Net.bind0(Native Method)
at sun.nio.ch.Net.bind(Net.java:461)
at sun.nio.ch.Net.bind(Net.java:453)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:222)
at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:134)
at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:562)
at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1334)
at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:506)
at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:491)
at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:973)
at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:260)
at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:356)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.ktor.server.netty.EventLoopGroupProxy$Companion$create$factory$1$1.run(NettyApplicationEngine.kt:241)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
This happens because the server is using the port which is already in use.
What i am trying to achieve, is that the server should generate a unique port number for the second instance automatically. I am not sure how to do this on Ktor.
This is the current configuration of my server:
fun main() {
val env = applicationEngineEnvironment {
module {
main()
}
connector {
host = "localhost"
port = 8080
}
}
embeddedServer(Netty, env).start(true)
}
You can use ServerSocket to automatically allocate a port number and close that socket. Here is an example:
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import java.net.ServerSocket
fun main(args: Array<String>) {
embeddedServer(Netty, port = getFreePort()) {
routing {
get("/") {
call.respondText { "Hello" }
}
}
}.start()
}
fun getFreePort(): Int {
val socket = ServerSocket(0)
val port = socket.localPort
socket.close()
return port
}
Also, there is an up for grabs feature request to allocate a random port number out of the box.

Timer: Not start my method periodically every N seconds

In my Kotlin project
I want start two independent timer.
First must run every 30 seconds.
Second must run every 20 seconds (start after 5 seconds).
I try this:
import kotlin.concurrent.schedule
import kotlin.concurrent.timerTask
Timer().scheduleAtFixedRate(timerTask { login() }, 1000, 30 * 1000)
Timer().schedule(5_000) { Timer().scheduleAtFixedRate(timerTask { updateState() }, 1000, 20 * 1000) }
First timer success run (login) periodically ever 20 seconds. Nice.
But seconds timer start only ONCE. Not run (updateState) periodically every 30 seconds.
Method login do sync http request. Method updateState also do sync http request.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
fun login() {
val loginRequestURL =
"${Config.instance.loginApiUrl}/importAPILogist.php"
val requestToc= Request.Builder()
.url(loginRequestURL)
.get()
.build()
val httpClient = OkHttpClient()
val loginResponse: Response = httpClient.newCall(requestToc).execute() // sync request
}
and another method:
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
fun updateOrdersState() {
logger.info("updateOrdersState:")
val someRequestURL = "${Config.instance.someApiUrl}/exportAPI"
val requestOrderStateTocan = Request.Builder()
.url(someRequestURL)
.get()
.build()
val httpClient = OkHttpClient()
val tocanOrderStateResponse: Response = httpClient.newCall(requestOrderStateTocan).execute() // sync request
Method updateState can throw exception(java.net.SocketTimeoutException)
Maybe I need to catch exception when http request is not success? E.g.
Exception in thread "Timer-2" java.net.SocketTimeoutException: timeout

Subscribe to Flux Websocket inbound connection with projectreactor blocking?

In the below code, IntelliJ warns the subscribe should not be called in a blocking scope. Unfortunately subscribe seems to be the most intuitive way of associating a consumer with the inbound message stream, is there a better way?
Code snippet in Kotlin, based on the example Java code in the projectreactor documentation.
I want to subscribe to the inbound messages with a consumer that is injected, or expose the inbound messages flux in a way that other consumers can access and subscribe to it and I don't want this to be blocking.
import io.netty.buffer.Unpooled
import io.netty.util.CharsetUtil
import reactor.core.publisher.Flux
import reactor.netty.http.client.HttpClient
fun main() {
HttpClient.create()
.websocket()
.uri("wss://echo.websocket.org")
.handle { inbound, outbound ->
inbound.receive()
.asString()
.take(1)
.subscribe(
{ println(it) },
{ println("error $it") },
{ println("completed") }
)
val msgBytes = "hello".toByteArray(CharsetUtil.ISO_8859_1)
outbound.send(Flux.just(Unpooled.wrappedBuffer(msgBytes))).neverComplete()
}
.blockLast()
}
We found an alternative to subscribe that was non-blocking. then and zip. Example in Kotlin.
import io.netty.buffer.Unpooled
import io.netty.util.CharsetUtil.UTF_8
import reactor.core.publisher.Flux
import reactor.netty.http.client.HttpClient
fun main() {
val outgoingMessagesFlux = Flux.just(Unpooled.wrappedBuffer("hello".toByteArray(UTF_8)))
HttpClient.create()
.websocket()
.uri("wss://echo.websocket.org")
.handle { inbound, outbound ->
val thenInbound = inbound.receive()
.asString()
.doOnNext { println(it) }
.then()
val thenOutbound = outbound.send(outgoingMessagesFlux).neverComplete()
Flux.zip(thenInbound, thenOutbound).then()
}.blockLast()
}
This was based on the Spring WebFlux Netty websocket client source code implementation and the current spring-framework documentation.