Transport
Transport layer exists to abstract from the RPC requests logic and focus on delivering and receiving encoded RPC messages in kRPC Protocol. This layer is represented by RPCTransport
interface. It supports two message formats — string and binary, and depending on which serialization format you choose, one or the other will be used.
Ktor transport
The kotlinx.rpc
library provides integration with the Ktor framework with the in-house RPC protocol. This includes both server and client APIs. Under the hood, the library uses WebSocket plugin to create a RPCTransport
and send and receive messages through it.
Client
kotlinx.rpc
provides a way to plug-in into existing Ktor clients with your RPC services. To do that, the following DSL can be used:
val ktorClient = HttpClient {
installRPC { // this: RPCConfigBuilder.Client
waitForServices = true
}
}
val rpcClient: KtorRPCClient =
ktorClient.rpc("ws://localhost:4242/services") { // this: HttpRequestBuilder
rpcConfig { // this: RPCConfigBuilder.Client
waitForServices = false
}
}
// access WebSocketSession that created the connection
rpcClient.webSocketSession
// create RPC service
val myService: MyService = rpcClient.withService<MyService>()
Note that in this example, only the latter defined RPCConfig
will be used.
Server
kotlinx.rpc
provides a way to plug-in into existing server routing with your RPC services. To do that, the following DSL can be used:
fun Application.module() {
install(RPC) { // this: RPCConfigBuilder.Server
waitForServices = true
}
routing {
rpc("/services") { // this RPCRoute, inherits WebSocketSession
rpcConfig { // this: RPCConfigBuilder.Server
waitForServices = false
}
registerService<MyService> { ctx -> MyServiceImpl(ctx) }
registerService<MyOtherService> { ctx - MyOtherServiceImpl(ctx) }
// etc
}
}
}
Ktor application example
An example code for a Ktor web application may look like this:
// ### COMMON CODE ###
@Serializable
data class ProcessedImage(
val url: String,
val numberOfCats: Int,
val numberOfDogs: Int
)
interface ImageService : RPC {
suspend fun processImage(url: Srting): ProcessedImage
}
// ### CLIENT CODE ###
val client = HttpClient {
installRPC {
serialization {
json()
}
}
}
val service = client.rpc("/image-recognizer").withService<ImageService>()
service.processImage(url = "https://catsanddogs.com/cats/1")
// ### SERVER CODE ###
class ImageServiceImpl(override val coroutineContext: CoroutineContext) : ImageService {
// user defined classes
private val downloader = Downloader()
private val recognizer = AnimalRecognizer()
override suspend fun processImage(url: Srting): ProcessedImage {
val image = downloader.loadImage(url)
return ProcessedImage(
url,
recognizer.getNumberOfCatsOnImage(image),
recognizer.getNumberOfDogsOnImage(image)
)
}
}
fun main() {
embeddedServer(Netty, port = 8080) {
install(RPC) {
serialization {
json()
}
}
routing {
rpc("/image-recognizer") {
registerService<ImageService> { ctx -> ImageServiceImpl(ctx) }
}
}
}.start(wait = true)
}
For more details and complete examples, see the code samples.
Other transports
Generally, there are no specific guidelines on how RPC should be set up for different transports, but structures and APIs used to develop integration with Ktor should outline the common approach. You can provide your own transport and even your own fully implemented protocols, while the library will take care of code generation.
Last modified: 20 June 2024