在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):marcoferrer/kroto-plus开源软件地址(OpenSource Url):https://github.com/marcoferrer/kroto-plus开源编程语言(OpenSource Language):Kotlin 100.0%开源软件介绍(OpenSource Introduction):gRPC Kotlin Coroutines, Protobuf DSL, Scripting for ProtocCommunity Contributions are Welcomed
Quick Start: gRPC CoroutinesRun the following command to get started with a preconfigured template project. (kotlin-coroutines-gRPC-template) git clone https://github.com/marcoferrer/kotlin-coroutines-gRPC-template && \
cd kotlin-coroutines-gRPC-template && \
./gradlew run Getting StartedCode Generators
Proto Builder Generator (Message DSL)Setup & DocumentationThis generator creates lambda based builders for message types val starPlatinum = Stand {
name = "Star Platinum"
powerLevel = 500
speed = 550
attack {
name = "ORA ORA ORA"
damage = 100
range = Attack.Range.CLOSE
}
}
val attack = Attack {
name = "ORA ORA ORA"
damage = 100
range = Attack.Range.CLOSE
}
// Copy extensions
val newAttack = attack.copy { damage = 200 }
// orDefault() will return the messages default instance when null
val nullableAttack: Attack? = null
nullableAttack.orDefault()
// Plus operator extensions
val mergedAttack = attack + Attack { name = "Sunlight Yellow Overdrive" } gRPC Coroutines Client & ServerThis option requires the artifact Configuration OptionsClient / Server Examples
// Creates new stub with a default coroutine context of `EmptyCoroutineContext`
val stub = GreeterCoroutineGrpc.newStub(channel)
// Suspends and creates new stub using the current coroutine context as the default.
val stub = GreeterCoroutineGrpc.newStubWithContext(channel)
// An existing stub can replace its current coroutine context using either
stub.withCoroutineContext()
stub.withCoroutineContext(coroutineContext)
// Stubs can accept message builder lambdas as an argument
stub.sayHello { name = "John" }
// For more idiomatic usage in coroutines, stubs can be created
// with an explicit coroutine scope using the `newGrpcStub` scope extension function.
launch {
// Using `newGrpcStub` makes it clear that the resulting stub will use the receiving
// coroutine scope to launch any concurrent work. (usually for manual flow control in streaming apis)
val stub = newGrpcStub(GreeterCoroutineGrpc.GreeterCoroutineStub, channel)
val (requestChannel, responseChannel) = stub.sayHelloStreaming()
...
}
Cancellation Propagation
ExamplesUnaryClient: Unary calls will suspend until a response is received from the corresponding server. In the event of a cancellation or the server responds with an error the call will throw the appropriate val response = stub.sayHello { name = "John" } Server: Unary rpc methods can respond to client requests by either returning the expected response type, or throwing an exception. override suspend fun sayHello(request: HelloRequest): HelloReply {
if (isValid(request.name))
return HelloReply { message = "Hello there, ${request.name}!" } else
throw Status.INVALID_ARGUMENT.asRuntimeException()
} Client StreamingClient: val (requestChannel, response) = stub.sayHelloClientStreaming()
launchProducerJob(requestChannel){
repeat(5){
send { name = "name #$it" }
}
}
println("Client Streaming Response: ${response.await()}") Server: Client streaming rpc methods can respond to client requests by either returning the expected response type, or throwing an exception. Calls to override suspend fun sayHelloClientStreaming(
requestChannel: ReceiveChannel<HelloRequest>
): HelloReply = HelloReply {
message = requestChannel.toList().joinToString()
} Server StreamingClient: val responseChannel = stub.sayHelloServerStreaming { name = "John" }
responseChannel.consumeEach {
println("Server Streaming Response: $it")
} Server: Server streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs. Calls to For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt. override suspend fun sayHelloServerStreaming(
request: HelloRequest,
responseChannel: SendChannel<HelloReply>
) {
for(char in request.name) {
responseChannel.send {
message = "Hello $char!"
}
}
}
Bi-Directional StreamingClient: val (requestChannel, responseChannel) = stub.sayHelloStreaming()
launchProducerJob(requestChannel){
repeat(5){
send { name = "person #$it" }
}
}
responseChannel.consumeEach {
println("Bidi Response: $it")
} Server: Bidi streaming rpc methods can respond to client requests by submitting messages of the expected response type to the response channel. Completion of service method implementations will automatically close response channels in order to prevent abandoned rpcs. Calls to For an example of how to implement long lived response streams please reference MultipleClientSubscriptionsExample.kt. override suspend fun sayHelloStreaming(
requestChannel: ReceiveChannel<HelloRequest>,
responseChannel: SendChannel<HelloReply>
) {
requestChannel.mapTo(responseChannel){
HelloReply {
message = "Hello there, ${it.name}!"
}
}
} gRPC Stub ExtensionsConfiguration OptionsThis modules generates convenience extensions that overload the request message argument for rpc methods with a builder lambda block and a default value. It also supports generating overloads based off (google.api.method_signature) method options. More info available here
//Kroto+ Generated Extension
val response = serviceStub.myRpcMethod {
id = 100
name = "some name"
}
//Original Java Fluent builders
val response = serviceStub.myRpcMethod(ExampleServiceGrpc.MyRpcMethodRequest
.newBuilder()
.setId(100)
.setName("some name")
.build()) For unary rpc methods, the generator will create the following extensions //Future Stub with default argument
fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): ListenableFuture<Response>
//Future Stub with builder lambda
inline fun ServiceFutureStub.myRpcMethod(block: Request.Builder.() -> Unit): ListenableFuture<Response>
//Blocking Stub with default argument
fun ServiceBlockingStub.myRpcMethod(request: Request = Request.defaultInstance): Response
//Blocking Stub with builder lambda
inline fun ServiceBlockingStub.myRpcMethod(block: Request.Builder.() -> Unit): Response Coroutine SupportIn addition to request message arguments as builder lambda rpc overloads, coroutine overloads for rpc calls can also be generated. This provides the same functionality as the generated coroutine stubs. Usage is identical to the client examples outlined in Coroutine Client Examples.
Context.current().withValue(MY_KEY, myValue).attach()
val myGrpcContext = Context.current()
val job = launch( GrpcContextElement() ) { //Alternate usage: myGrpcContext.asContextElement()
launch {
assertEquals(myGrpcContext, Context.current())
}
GlobalScope.launch{
assertNotEquals(myGrpcContext, Context.current())
}
} Mock Service GeneratorConfiguration OptionsThis generator creates mock implementations of proto service definitions. This is useful for orchestrating a set of expected responses, aiding in unit testing methods that rely on rpc calls.
Full example for mocking services in unit tests. The code generated relies on the
@Test fun `Test Unary Response Queue`(){
MockStandService.getStandByNameResponseQueue.apply {
//Queue up a valid response message
addMessage {
name = "Star Platinum"
powerLevel = 500
speed = 550
addAttacks {
name = "ORA ORA ORA"
damage = 100
range = StandProto.Attack.Range.CLOSE
}
}
//Queue up an error
addError(Status.INVALID_ARGUMENT)
}
val standStub = StandServiceGrpc.newBlockingStub(grpcServerRule.channel)
standStub.getStandByName { name = "Star Platinum" }.let{ response ->
assertEquals("Star Platinum",response.name)
assertEquals(500,response.powerLevel)
assertEquals(550,response.speed)
response.attacksList.first().let{ attack ->
assertEquals("ORA ORA ORA",attack.name)
assertEquals(100,attack.damage)
assertEquals(StandProto.Attack.Range.CLOSE,attack.range)
}
}
try{
standStub.getStandByName { name = "The World" }
fail("Exception was expected with status code: ${Status.INVALID_ARGUMENT.code}")
}catch (e: StatusRuntimeException){
assertEquals(Status.INVALID_ARGUMENT.code, e.status.code)
}
} Extendable Messages Generator (Experimental)Configuration OptionsGenerated code relies on the inline fun <reified M, B> M.copy( block: B.() -> Unit ): M
where M : KpMessage<M, B>, B : KpBuilder<M> {
return this.toBuilder.apply(block).build()
}
// Usage
myMessage.copy { ... }
inline fun <reified M, B> build( block: B.() -> Unit ): M
where M : KpMessage<M, B>, B : KpBuilder<M> {
return KpCompanion.Registry[M::class.java].build(block)
}
// Usage
build<MyMessage> { ... }
inline fun <M, B> KpCompanion<M, B>.build( block: B.() -> Unit ): M
where B : KpBuilder<M>,M : KpMessage< |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论