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

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.

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))
}
}

How to place the JSON data in a list in a Ktor server?

I have been trying to get Json data from dummyjson and place them into an Array in the ktor server. But it just won't work.
Application.kt:
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
gson {
setDateFormat(DateFormat.LONG)
setPrettyPrinting()
}
}
val client = HttpClient(CIO)
val products: Array<Product> = client.get("https://dummyjson.com/products").body()
configureRouting()
}.start(wait = true)
}
I could create the client but I am not allowed to use the get method because it says Suspend function 'get' should be called only from a coroutine or another suspend function.
Then, in configureRouting.kt I defined the client and used the get method. It works and returns the String to the client.
fun Application.configureRouting() {
val client = HttpClient(CIO)
routing {
get("/get-all-products"){
val product: String = client.get("https://dummyjson.com/products").body()
call.respond(product)
}
}
However, what I want to achieve is without the client's request, the server can automatically load all the JSON file from dummyjson, and place it in a list. But it just keep giving me the same error if I place this get method outside the routing.

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

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"))
}
}

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")
}
}

Redirect to absolute URL in Ktor

I'm learing how to work with ktor. For a special use case i need to redirect to a different domain from my ktor server. However i cant get it to work rigth now.
As an simple example i have an Application.kt
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main(args: Array<String>) {
val server = embeddedServer(Netty, port = 8080) {
routing {
get("/") {
call.respondRedirect("www.google.com")
}}
}
}
server.start(wait = true)
}
What i except is that it redirects me to www.google.com however it redirects me to localhost:8080/www.google.com
Figured it out. You need to set also the protocol. This works
call.respondRedirect("https://google.com/")