Kroto+是一个面向Kotlin的gRPC库
https://github.com/marcoferrer/kroto-plus
以往,想在Kotlin上使用protobuf协议进行grpc通信,需要基于java生成本地stub代码然后在kotlin中调用,现在通过Kroto+可以直接生成更加kotlin范儿的本地stub,包括对Coroutine的支持、支持DSL等,非常方便!
// before
val message1 = MessageRequest.newBuilder()
.setMessage("hello")
.build()
// Kroto+
val message2 = MessageRequest {
message = "hello"
}
接下来通过一个例子了解一下=Kproto+的使用
gradle.properties定义版本号
krotoplus_version=0.5.0
protobuf_version=3.10.0
coroutines_version=1.3.2
grpc_version=1.25.0
添加protobuf的plugin
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
id 'com.google.protobuf' version '0.8.10' // 添加protobuf
id 'application'
}
添加dependencies依赖
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "com.github.marcoferrer.krotoplus:kroto-plus-coroutines:$krotoplus_version"
implementation "com.github.marcoferrer.krotoplus:kroto-plus-message:$krotoplus_version"
implementation "com.google.protobuf:protobuf-java:$protobuf_version"
implementation "io.grpc:grpc-protobuf:$grpc_version"
implementation "io.grpc:grpc-stub:$grpc_version"
implementation "io.grpc:grpc-netty:$grpc_version"
}
配置compileKotlin
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += [
"-Xuse-experimental=kotlinx.coroutines.ObsoleteCoroutinesApi"
]
}
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
配置protobuf的task
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobuf_version"
}
plugins {
grpc {
artifact = "io.grpc:protoc-gen-grpc-java:$grpc_version"
}
coroutines {
artifact = "com.github.marcoferrer.krotoplus:protoc-gen-grpc-coroutines:$krotoplus_version:jvm8@jar"
}
kroto {
artifact = "com.github.marcoferrer.krotoplus:protoc-gen-kroto-plus:$krotoplus_version:jvm8@jar"
}
}
generateProtoTasks {
def krotoConfig = file("krotoPlusConfig.asciipb")
all().each { task ->
task.inputs.files krotoConfig
task.plugins {
grpc {}
coroutines {}
kroto {
outputSubDir = "java"
option "ConfigPath=$krotoConfig"
}
}
}
}
}
generateProtoTasks.krotoConfig
中设置的krotoPlusConfig.asciipb
是生成stub代码的配置,本例子需要支持DSL,所以配置如下
//krotoPlusConfig.ascilibp
proto_builders {
filter { exclude_path: "google/*" }
unwrap_builders: true
use_dsl_markers: true
}
最后,为了让生成代码参与编译,配置sourceSets指定codegen的目录
sourceSets {
main {
java {
srcDir("$buildDir/generated/source/proto/main/java")
srcDir("$buildDir/generated/source/proto/main/grpc")
srcDir("$buildDir/generated/source/proto/main/coroutines")
}
}
}
分别定义了四种服务类型的rpc
api.proto:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.my.krotosample.protobuf";
option java_outer_classname = "KrotoSample";
package api;
message MessageRequest {
string message = 1;
}
message MessageResponse {
string message = 1;
}
service MessageService {
rpc Unary (MessageRequest) returns (MessageResponse);
rpc ClientStream (stream MessageRequest) returns (MessageResponse);
rpc ServerStream (MessageRequest) returns (stream MessageResponse);
rpc BidirectionalStream (stream MessageRequest) returns (stream MessageResponse);
}
gradle命令执行 generateProto
./gradlew generateProto
实现MessageServiceImpl
,继承自MessageServiceCoroutineGrpc.MessageServiceImplBase
@ExperimentalCoroutinesApi
class MessageServiceImpl : MessageServiceCoroutineGrpc.MessageServiceImplBase() {
override val initialContext: CoroutineContext
get() = Dispatchers.Default + SupervisorJob()
override suspend fun unary(
request: MessageRequest
): MessageResponse {
return MessageResponse {
message = request.message.toUpperCase()
}
}
override suspend fun clientStream(
requestChannel: ReceiveChannel<MessageRequest>
): MessageResponse {
val requestList = requestChannel.toList()
return MessageResponse {
message = requestList.joinToString("\n") {
it.message.toUpperCase()
}
}
}
override suspend fun serverStream(
request: MessageRequest,
responseChannel: SendChannel<MessageResponse>
) {
val response = MessageResponse {
message = request.message.toUpperCase()
}
repeat(2) {
responseChannel.send(response)
}
responseChannel.close()
}
override suspend fun bidirectionalStream(
requestChannel: ReceiveChannel<MessageRequest>,
responseChannel: SendChannel<MessageResponse>
) {
requestChannel.consumeEach { request ->
val response = MessageResponse {
message = request.message.toUpperCase()
}
responseChannel.send(response)
}
}
}
作为例子,业务比较简单,将request携带的message进行upperCase后返回。
stream中使用了Coroutine的Channel,相对于回调的写法更加方便
gradle启动server
./gradlew run
Main.kt:
@ExperimentalCoroutinesApi
fun main() {
val port = 6565
val server = ServerBuilder.forPort(port)
.addService(MessageServiceImpl())
.build()
.start()
Runtime.getRuntime().addShutdownHook(Thread() {
server.shutdown()
})
server.awaitTermination()
}
build.gradle中配置main
application {
mainClassName = "MainKt"
}
Client端的stub也可以支持Coroutine
Main.kt:
@ExperimentalCoroutinesApi
fun main() {
val port = 6565
val server = ServerBuilder.forPort(port)
.addService(MessageServiceImpl())
.build()
.start()
val channel = ManagedChannelBuilder.forAddress("localhost", 6565)
.usePlaintext()
.build()
val client = MessageServiceCoroutineGrpc
.MessageServiceCoroutineStub.newStub(channel)
println("--- Bidirectional Stream start ---")
runBlocking {
val (requestChannel, responseChannel) = client.bidirectionalStream()
listOf("hello", "kotlin", "proto").forEach {
requestChannel.send {
message = it
}
}
requestChannel.close()
responseChannel.consumeEach {
println(it.message)
}
}
println("--- Bidirectional Stream finish ---")
println("--- Client Stream start ---")
runBlocking {
val (requestChannel, response) = client.clientStream()
listOf("hello", "kotlin", "proto").forEach {
requestChannel.send {
message = it
}
}
requestChannel.close()
println(response.await().message)
}
println("--- Client Stream finish ---")
println("--- Server Stream start ---")
runBlocking {
val request = MessageRequest {
message = "kproto"
}
client.serverStream(request).consumeEach {
println(it.message)
}
}
println("--- Server Stream finish ---")
server.shutdown()
}
Kproto+生成的stub都是suspend函数,所以可以在Coroutine中执行,同样使用Channel甚至Flow方便的进行IPC通信
运行结果
--- Bidirectional Stream start ---
HELLO
KOTLIN
PROTO
--- Bidirectional Stream finish ---
--- Client Stream start ---
HELLO
KOTLIN
PROTO
--- Client Stream finish ---
--- Server Stream start ---
KPROTO
KPROTO
--- Server Stream finish ---