mirror of https://github.com/grpc/grpc.io.git
Kotlin basics tutorial update due to code reorg (#476)
- Contributes to #465 - Also did some copyediting
This commit is contained in:
parent
91309767d5
commit
0e23f24484
|
|
@ -1,6 +1,7 @@
|
||||||
---
|
---
|
||||||
title: Basics tutorial
|
title: Basics tutorial
|
||||||
description: A basic tutorial introduction to gRPC in Kotlin/JVM.
|
description: A basic tutorial introduction to gRPC in Kotlin/JVM.
|
||||||
|
spelling: cSpell:ignore grpckt Mendham millis println
|
||||||
weight: 50
|
weight: 50
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -9,59 +10,51 @@ working with gRPC.
|
||||||
|
|
||||||
By walking through this example you'll learn how to:
|
By walking through this example you'll learn how to:
|
||||||
|
|
||||||
- Define a service in a .proto file.
|
- Define a service in a `.proto` file.
|
||||||
- Generate server and client code using the protocol buffer compiler.
|
- Generate server and client code using the protocol buffer compiler.
|
||||||
- Use the Kotlin gRPC API to write a simple client and server for your service.
|
- Use the Kotlin gRPC API to write a simple client and server for your service.
|
||||||
|
|
||||||
It assumes that you have read the [Introduction to gRPC](/docs/what-is-grpc/introduction/) and are familiar
|
You should already be familiar gRPC and protocol buffers; if not, see
|
||||||
with [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). Note that the
|
[Introduction to gRPC][] and the proto3 [Language guide][proto3].
|
||||||
example in this tutorial uses the proto3 version of the protocol buffers
|
|
||||||
language: you can find out more in the
|
|
||||||
[proto3 language
|
|
||||||
guide](https://developers.google.com/protocol-buffers/docs/proto3).
|
|
||||||
|
|
||||||
### Why use gRPC?
|
### Why use gRPC?
|
||||||
|
|
||||||
Our example is a simple route mapping application that lets clients get
|
{{< why-grpc >}}
|
||||||
information about features on their route, create a summary of their route, and
|
|
||||||
exchange route information such as traffic updates with the server and other
|
|
||||||
clients.
|
|
||||||
|
|
||||||
With gRPC we can define our service once in a .proto file and implement clients
|
### Setup
|
||||||
and servers in any of gRPC's supported languages, which in turn can be run in
|
|
||||||
environments ranging from servers inside Google to your own tablet - all the
|
|
||||||
complexity of communication between different languages and environments is
|
|
||||||
handled for you by gRPC. We also get all the advantages of working with protocol
|
|
||||||
buffers, including efficient serialization, a simple IDL, and easy interface
|
|
||||||
updating.
|
|
||||||
|
|
||||||
### Example code and setup
|
This tutorial has the same [prerequisites][] as the [Quick start][]. Install the
|
||||||
|
necessary SDKs and tools before proceeding.
|
||||||
|
|
||||||
The example code for our tutorial is in
|
### Get the example code
|
||||||
[grpc/grpc-kotlin/examples/src/main/kotlin/io/grpc/examples/routeguide](https://github.com/grpc/grpc-kotlin/tree/master/examples/src/main/kotlin/io/grpc/examples/routeguide).
|
|
||||||
To download the example, clone the latest release in `grpc-kotlin` repository by
|
|
||||||
running the following command:
|
|
||||||
|
|
||||||
```sh
|
The example code is part of the [grpc-kotlin][] repo.
|
||||||
$ git clone https://github.com/grpc/grpc-kotlin.git
|
|
||||||
```
|
|
||||||
|
|
||||||
Then change to the example's main source folder:
|
1. [Download the repo as a zip file][download] and unzip it, or clone
|
||||||
|
the repo:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ cd grpc-kotlin/examples/src/main/kotlin/io/grpc/examples/routeguide
|
$ git clone https://github.com/grpc/grpc-kotlin
|
||||||
```
|
```
|
||||||
|
|
||||||
|
2. Change to the examples directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd grpc-kotlin/examples
|
||||||
|
```
|
||||||
|
|
||||||
### Defining the service
|
### Defining the service
|
||||||
|
|
||||||
Our first step (as you'll know from the [Introduction to gRPC](/docs/what-is-grpc/introduction/)) is to
|
Your first step (as you'll know from the [Introduction to gRPC][]) is to define
|
||||||
define the gRPC *service* and the method *request* and *response* types using
|
the gRPC _service_ and the method _request_ and _response_ types using [protocol
|
||||||
[protocol
|
buffers][proto3].
|
||||||
buffers](https://developers.google.com/protocol-buffers/docs/overview). You can
|
|
||||||
see the complete .proto file in
|
|
||||||
[grpc-kotlin/examples/src/main/proto/route_guide.proto](https://github.com/grpc/grpc-kotlin/blob/master/examples/src/main/proto/route_guide.proto).
|
|
||||||
|
|
||||||
To define a service, you specify a named `service` in the .proto file:
|
If you'd like to follow along by looking at the complete `.proto` file, see
|
||||||
|
`routeguide/route_guide.proto` from the
|
||||||
|
[protos/src/main/proto/io/grpc/examples][protos-src] folder.
|
||||||
|
|
||||||
|
To define a service, you specify a named `service` in the `.proto` file like
|
||||||
|
this:
|
||||||
|
|
||||||
```proto
|
```proto
|
||||||
service RouteGuide {
|
service RouteGuide {
|
||||||
|
|
@ -122,8 +115,8 @@ all of which are used in the `RouteGuide` service:
|
||||||
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
|
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Our `.proto` file also contains protocol buffer message type definitions for all
|
The `.proto` file also contains protocol buffer message type definitions for all
|
||||||
the request and response types used in our service methods - for example, here's
|
the request and response types used by the service methods -- for example, here's
|
||||||
the `Point` message type:
|
the `Point` message type:
|
||||||
|
|
||||||
```proto
|
```proto
|
||||||
|
|
@ -139,51 +132,54 @@ message Point {
|
||||||
|
|
||||||
### Generating client and server code
|
### Generating client and server code
|
||||||
|
|
||||||
Next we need to generate the gRPC client and server interfaces from our .proto
|
Next, you need to generate the gRPC client and server interfaces from the `.proto`
|
||||||
service definition. We do this using the protocol buffer compiler `protoc` with
|
service definition. You do this using the protocol buffer compiler `protoc` with
|
||||||
a special gRPC Kotlin and Java plugins. You need to use the
|
special gRPC Kotlin and Java plugins.
|
||||||
[proto3](https://github.com/google/protobuf/releases) compiler (which supports
|
|
||||||
both proto2 and proto3 syntax) in order to generate gRPC services.
|
|
||||||
|
|
||||||
When using Gradle or Maven, the protoc build plugin can generate the necessary
|
When using Gradle or Maven, the `protoc` build plugin will generate the
|
||||||
code as part of the build. See the [grpc-kotlin README][] for details.
|
necessary code as part of the build process. For a Gradle example, see
|
||||||
|
[stub/build.gradle.kts][].
|
||||||
|
|
||||||
[grpc-kotlin README]: https://github.com/grpc/grpc-kotlin/blob/master/README.md
|
If you run `./gradlew installDist` from the examples folder, the following files
|
||||||
|
are generated from the service definition -- you'll find the generated files in
|
||||||
|
subdirectories below `stub/build/generated/source/proto/main`:
|
||||||
|
|
||||||
The following classes are generated from our service definition:
|
- `Feature.java`, `Point.java`, `Rectangle.java`, and others, which contain all
|
||||||
|
|
||||||
- `Feature.java`, `Point.java`, `Rectangle.java`, and others which contain all
|
|
||||||
the protocol buffer code to populate, serialize, and retrieve our request and
|
the protocol buffer code to populate, serialize, and retrieve our request and
|
||||||
response message types.
|
response message types.
|
||||||
- `RouteGuideGrpcKt.kt`, which contains, among other things:
|
|
||||||
- A base class for `RouteGuide` servers to implement,
|
You'll find these files in the `java/io/grpc/examples/routeguide`
|
||||||
`RouteGuideGrpcKt.RouteGuideCoroutineImplBase`, with all the methods defined
|
subdirectory.
|
||||||
in the `RouteGuide` service.
|
- `RouteGuideOuterClassGrpcKt.kt`, which contains, among other things:
|
||||||
- The `RouteGuideCoroutineStub` class that clients use to talk to a
|
- `RouteGuideGrpcKt.RouteGuideCoroutineImplBase`, an abstract base class for
|
||||||
`RouteGuide` server.
|
`RouteGuide` servers to implement, with all the methods defined in the
|
||||||
|
`RouteGuide` service.
|
||||||
|
- `RouteGuideGrpcKt.RouteGuideCoroutineStub`, a class that clients use to talk
|
||||||
|
to a `RouteGuide` server.
|
||||||
|
|
||||||
|
You'll find this Kotlin file under `grpckt/io/grpc/examples/routeguide`.
|
||||||
|
|
||||||
### Creating the server {#server}
|
### Creating the server {#server}
|
||||||
|
|
||||||
First let's look at how we create a `RouteGuide` server. If you're only
|
First consider how to create a `RouteGuide` server. If you're only interested in
|
||||||
interested in creating gRPC clients, you can skip this section and go straight
|
creating gRPC clients, skip ahead to [Creating the client](#client) -- though
|
||||||
to [Creating the client](#client) (though you might find it interesting
|
you might find this section interesting anyway!
|
||||||
anyway!).
|
|
||||||
|
|
||||||
There are two parts to making our `RouteGuide` service do its job:
|
There are two main things that you need to do when creating a `RouteGuide`
|
||||||
|
server:
|
||||||
|
|
||||||
- Overriding the service base class generated from our service definition: doing
|
- Extend the `RouteGuideCoroutineImplBase` service base class to do the actual
|
||||||
the actual "work" of our service.
|
service work.
|
||||||
- Running a gRPC server to listen for requests from clients and return the
|
- Create and run a gRPC server to listen for requests from clients and return
|
||||||
service responses.
|
the service responses.
|
||||||
|
|
||||||
You can find our example `RouteGuide` server in
|
Open the example `RouteGuide` server code in `routeguide/RouteGuideServer.kt`
|
||||||
[grpc-kotlin/examples/src/main/kotlin/io/grpc/examples/routeguide/RouteGuideServer.kt](https://github.com/grpc/grpc-kotlin/blob/master/examples/src/main/kotlin/io/grpc/examples/routeguide/RouteGuideServer.kt).
|
under the [server/src/main/kotlin/io/grpc/examples][server-src] folder.
|
||||||
Let's take a closer look at how it works.
|
|
||||||
|
|
||||||
#### Implementing RouteGuide
|
#### Implementing RouteGuide
|
||||||
|
|
||||||
As you can see, our server has a `RouteGuideService` class that extends the
|
As you can see, the server has a `RouteGuideService` class that extends the
|
||||||
generated `RouteGuideGrpcKt.RouteGuideCoroutineImplBase` abstract class:
|
generated service base class:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
class RouteGuideService(
|
class RouteGuideService(
|
||||||
|
|
@ -196,10 +192,9 @@ class RouteGuideService(
|
||||||
|
|
||||||
#### Simple RPC
|
#### Simple RPC
|
||||||
|
|
||||||
`RouteGuideService` implements all our service methods. Let's look at the
|
`RouteGuideService` implements all the service methods. Consider the simplest
|
||||||
simplest method first, `GetFeature()`, which just gets a `Point` from the client
|
method first, `GetFeature()`, which gets a `Point` from the client and returns a
|
||||||
and returns the corresponding feature information from its database in a
|
`Feature` built from the corresponding feature information in the database.
|
||||||
`Feature`.
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
override suspend fun getFeature(request: Point): Feature =
|
override suspend fun getFeature(request: Point): Feature =
|
||||||
|
|
@ -209,32 +204,31 @@ override suspend fun getFeature(request: Point): Feature =
|
||||||
```
|
```
|
||||||
|
|
||||||
The method accepts a client's `Point` message request as a parameter, and it
|
The method accepts a client's `Point` message request as a parameter, and it
|
||||||
returns a `Feature` message as a response. In the method we populate the
|
returns a `Feature` message as a response. The method populates the `Feature`
|
||||||
`Feature` with the appropriate information, and then `return` it to the gRPC
|
with the appropriate information, and then returns it to the gRPC framework,
|
||||||
framework, which sends it back to the client.
|
which sends it back to the client.
|
||||||
|
|
||||||
##### Server-side streaming RPC
|
##### Server-side streaming RPC
|
||||||
|
|
||||||
Next let's look at one of our streaming RPCs. `ListFeatures` is a server-side
|
Next, consider one of the streaming RPCs. `ListFeatures()` is a server-side
|
||||||
streaming RPC, so we need to send back multiple `Feature`s to our client.
|
streaming RPC, so the server gets to send back multiple `Feature` messages to
|
||||||
|
the client.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
override fun listFeatures(request: Rectangle): Flow<Feature> =
|
override fun listFeatures(request: Rectangle): Flow<Feature> =
|
||||||
features.asFlow().filter { it.exists() && it.location in request }
|
features.asFlow().filter { it.exists() && it.location in request }
|
||||||
```
|
```
|
||||||
|
|
||||||
Like the simple RPC, this method gets a request object (the `Rectangle` in which
|
The request object is a `Rectangle`. The server collects, and returns to the
|
||||||
our client wants to find `Feature`s).
|
client, all the `Feature` objects in its collection that are inside the given
|
||||||
|
`Rectangle`.
|
||||||
This time, we get as many `Feature` objects as we need to return to the client
|
|
||||||
-- in this case, we select them from the service's feature collection based on
|
|
||||||
whether they're inside our request `Rectangle`.
|
|
||||||
|
|
||||||
##### Client-side streaming RPC
|
##### Client-side streaming RPC
|
||||||
|
|
||||||
Now let's look at something a little more complicated: the client-side streaming
|
Now consider something a little more complicated: the client-side streaming
|
||||||
method `RecordRoute()`, where we get a stream of `Point`s from the client and
|
method `RecordRoute()`, where the server gets a stream of `Point` objects from
|
||||||
return a single `RouteSummary` with information about their trip.
|
the client, and returns a single `RouteSummary` with information about their
|
||||||
|
trip through the given points.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
override suspend fun recordRoute(requests: Flow<Point>): RouteSummary {
|
override suspend fun recordRoute(requests: Flow<Point>): RouteSummary {
|
||||||
|
|
@ -267,11 +261,9 @@ The request parameter is a stream of client request messages represented as a
|
||||||
Kotlin [Flow][]. The server returns a single response just like in the simple
|
Kotlin [Flow][]. The server returns a single response just like in the simple
|
||||||
RPC case.
|
RPC case.
|
||||||
|
|
||||||
[Flow]: https://kotlinlang.org/docs/reference/coroutines/flow.html#flows
|
|
||||||
|
|
||||||
##### Bidirectional streaming RPC
|
##### Bidirectional streaming RPC
|
||||||
|
|
||||||
Finally, let's look at our bidirectional streaming RPC `RouteChat()`.
|
Finally, consider the bidirectional streaming RPC `RouteChat()`.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
override fun routeChat(requests: Flow<RouteNote>): Flow<RouteNote> =
|
override fun routeChat(requests: Flow<RouteNote>): Flow<RouteNote> =
|
||||||
|
|
@ -289,90 +281,69 @@ override fun routeChat(requests: Flow<RouteNote>): Flow<RouteNote> =
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This time we get a stream of `RouteNote` objects that, as in our client-side
|
Similar to the client-side streaming example, for this method, the server gets a
|
||||||
streaming example, can be used to access messages.
|
stream of `RouteNote` objects as a `Flow`. However, this time the server returns
|
||||||
|
`RouteNote` instances via the method's returned stream _while_ the client is still
|
||||||
<!-- However, this time we return
|
writing messages to _its_ message stream.
|
||||||
values via our method's returned stream, while the client is still writing
|
|
||||||
messages to *their* message stream. -->
|
|
||||||
|
|
||||||
#### Starting the server
|
#### Starting the server
|
||||||
|
|
||||||
Once we've implemented all our methods, we also need to start up a gRPC server
|
Once all the server's methods are implemented, you need code to create a gRPC
|
||||||
so that clients can actually use our service. The following snippet shows how we
|
server instance, something like this:
|
||||||
do this for our `RouteGuide` service:
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
class RouteGuideServer private constructor(
|
class RouteGuideServer(
|
||||||
val port: Int,
|
val port: Int,
|
||||||
val server: Server
|
val features: Collection<Feature> = Database.features(),
|
||||||
|
val server: Server =
|
||||||
|
ServerBuilder.forPort(port)
|
||||||
|
.addService(RouteGuideService(features)).build()
|
||||||
) {
|
) {
|
||||||
constructor(port: Int) : this(port, defaultFeatureSource())
|
|
||||||
|
|
||||||
constructor(port: Int, featureData: ByteSource) :
|
|
||||||
this(
|
|
||||||
serverBuilder = ServerBuilder.forPort(port),
|
|
||||||
port = port,
|
|
||||||
features = featureData.parseJsonFeatures()
|
|
||||||
)
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
serverBuilder: ServerBuilder<*>,
|
|
||||||
port: Int,
|
|
||||||
features: Collection<Feature>
|
|
||||||
) : this(
|
|
||||||
port = port,
|
|
||||||
server = serverBuilder.addService(RouteGuideService(features)).build()
|
|
||||||
)
|
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
server.start()
|
server.start()
|
||||||
println("Server started, listening on $port")
|
println("Server started, listening on $port")
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
fun main(args: Array<String>) {
|
||||||
@JvmStatic
|
val port = 8980
|
||||||
fun main(args: Array<String>) {
|
val server = RouteGuideServer(port)
|
||||||
val port = 8980
|
server.start()
|
||||||
val server = RouteGuideServer(port)
|
|
||||||
server.start()
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ... */
|
/* ... */
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
As you can see, we build and start our server using a `ServerBuilder`.
|
|
||||||
|
|
||||||
To do this, we:
|
A server instance is built and started using a `ServerBuilder` as follows:
|
||||||
|
|
||||||
1. Specify the address and port we want to use to listen for client requests
|
1. Specify the port, that the server will listen for client requests on, using
|
||||||
using the builder's `forPort()` method.
|
`forPort()`.
|
||||||
1. Create an instance of our service implementation class `RouteGuideService`
|
1. Create an instance of the service implementation class `RouteGuideService`
|
||||||
and pass it to the builder's `addService()` method.
|
and pass it to the builder's `addService()` method.
|
||||||
1. Call `build()` and `start()` on the builder to create and start an RPC server
|
1. Call `build()` and `start()` on the builder to create and start an RPC server
|
||||||
for our service.
|
for the route guide service.
|
||||||
|
|
||||||
### Creating the client {#client}
|
### Creating the client {#client}
|
||||||
|
|
||||||
In this section, we'll look at creating a client for our `RouteGuide`
|
In this section, you'll look at a client for the `RouteGuide` service.
|
||||||
service. You can see our complete example client code in
|
|
||||||
[grpc-kotlin/examples/src/main/kotlin/io/grpc/examples/routeguide/RouteGuideClient.kt](https://github.com/grpc/grpc-kotlin/blob/master/examples/src/main/kotlin/io/grpc/examples/routeguide/RouteGuideClient.kt).
|
For the complete client code, open `routeguide/RouteGuideClient.kt`
|
||||||
|
under the [client/src/main/kotlin/io/grpc/examples][client-src] folder.
|
||||||
|
|
||||||
#### Instantiating a stub
|
#### Instantiating a stub
|
||||||
|
|
||||||
To call service methods, we first need to create a gRPC *channel* to communicate
|
To call service methods, you first need to create a gRPC _channel_ using a
|
||||||
with the server. We use a `ManagedChannelBuilder` to create the channel:
|
`ManagedChannelBuilder`. You'll use this channel to communicate with the server.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val channel = ManagedChannelBuilder.forAddress("localhost", 8980).usePlaintext()
|
val channel = ManagedChannelBuilder.forAddress("localhost", 8980).usePlaintext().build()
|
||||||
```
|
```
|
||||||
|
|
||||||
Once the gRPC *channel* is setup, we need a client _stub_ to perform RPCs. We
|
Once the gRPC channel is setup, you need a client _stub_ to perform RPCs. Get it
|
||||||
get it by instantiating `RouteGuideCoroutineStub`, which is available from the
|
by instantiating `RouteGuideCoroutineStub`, which is available from the package
|
||||||
package that we generated from our .proto file.
|
that was generated from the `.proto` file.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val stub = RouteGuideCoroutineStub(channel)
|
val stub = RouteGuideCoroutineStub(channel)
|
||||||
|
|
@ -380,12 +351,12 @@ val stub = RouteGuideCoroutineStub(channel)
|
||||||
|
|
||||||
#### Calling service methods
|
#### Calling service methods
|
||||||
|
|
||||||
Now let's look at how we call our service methods.
|
Now consider how you'll call service methods.
|
||||||
|
|
||||||
##### Simple RPC
|
##### Simple RPC
|
||||||
|
|
||||||
Calling the simple RPC `GetFeature()` is as straightforward as calling a local
|
Calling the simple RPC `GetFeature()` is as straightforward as calling a local
|
||||||
method.
|
method:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val request = point(latitude, longitude)
|
val request = point(latitude, longitude)
|
||||||
|
|
@ -393,13 +364,10 @@ val feature = stub.getFeature(request)
|
||||||
```
|
```
|
||||||
|
|
||||||
The stub method `getFeature()` executes the corresponding RPC, suspending until
|
The stub method `getFeature()` executes the corresponding RPC, suspending until
|
||||||
the RPC completes. We want the client to await (or block) until the RPC
|
the RPC completes:
|
||||||
completes, so we make the stub method call inside a [runBlocking][] coroutine
|
|
||||||
builder. We wrap the entire body of the client class's `getFeature()` helper
|
|
||||||
method like this:
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun getFeature(latitude: Int, longitude: Int) = runBlocking {
|
suspend fun getFeature(latitude: Int, longitude: Int) {
|
||||||
val request = point(latitude, longitude)
|
val request = point(latitude, longitude)
|
||||||
val feature = stub.getFeature(request)
|
val feature = stub.getFeature(request)
|
||||||
if (feature.exists()) { /* ... */ }
|
if (feature.exists()) { /* ... */ }
|
||||||
|
|
@ -408,11 +376,11 @@ fun getFeature(latitude: Int, longitude: Int) = runBlocking {
|
||||||
|
|
||||||
##### Server-side streaming RPC
|
##### Server-side streaming RPC
|
||||||
|
|
||||||
Next, let's look at a server-side streaming `ListFeatures()` RPC, which returns
|
Next, consider the server-side streaming `ListFeatures()` RPC, which returns a
|
||||||
a stream of geographical `Feature`s:
|
stream of geographical features:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun listFeatures(lowLat: Int, lowLon: Int, hiLat: Int, hiLon: Int) = runBlocking {
|
suspend fun listFeatures(lowLat: Int, lowLon: Int, hiLat: Int, hiLon: Int) {
|
||||||
val request = Rectangle.newBuilder()
|
val request = Rectangle.newBuilder()
|
||||||
.setLo(point(lowLat, lowLon))
|
.setLo(point(lowLat, lowLon))
|
||||||
.setHi(point(hiLat, hiLon))
|
.setHi(point(hiLat, hiLon))
|
||||||
|
|
@ -425,16 +393,16 @@ fun listFeatures(lowLat: Int, lowLon: Int, hiLat: Int, hiLon: Int) = runBlocking
|
||||||
```
|
```
|
||||||
|
|
||||||
The stub `listFeatures()` method returns a stream of features in the form of a
|
The stub `listFeatures()` method returns a stream of features in the form of a
|
||||||
`Flow<Feature>` instance. The flow `collect` method allows the client to
|
`Flow<Feature>` instance. The flow `collect()` method allows the client to
|
||||||
processes the server-provided features as they become available.
|
processes the server-provided features as they become available.
|
||||||
|
|
||||||
##### Client-side streaming RPC
|
##### Client-side streaming RPC
|
||||||
|
|
||||||
With the client-side streaming `RecordRoute()` RPC, we send a stream of
|
The client-side streaming `RecordRoute()` RPC sends a stream of `Point` messages
|
||||||
`Point` messages to the server and get back a single `RouteSummary`.
|
to the server and gets back a single `RouteSummary`.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun recordRoute(points: Flow<Point>) = runBlocking {
|
suspend fun recordRoute(points: Flow<Point>) {
|
||||||
println("*** RecordRoute")
|
println("*** RecordRoute")
|
||||||
val summary = stub.recordRoute(points)
|
val summary = stub.recordRoute(points)
|
||||||
println("Finished trip with ${summary.pointCount} points.")
|
println("Finished trip with ${summary.pointCount} points.")
|
||||||
|
|
@ -445,9 +413,9 @@ fun recordRoute(points: Flow<Point>) = runBlocking {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
We generate the route points from the points associated with a randomly selected
|
The method generates the route points from the points associated with a randomly
|
||||||
list of features. The random selection is taken from a previously loaded feature
|
selected list of features. The random selection is taken from a previously
|
||||||
collection:
|
loaded feature collection:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun generateRoutePoints(features: List<Feature>, numPoints: Int): Flow<Point> = flow {
|
fun generateRoutePoints(features: List<Feature>, numPoints: Int): Flow<Point> = flow {
|
||||||
|
|
@ -466,16 +434,15 @@ until the server requests the next point.
|
||||||
|
|
||||||
##### Bidirectional streaming RPC
|
##### Bidirectional streaming RPC
|
||||||
|
|
||||||
Finally, let's look at our bidirectional streaming RPC `RouteChat()`. As in the
|
Finally, consider the bidirectional streaming RPC `RouteChat()`. As in the case
|
||||||
case of `RecordRoute()`, we pass to the method a stream that we'll use to write
|
of `RecordRoute()`, you pass to the stub method a stream that you use to write
|
||||||
the request messages to; like in `ListFeatures()`, we get back a stream that we
|
the request messages to; like in `ListFeatures()`, you get back a stream that
|
||||||
can use to read response messages from. However, this time we'll send values via
|
you can use to read response messages from. However, this time you send values
|
||||||
our method's stream while the server is also writing messages to _its_ message
|
via our method's stream while the server is also writing messages to _its_
|
||||||
stream.
|
message stream.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun routeChat() = runBlocking {
|
suspend fun routeChat() {
|
||||||
println("*** RouteChat")
|
|
||||||
val requests = generateOutgoingNotes()
|
val requests = generateOutgoingNotes()
|
||||||
stub.routeChat(requests).collect { note ->
|
stub.routeChat(requests).collect { note ->
|
||||||
println("Got message \"${note.message}\" at ${note.location.toStr()}")
|
println("Got message \"${note.message}\" at ${note.location.toStr()}")
|
||||||
|
|
@ -487,44 +454,41 @@ private fun generateOutgoingNotes(): Flow<RouteNote> = flow {
|
||||||
val notes = listOf(/* ... */)
|
val notes = listOf(/* ... */)
|
||||||
for (note in notes) {
|
for (note in notes) {
|
||||||
println("Sending message \"${note.message}\" at ${note.location.toStr()}")
|
println("Sending message \"${note.message}\" at ${note.location.toStr()}")
|
||||||
emit(note);
|
emit(note)
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The syntax for reading and writing here is very similar to our client-side and
|
The syntax for reading and writing here is very similar to the client-side and
|
||||||
server-side streaming methods. Although each side will always get the other's
|
server-side streaming methods. Although each side will always get the other's
|
||||||
messages in the order they were written, both the client and server can read and
|
messages in the order they were written, both the client and server can read and
|
||||||
write in any order —- the streams operate completely independently.
|
write in any order —- the streams operate completely independently.
|
||||||
|
|
||||||
### Try it out!
|
### Try it out!
|
||||||
|
|
||||||
1. Work from the example directory:
|
Run the following commands from the `grpc-kotlin/examples` directory:
|
||||||
|
|
||||||
```sh
|
1. Compile the client and server
|
||||||
$ cd examples/route_guide
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Compile the client and server
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ./gradlew installDist
|
$ ./gradlew installDist
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run the server:
|
2. Run the server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ./build/install/examples/bin/route-guide-server
|
$ ./server/build/install/server/bin/route-guide-server
|
||||||
|
Server started, listening on 8980
|
||||||
```
|
```
|
||||||
|
|
||||||
4. From another terminal, run the client:
|
3. From another terminal, run the client:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ./build/install/examples/bin/route-guide-client
|
$ ./client/build/install/client/bin/route-guide-client
|
||||||
```
|
```
|
||||||
|
|
||||||
You'll see output like this:
|
You'll see client output like this:
|
||||||
|
|
||||||
```nocode
|
```nocode
|
||||||
*** GetFeature: lat=409146138 lon=-746188906
|
*** GetFeature: lat=409146138 lon=-746188906
|
||||||
|
|
@ -563,3 +527,15 @@ Got message "First message" at 0.0, 0.0
|
||||||
Got message "Second message" at 0.0, 0.0
|
Got message "Second message" at 0.0, 0.0
|
||||||
Finished RouteChat
|
Finished RouteChat
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[client-src]: https://github.com/grpc/grpc-kotlin/tree/master/examples/client/src/main/kotlin/io/grpc/examples
|
||||||
|
[download]: https://github.com/grpc/grpc-kotlin/archive/master.zip
|
||||||
|
[Flow]: https://kotlinlang.org/docs/reference/coroutines/flow.html#flows
|
||||||
|
[grpc-kotlin]: https://github.com/grpc/grpc-kotlin
|
||||||
|
[Introduction to gRPC]: /docs/what-is-grpc/introduction/
|
||||||
|
[proto3]: https://developers.google.com/protocol-buffers/docs/proto3
|
||||||
|
[Prerequisites]: ../quickstart/#prerequisites
|
||||||
|
[protos-src]: https://github.com/grpc/grpc-kotlin/tree/master/examples/protos/src/main/proto/io/grpc/examples
|
||||||
|
[Quick start]: ../quickstart/
|
||||||
|
[server-src]: https://github.com/grpc/grpc-kotlin/tree/master/examples/server/src/main/kotlin/io/grpc/examples
|
||||||
|
[stub/build.gradle.kts]: https://github.com/grpc/grpc-kotlin/blob/master/examples/stub/build.gradle.kts
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue