Adam Stulpa
07/27/2025, 11:03 AMfun main() {
val client = HttpClient(CIO)
embeddedServer(Netty, port = 8080) {
routing {
route("/{...}") {
handle {
forwardRequest(call, client, "localhost", 9090)
}
}
}
}.start(wait = true)
}
suspend fun forwardRequest(call: ApplicationCall, client: HttpClient, targetHost: String, targetPort: Int) {
val response = client.request {
url {
protocol = URLProtocol.HTTP
host = targetHost
port = targetPort
encodedPath = call.request.uri
encodedParameters.appendAll(call.request.rawQueryParameters)
}
method = call.request.httpMethod
// handle headers
if (call.request.httpMethod in listOf(<http://HttpMethod.Post|HttpMethod.Post>, HttpMethod.Put, HttpMethod.Patch)) {
val byteReadChannel = call.receiveChannel()
setBody(object : OutgoingContent.ReadChannelContent() {
override val contentType = call.request.contentType()
override val contentLength = call.request.contentLength()
override fun readFrom() = byteReadChannel
})
}
}
val responseBodyChannel = response.bodyAsChannel()
call.respond(
status = response.status,
object : OutgoingContent.WriteChannelContent() {
// processing of headers, content type, content length, response status,... (override status,...)
override suspend fun writeTo(channel: ByteWriteChannel) {
responseBodyChannel.copyAndClose(channel)
}
}
)
}
We’d really appreciate any feedback from experienced developers — are there potential pitfalls or best practices we might be overlooking? What would you consider essential when building a robust reverse proxy in Ktor?Aleksei Tirman [JB]
07/28/2025, 7:46 AMOutgoingContent.ReadChannelContent
to directly stream the original response body instead of copying it.
• Develop a plugin to skip the routing process, which introduces a performance cost on every request.Adam Stulpa
07/28/2025, 10:40 AMOutgoingContent.ReadChannelContent
and also will consider developing the plugin. Thank you!