diff --git a/README.md b/README.md index f3e28f82e22..f6004f7594f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ You can find quick start guides for each language, including installation instru * [C++](https://github.com/grpc/grpc-common/tree/master/cpp) * [Java](https://github.com/grpc/grpc-common/tree/master/java) * [Go](https://github.com/grpc/grpc-common/tree/master/go) -* [ruby](https://github.com/grpc/grpc-common/tree/master/ruby) +* [Ruby](https://github.com/grpc/grpc-common/tree/master/ruby) * [Node.js](https://github.com/grpc/grpc-common/tree/master/node) * [Android Java](https://github.com/grpc/grpc-common/tree/master/java/android) * [Python](https://github.com/grpc/grpc-common/tree/master/python/helloworld) @@ -441,4 +441,7 @@ it's written in a different language. ``` $ greeter_client ``` +## Read more! +- You can find links to language-specific tutorials, examples, and other docs in each language's [quick start](#quickstart). +- [gRPC Authentication Support]() introduces authentication support in gRPC with supported mechanisms and examples. diff --git a/go/gotutorial.md b/go/gotutorial.md index c1d06ce4476..f35199df245 100644 --- a/go/gotutorial.md +++ b/go/gotutorial.md @@ -183,24 +183,6 @@ func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide } return nil } - Status ListFeatures(ServerContext* context, const Rectangle* rectangle, - ServerWriter* writer) override { - auto lo = rectangle->lo(); - auto hi = rectangle->hi(); - long left = std::min(lo.longitude(), hi.longitude()); - long right = std::max(lo.longitude(), hi.longitude()); - long top = std::max(lo.latitude(), hi.latitude()); - long bottom = std::min(lo.latitude(), hi.latitude()); - for (const Feature& f : feature_list_) { - if (f.location().longitude() >= left && - f.location().longitude() <= right && - f.location().latitude() >= bottom && - f.location().latitude() <= top) { - writer->Write(f); - } - } - return Status::OK; - } ``` As you can see, instead of getting simple request and response objects in our method parameters, this time we get a request object (the `Rectangle` in which our client wants to find `Feature`s) and a special `RouteGuide_ListFeaturesServer` object. In the method, we populate as many `Feature` objects as we need to return, writing them to the `RouteGuide_ListFeaturesServer` using its `Send()` method. Finally, as in our simple RPC, we return a `nil` error to tell gRPC that we've finished writing responses. Should there be any error happened in this call, we return a non-`nil` error and the gRPC layer will translate it into an appropriate RPC status to be sent on the wire. diff --git a/grpc-auth-support.md b/grpc-auth-support.md index 129fb3045fb..6a5ef0fa3d0 100644 --- a/grpc-auth-support.md +++ b/grpc-auth-support.md @@ -1,51 +1,50 @@ #gRPC Authentication support -gRPC is designed to plug-in a number of authentication mechanisms. We provide an overview -of the various auth mechanisms supported, discuss the API and demonstrate usage through -code examples, and conclude with a discussion of extensibility. +gRPC is designed to plug-in a number of authentication mechanisms. This document provides a quick overview +of the various auth mechanisms supported, discusses the API with some examples, and concludes with a discussion of extensibility. More documentation and examples are coming soon! + +## Supported auth mechanisms ###SSL/TLS gRPC has SSL/TLS integration and promotes the use of SSL/TLS to authenticate the server, -and encrypt all the data exchanged between the client and the server. Optional -mechanisms are available for clients to provide certificates to accomplish mutual +and encrypt all the data exchanged between the client and the server. Optional +mechanisms are available for clients to provide certificates to accomplish mutual authentication. ###OAuth 2.0 -gRPC provides a generic mechanism (described below) to attach metadata to requests -and responses. This mechanism can be used to attach OAuth 2.0 Access Tokens to -RPCs being made at a client. Additional support for acquiring Access Tokens while -accessing Google APIs through gRPC is provided for certain auth flows, demonstrated +gRPC provides a generic mechanism (described below) to attach metadata to requests +and responses. This mechanism can be used to attach OAuth 2.0 Access Tokens to +RPCs being made at a client. Additional support for acquiring Access Tokens while +accessing Google APIs through gRPC is provided for certain auth flows, demonstrated through code examples below. -###API -To reduce complexity and minimize API clutter, gRPC works with a unified concept of -a Credentials object. Users construct gRPC credentials using corresponding bootstrap -credentials (e.g., SSL client certs or Service Account Keys), and use the -credentials while creating a gRPC channel to any server. Depending on the type of -credential supplied, the channel uses the credentials during the initial SSL/TLS +## API +To reduce complexity and minimize API clutter, gRPC works with a unified concept of +a Credentials object. Users construct gRPC credentials using corresponding bootstrap +credentials (e.g., SSL client certs or Service Account Keys), and use the +credentials while creating a gRPC channel to any server. Depending on the type of +credential supplied, the channel uses the credentials during the initial SSL/TLS handshake with the server, or uses the credential to generate and attach Access Tokens to each request being made on the channel. -###Code Examples - -####SSL/TLS for server authentication and encryption +###SSL/TLS for server authentication and encryption This is the simplest authentication scenario, where a client just wants to authenticate the server and encrypt all data. ``` -SslCredentialsOptions ssl_opts; // Options to override SSL params, empty by default +SslCredentialsOptions ssl_opts; // Options to override SSL params, empty by default // Create the credentials object by providing service account key in constructor std::unique_ptr creds = CredentialsFactory::SslCredentials(ssl_opts); // Create a channel using the credentials created in the previous step std::shared_ptr channel = CreateChannel(server_name, creds, channel_args); // Create a stub on the channel std::unique_ptr stub(Greeter::NewStub(channel)); -// Make actual RPC calls on the stub. +// Make actual RPC calls on the stub. grpc::Status s = stub->sayHello(&context, *request, response); ``` -For advanced use cases such as modifying the root CA or using client certs, -the corresponding options can be set in the SslCredentialsOptions parameter +For advanced use cases such as modifying the root CA or using client certs, +the corresponding options can be set in the SslCredentialsOptions parameter passed to the factory method. @@ -61,11 +60,11 @@ std::unique_ptr stub(Greeter::NewStub(channel)); grpc::Status s = stub->sayHello(&context, *request, response); ``` -This credential works for applications using Service Accounts as well as for -applications running in Google Compute Engine (GCE). In the former case, the +This credential works for applications using Service Accounts as well as for +applications running in [Google Compute Engine (GCE)](https://cloud.google.com/compute/). In the former case, the service account’s private keys are loaded from the file named in the environment variable `GOOGLE_APPLICATION_CREDENTIALS`. The -keys are used to generate bearer tokens that are attached to each outgoing RPC +keys are used to generate bearer tokens that are attached to each outgoing RPC on the corresponding channel. For applications running in GCE, a default service account and corresponding @@ -75,21 +74,23 @@ tokens and attaches them to each outgoing RPC on the corresponding channel. Extending gRPC to support other authentication mechanisms The gRPC protocol is designed with a general mechanism for sending metadata associated with RPC. Clients can send metadata at the beginning of an RPC and -servers can send back metadata at the beginning and end of the RPC. This -provides a natural mechanism to support OAuth2 and other authentication -mechanisms that need attach bearer tokens to individual request. +servers can send back metadata at the beginning and end of the RPC. This +provides a natural mechanism to support OAuth2 and other authentication +mechanisms that need attach bearer tokens to individual request. In the simplest case, there is a single line of code required on the client -to add a specific token as metadata to an RPC and a corresponding access on -the server to retrieve this piece of metadata. The generation of the token +to add a specific token as metadata to an RPC and a corresponding access on +the server to retrieve this piece of metadata. The generation of the token on the client side and its verification at the server can be done separately. -A deeper integration can be achieved by plugging in a gRPC credentials implementation for any custom authentication mechanism that needs to attach per-request tokens. gRPC internals also allow switching out SSL/TLS with other encryption mechanisms. +A deeper integration can be achieved by plugging in a gRPC credentials implementation for any custom authentication mechanism that needs to attach per-request tokens. gRPC internals also allow switching out SSL/TLS with other encryption mechanisms. + +## Examples These authentication mechanisms will be available in all gRPC's supported languages. -The following sections demonstrate how authentication and authorization features described above appear in each language +The following sections demonstrate how authentication and authorization features described above appear in each language: more languages are coming soon. -####SSL/TLS for server authentication and encryption (Ruby) +###SSL/TLS for server authentication and encryption (Ruby) ```ruby # Base case - No encryption stub = Helloworld::Greeter::Stub.new('localhost:50051') @@ -116,3 +117,24 @@ stub = Helloworld::Greeter::Stub.new('localhost:50051', creds: creds, update_metadata: authorization.updater_proc) ``` + +###Authenticating with Google (Node.js) + +```node +// Base case - No encryption/authorization +var stub = new helloworld.Greeter('localhost:50051'); +... +// Authenticating with Google +var GoogleAuth = require('google-auth-library'); // from https://www.npmjs.com/package/google-auth-library +... +var creds = grpc.Credentials.createSsl(load_certs); // load_certs typically loads a CA roots file +var scope = 'https://www.googleapis.com/auth/grpc-testing'; +(new GoogleAuth()).getApplicationDefault(function(err, auth) { + if (auth.createScopeRequired()) { + auth = auth.createScoped(scope); + } + var stub = new helloworld.Greeter('localhost:50051', + {credentials: creds}, + grpc.getGoogleAuthDelegate(auth)); +}); +``` diff --git a/java/android/README.md b/java/android/README.md index a231faf9817..aabc3560112 100644 --- a/java/android/README.md +++ b/java/android/README.md @@ -9,32 +9,32 @@ PREREQUISITES ------------- - [Java gRPC](https://github.com/grpc/grpc-java) -- [Android Tutorial](https://developer.android.com/training/basics/firstapp/index.html) If you're new to Android development +- [Android Tutorial](https://developer.android.com/training/basics/firstapp/index.html) if you're new to Android development - We only have Android gRPC client in this example. Please follow examples in other languages to build and run a gRPC server. INSTALL ------- -1 Clone the gRPC Java git repo +**1 Clone the gRPC Java git repo** ```sh $ git clone https://github.com/grpc/grpc-java ``` -2 Install gRPC Java, as described in [How to Build](https://github.com/grpc/grpc-java#how-to-build) +**2 Install gRPC Java, as described in [How to Build](https://github.com/grpc/grpc-java#how-to-build)** ```sh $ # from this dir $ cd grpc-java $ # follow the instructions in 'How to Build' ``` -3 [Create an Android project](https://developer.android.com/training/basics/firstapp/creating-project.html) under your working directory. +**3 [Create an Android project](https://developer.android.com/training/basics/firstapp/creating-project.html) under your working directory.** - Set Application name to "Helloworld Example" and set Company Domain to "grpc.io". Make sure your package name is "io.grpc.helloworldexample" - Choose appropriate minimum SDK - Use Blank Activity - Set Activity Name to HelloworldActivity - Set Layout Name to activity_helloworld -4 Prepare the app +**4 Prepare the app** - Clone this git repo ```sh $ git clone https://github.com/grpc/grpc-common @@ -48,18 +48,21 @@ $ git clone https://github.com/grpc/grpc-common ``` added outside your appplication tag -5 Add dependencies. gRPC Java on Android depends on grpc-java, protobuf nano, okhttp -- Copy grpc-java .jar files to your_app_dir/app/libs/: - - grpc-java/core/build/libs/*.jar - - grpc-java/stub/build/libs/*.jar - - grpc-java/nano/build/libs/*.jar - - grpc-java/okhttp/build/libs/*.jar -- Copy or download other dependencies to your_app_dir/app/libs/: +**5 Add dependencies. gRPC Java on Android depends on grpc-java, protobuf nano, okhttp** +- Copy grpc-java .jar files to your_app_dir/app/libs +```sh +$ cp grpc-java/core/build/libs/*.jar your_app_dir/app/libs/ +$ cp grpc-java/stub/build/libs/*.jar your_app_dir/app/libs/ +$ cp grpc-java/nano/build/libs/*.jar your_app_dir/app/libs/ +$ cp grpc-java/okhttp/build/libs/*.jar your_app_dir/app/libs/ +``` +- Copy or download other dependencies to your_app_dir/app/libs/ - [Guava 18](http://search.maven.org/remotecontent?filepath=com/google/guava/guava/18.0/guava-18.0.jar) - [okhttp 2.2.0](http://repo1.maven.org/maven2/com/squareup/okhttp/okhttp/2.2.0/okhttp-2.2.0.jar) + - [okio](https://github.com/square/okio) - protobuf nano: ```sh -$ cp ~/.m2/repository/com/google/protobuf/nano/protobuf-javanano/2.6.2-pre/protobuf-javanano-2.6.2-pre.jar your_app_dir/app/libs/ +$ cp ~/.m2/repository/com/google/protobuf/nano/protobuf-javanano/3.0.0-alpha-2/protobuf-javanano-3.0.0-alpha-2.jar your_app_dir/app/libs/ ``` - Make sure your_app_dir/app/build.gradle contains: ```sh @@ -68,4 +71,4 @@ dependencies { } ``` -6 [Run your example app](https://developer.android.com/training/basics/firstapp/running-app.html) +**6 [Run your Helloworld Example app](https://developer.android.com/training/basics/firstapp/running-app.html)** diff --git a/java/javatutorial.md b/java/javatutorial.md index d4f3216d464..1a79f31dd53 100644 --- a/java/javatutorial.md +++ b/java/javatutorial.md @@ -106,7 +106,9 @@ For simplicity, we've provided a [Gradle build file](https://github.com/grpc/grp which actually runs: -[actual command] +```shell +protoc -I examples/src/main/proto -I examples/build/extracted-protos/main --java_out=examples/build/generated-sources/main --java_plugin_out=examples/build/generated-sources/main --plugin=protoc-gen-java_plugin=compiler/build/binaries/java_pluginExecutable/java_plugin examples/src/main/proto/route_guide.proto +``` Running this command generates the following files: - `RouteGuideOuterClass.java`, which contains all the protocol buffer code to populate, serialize, and retrieve our request and response message types @@ -330,6 +332,8 @@ First we need to create a gRPC *channel* for our stub, specifying the server add .build(); ``` +As with our server, we're using the [Netty](http://netty.io/) transport framework, so we use a `NettyChannelBuilder`. + Now we can use the channel to create our stubs using the `newStub` and `newBlockingStub` methods provided in the `RouteGuideGrpc` class we generated from our .proto. ```java @@ -343,17 +347,143 @@ Now let's look at how we call our service methods. #### Simple RPC +Calling the simple RPC `GetFeature` on the blocking stub is as straightforward as calling a local method. + +```java + Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build(); + Feature feature = blockingStub.getFeature(request); +``` +We create and populate a request protocol buffer object (in our case `Point`), pass it to the `getFeature()` method on our blocking stub, and get back a `Feature`. #### Server-side streaming RPC +Next, let's look at a server-side streaming call to `ListFeatures`, which returns a stream of geographical `Feature`s: +```java + Rectangle request = + Rectangle.newBuilder() + .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build()) + .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build(); + Iterator features = blockingStub.listFeatures(request); +``` + +As you can see, it's very similar to the simple RPC we just looked at, except instead of returning a single `Feature`, the method returns an `Iterator` that the client can use to read all the returned `Feature`s. #### Client-side streaming RPC +Now for something a little more complicated: the client-side streaming method `RecordRoute`, where we send a stream of `Point`s to the server and get back a single `RouteSummary`. For this method we need to use the asynchronous stub. If you've already read [Creating the server](#server) some of this may look very familiar - asynchronous streaming RPCs are implemented in a similar way on both sides. + +```java + public void recordRoute(List features, int numPoints) throws Exception { + info("*** RecordRoute"); + final SettableFuture finishFuture = SettableFuture.create(); + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onValue(RouteSummary summary) { + info("Finished trip with {0} points. Passed {1} features. " + + "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(), + summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime()); + } + + @Override + public void onError(Throwable t) { + finishFuture.setException(t); + } + + @Override + public void onCompleted() { + finishFuture.set(null); + } + }; + + StreamObserver requestObserver = asyncStub.recordRoute(responseObserver); + try { + // Send numPoints points randomly selected from the features list. + StringBuilder numMsg = new StringBuilder(); + Random rand = new Random(); + for (int i = 0; i < numPoints; ++i) { + int index = rand.nextInt(features.size()); + Point point = features.get(index).getLocation(); + info("Visiting point {0}, {1}", RouteGuideUtil.getLatitude(point), + RouteGuideUtil.getLongitude(point)); + requestObserver.onValue(point); + // Sleep for a bit before sending the next one. + Thread.sleep(rand.nextInt(1000) + 500); + if (finishFuture.isDone()) { + break; + } + } + info(numMsg.toString()); + requestObserver.onCompleted(); + + finishFuture.get(); + info("Finished RecordRoute"); + } catch (Exception e) { + requestObserver.onError(e); + logger.log(Level.WARNING, "RecordRoute Failed", e); + throw e; + } + } +``` + +As you can see, to call this method we need to create a `StreamObserver`, which implements a special interface for the server to call with its `RouteSummary` response. In our `StreamObserver` we: +- Override the `onValue()` method to print out the returned information when the server writes a `RouteSummary` to the message stream. +- Override the `onCompleted()` method (called when the *server* has completed the call on its side) to set a `SettableFuture` that we can check to see if the server has finished writing. + +We then pass the `StreamObserver` to the asynchronous stub's `recordRoute()` method and get back our own `StreamObserver` request observer to write our `Point`s to send to the server. Once we've finished writing points, we use the request observer's `onCompleted()` method to tell gRPC that we've finished writing on the client side. Once we're done, we check our `SettableFuture` to check that the server has completed on its side. #### Bidirectional streaming RPC +Finally, let's look at our bidirectional streaming RPC `RouteChat()`. + +```java + public void routeChat() throws Exception { + info("*** RoutChat"); + final SettableFuture finishFuture = SettableFuture.create(); + StreamObserver requestObserver = + asyncStub.routeChat(new StreamObserver() { + @Override + public void onValue(RouteNote note) { + info("Got message \"{0}\" at {1}, {2}", note.getMessage(), note.getLocation() + .getLatitude(), note.getLocation().getLongitude()); + } + + @Override + public void onError(Throwable t) { + finishFuture.setException(t); + } + + @Override + public void onCompleted() { + finishFuture.set(null); + } + }); + + try { + RouteNote[] requests = + {newNote("First message", 0, 0), newNote("Second message", 0, 1), + newNote("Third message", 1, 0), newNote("Fourth message", 1, 1)}; + + for (RouteNote request : requests) { + info("Sending message \"{0}\" at {1}, {2}", request.getMessage(), request.getLocation() + .getLatitude(), request.getLocation().getLongitude()); + requestObserver.onValue(request); + } + requestObserver.onCompleted(); + + finishFuture.get(); + info("Finished RouteChat"); + } catch (Exception t) { + requestObserver.onError(t); + logger.log(Level.WARNING, "RouteChat Failed", t); + throw t; + } + } +``` + +As with our client-side streaming example, we both get and return a `StreamObserver` response observer, except this time we send values via our method's response observer while the server is still writing messages to *their* message stream. The syntax for reading and writing here is exactly the same as for our client-streaming method. Although each side will always get the other's messages in the order they were written, both the client and server can read and write in any order — the streams operate completely independently. + ## Try it out! diff --git a/python/route_guide/.gitignore b/python/route_guide/.gitignore new file mode 100644 index 00000000000..0d20b6487c6 --- /dev/null +++ b/python/route_guide/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/python/route_guide/route_guide.proto b/python/route_guide/route_guide.proto new file mode 100644 index 00000000000..62567dfdd8a --- /dev/null +++ b/python/route_guide/route_guide.proto @@ -0,0 +1,119 @@ +// Copyright 2015, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; + +//TODO: see https://github.com/grpc/grpc/issues/814 +//package examples; + +// Interface exported by the server. +service RouteGuide { + // A simple RPC. + // + // Obtains the feature at a given position. + rpc GetFeature(Point) returns (Feature) {} + + // A server-to-client streaming RPC. + // + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} + + // A client-to-server streaming RPC. + // + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} + + // A Bidirectional streaming RPC. + // + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} +} + +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + optional int32 latitude = 1; + optional int32 longitude = 2; +} + +// A latitude-longitude rectangle, represented as two diagonally opposite +// points "lo" and "hi". +message Rectangle { + // One corner of the rectangle. + optional Point lo = 1; + + // The other corner of the rectangle. + optional Point hi = 2; +} + +// A feature names something at a given point. +// +// If a feature could not be named, the name is empty. +message Feature { + // The name of the feature. + optional string name = 1; + + // The point where the feature is detected. + optional Point location = 2; +} + +// A RouteNote is a message sent while at a given point. +message RouteNote { + // The location from which the message is sent. + optional Point location = 1; + + // The message to be sent. + optional string message = 2; +} + +// A RouteSummary is received in response to a RecordRoute rpc. +// +// It contains the number of individual points received, the number of +// detected features, and the total distance covered as the cumulative sum of +// the distance between each point. +message RouteSummary { + // The number of points received. + optional int32 point_count = 1; + + // The number of known features passed while traversing the route. + optional int32 feature_count = 2; + + // The distance covered in metres. + optional int32 distance = 3; + + // The duration of the traversal in seconds. + optional int32 elapsed_time = 4; +} diff --git a/python/route_guide/route_guide_client.py b/python/route_guide/route_guide_client.py new file mode 100755 index 00000000000..2ba6f05ba55 --- /dev/null +++ b/python/route_guide/route_guide_client.py @@ -0,0 +1,131 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The Python implementation of the gRPC route guide client.""" + +import random +import time + +import route_guide_pb2 +import route_guide_resources + +_TIMEOUT_SECONDS = 30 + + +def make_route_note(message, latitude, longitude): + route_note = route_guide_pb2.RouteNote(message=message) + route_note.location.latitude = latitude + route_note.location.longitude = longitude + return route_note + + +def guide_get_one_feature(stub, point): + feature = stub.GetFeature(point, _TIMEOUT_SECONDS) + if not feature.location: + print "Server returned incomplete feature" + return + + if feature.name: + print "Feature called %s at %s" % (feature.name, feature.location) + else: + print "Found no feature at %s" % feature.location + + +def guide_get_feature(stub): + guide_get_one_feature(stub, route_guide_pb2.Point(latitude=409146138, longitude=-746188906)) + guide_get_one_feature(stub, route_guide_pb2.Point(latitude=0, longitude=0)) + + +def guide_list_features(stub): + rect = route_guide_pb2.Rectangle() + rect.lo.latitude = 400000000 + rect.lo.longitude = -750000000 + rect.hi.latitude = 420000000 + rect.hi.longitude = -730000000 + print "Looking for features between 40, -75 and 42, -73" + + features = stub.ListFeatures(rect, _TIMEOUT_SECONDS) + + for feature in features: + print "Feature called %s at %s" % (feature.name, feature.location) + + +def generate_route(feature_list): + for _ in range(0, 10): + random_feature = feature_list[random.randint(0, len(feature_list) - 1)] + print "Visiting point %s" % random_feature.location + yield random_feature.location + time.sleep(random.uniform(0.5, 1.5)) + + +def guide_record_route(stub): + feature_list = route_guide_resources.read_route_guide_database() + + route_iter = generate_route(feature_list) + route_summary = stub.RecordRoute(route_iter, _TIMEOUT_SECONDS) + print "Finished trip with %s points " % route_summary.point_count + print "Passed %s features " % route_summary.feature_count + print "Travelled %s meters " % route_summary.distance + print "It took %s seconds " % route_summary.elapsed_time + + +def generate_messages(): + messages = [ + make_route_note("First message", 0, 0), + make_route_note("Second message", 0, 1), + make_route_note("Third message", 1, 0), + make_route_note("Fourth message", 0, 0), + make_route_note("Fifth message", 1, 0), + ] + for msg in messages: + print "Sending %s at %s" % (msg.message, msg.location) + yield msg + time.sleep(random.uniform(0.5, 1.0)) + + +def guide_route_chat(stub): + responses = stub.RouteChat(generate_messages(), _TIMEOUT_SECONDS) + for response in responses: + print "Received message %s at %s" % (response.message, response.location) + + +def run(): + with route_guide_pb2.early_adopter_create_RouteGuide_stub('localhost', 50051) as stub: + print "-------------- GetFeature --------------" + guide_get_feature(stub); + print "-------------- ListFeatures --------------" + guide_list_features(stub); + print "-------------- RecordRoute --------------" + guide_record_route(stub); + print "-------------- RouteChat --------------" + guide_route_chat(stub); + + +if __name__ == '__main__': + run() diff --git a/python/route_guide/route_guide_db.json b/python/route_guide/route_guide_db.json new file mode 100644 index 00000000000..9d6a980ab7d --- /dev/null +++ b/python/route_guide/route_guide_db.json @@ -0,0 +1,601 @@ +[{ + "location": { + "latitude": 407838351, + "longitude": -746143763 + }, + "name": "Patriots Path, Mendham, NJ 07945, USA" +}, { + "location": { + "latitude": 408122808, + "longitude": -743999179 + }, + "name": "101 New Jersey 10, Whippany, NJ 07981, USA" +}, { + "location": { + "latitude": 413628156, + "longitude": -749015468 + }, + "name": "U.S. 6, Shohola, PA 18458, USA" +}, { + "location": { + "latitude": 419999544, + "longitude": -740371136 + }, + "name": "5 Conners Road, Kingston, NY 12401, USA" +}, { + "location": { + "latitude": 414008389, + "longitude": -743951297 + }, + "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" +}, { + "location": { + "latitude": 419611318, + "longitude": -746524769 + }, + "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" +}, { + "location": { + "latitude": 406109563, + "longitude": -742186778 + }, + "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" +}, { + "location": { + "latitude": 416802456, + "longitude": -742370183 + }, + "name": "352 South Mountain Road, Wallkill, NY 12589, USA" +}, { + "location": { + "latitude": 412950425, + "longitude": -741077389 + }, + "name": "Bailey Turn Road, Harriman, NY 10926, USA" +}, { + "location": { + "latitude": 412144655, + "longitude": -743949739 + }, + "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" +}, { + "location": { + "latitude": 415736605, + "longitude": -742847522 + }, + "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" +}, { + "location": { + "latitude": 413843930, + "longitude": -740501726 + }, + "name": "162 Merrill Road, Highland Mills, NY 10930, USA" +}, { + "location": { + "latitude": 410873075, + "longitude": -744459023 + }, + "name": "Clinton Road, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 412346009, + "longitude": -744026814 + }, + "name": "16 Old Brook Lane, Warwick, NY 10990, USA" +}, { + "location": { + "latitude": 402948455, + "longitude": -747903913 + }, + "name": "3 Drake Lane, Pennington, NJ 08534, USA" +}, { + "location": { + "latitude": 406337092, + "longitude": -740122226 + }, + "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" +}, { + "location": { + "latitude": 406421967, + "longitude": -747727624 + }, + "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" +}, { + "location": { + "latitude": 416318082, + "longitude": -749677716 + }, + "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" +}, { + "location": { + "latitude": 415301720, + "longitude": -748416257 + }, + "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" +}, { + "location": { + "latitude": 402647019, + "longitude": -747071791 + }, + "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" +}, { + "location": { + "latitude": 412567807, + "longitude": -741058078 + }, + "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" +}, { + "location": { + "latitude": 416855156, + "longitude": -744420597 + }, + "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" +}, { + "location": { + "latitude": 404663628, + "longitude": -744820157 + }, + "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" +}, { + "location": { + "latitude": 407113723, + "longitude": -749746483 + }, + "name": "" +}, { + "location": { + "latitude": 402133926, + "longitude": -743613249 + }, + "name": "" +}, { + "location": { + "latitude": 400273442, + "longitude": -741220915 + }, + "name": "" +}, { + "location": { + "latitude": 411236786, + "longitude": -744070769 + }, + "name": "" +}, { + "location": { + "latitude": 411633782, + "longitude": -746784970 + }, + "name": "211-225 Plains Road, Augusta, NJ 07822, USA" +}, { + "location": { + "latitude": 415830701, + "longitude": -742952812 + }, + "name": "" +}, { + "location": { + "latitude": 413447164, + "longitude": -748712898 + }, + "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" +}, { + "location": { + "latitude": 405047245, + "longitude": -749800722 + }, + "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" +}, { + "location": { + "latitude": 418858923, + "longitude": -746156790 + }, + "name": "" +}, { + "location": { + "latitude": 417951888, + "longitude": -748484944 + }, + "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" +}, { + "location": { + "latitude": 407033786, + "longitude": -743977337 + }, + "name": "26 East 3rd Street, New Providence, NJ 07974, USA" +}, { + "location": { + "latitude": 417548014, + "longitude": -740075041 + }, + "name": "" +}, { + "location": { + "latitude": 410395868, + "longitude": -744972325 + }, + "name": "" +}, { + "location": { + "latitude": 404615353, + "longitude": -745129803 + }, + "name": "" +}, { + "location": { + "latitude": 406589790, + "longitude": -743560121 + }, + "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" +}, { + "location": { + "latitude": 414653148, + "longitude": -740477477 + }, + "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" +}, { + "location": { + "latitude": 405957808, + "longitude": -743255336 + }, + "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" +}, { + "location": { + "latitude": 411733589, + "longitude": -741648093 + }, + "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" +}, { + "location": { + "latitude": 412676291, + "longitude": -742606606 + }, + "name": "1270 Lakes Road, Monroe, NY 10950, USA" +}, { + "location": { + "latitude": 409224445, + "longitude": -748286738 + }, + "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" +}, { + "location": { + "latitude": 406523420, + "longitude": -742135517 + }, + "name": "652 Garden Street, Elizabeth, NJ 07202, USA" +}, { + "location": { + "latitude": 401827388, + "longitude": -740294537 + }, + "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" +}, { + "location": { + "latitude": 410564152, + "longitude": -743685054 + }, + "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" +}, { + "location": { + "latitude": 408472324, + "longitude": -740726046 + }, + "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" +}, { + "location": { + "latitude": 412452168, + "longitude": -740214052 + }, + "name": "5 White Oak Lane, Stony Point, NY 10980, USA" +}, { + "location": { + "latitude": 409146138, + "longitude": -746188906 + }, + "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" +}, { + "location": { + "latitude": 404701380, + "longitude": -744781745 + }, + "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 409642566, + "longitude": -746017679 + }, + "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" +}, { + "location": { + "latitude": 408031728, + "longitude": -748645385 + }, + "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" +}, { + "location": { + "latitude": 413700272, + "longitude": -742135189 + }, + "name": "367 Prospect Road, Chester, NY 10918, USA" +}, { + "location": { + "latitude": 404310607, + "longitude": -740282632 + }, + "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" +}, { + "location": { + "latitude": 409319800, + "longitude": -746201391 + }, + "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" +}, { + "location": { + "latitude": 406685311, + "longitude": -742108603 + }, + "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" +}, { + "location": { + "latitude": 419018117, + "longitude": -749142781 + }, + "name": "43 Dreher Road, Roscoe, NY 12776, USA" +}, { + "location": { + "latitude": 412856162, + "longitude": -745148837 + }, + "name": "Swan Street, Pine Island, NY 10969, USA" +}, { + "location": { + "latitude": 416560744, + "longitude": -746721964 + }, + "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" +}, { + "location": { + "latitude": 405314270, + "longitude": -749836354 + }, + "name": "" +}, { + "location": { + "latitude": 414219548, + "longitude": -743327440 + }, + "name": "" +}, { + "location": { + "latitude": 415534177, + "longitude": -742900616 + }, + "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" +}, { + "location": { + "latitude": 406898530, + "longitude": -749127080 + }, + "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" +}, { + "location": { + "latitude": 407586880, + "longitude": -741670168 + }, + "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" +}, { + "location": { + "latitude": 400106455, + "longitude": -742870190 + }, + "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" +}, { + "location": { + "latitude": 400066188, + "longitude": -746793294 + }, + "name": "" +}, { + "location": { + "latitude": 418803880, + "longitude": -744102673 + }, + "name": "40 Mountain Road, Napanoch, NY 12458, USA" +}, { + "location": { + "latitude": 414204288, + "longitude": -747895140 + }, + "name": "" +}, { + "location": { + "latitude": 414777405, + "longitude": -740615601 + }, + "name": "" +}, { + "location": { + "latitude": 415464475, + "longitude": -747175374 + }, + "name": "48 North Road, Forestburgh, NY 12777, USA" +}, { + "location": { + "latitude": 404062378, + "longitude": -746376177 + }, + "name": "" +}, { + "location": { + "latitude": 405688272, + "longitude": -749285130 + }, + "name": "" +}, { + "location": { + "latitude": 400342070, + "longitude": -748788996 + }, + "name": "" +}, { + "location": { + "latitude": 401809022, + "longitude": -744157964 + }, + "name": "" +}, { + "location": { + "latitude": 404226644, + "longitude": -740517141 + }, + "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" +}, { + "location": { + "latitude": 410322033, + "longitude": -747871659 + }, + "name": "" +}, { + "location": { + "latitude": 407100674, + "longitude": -747742727 + }, + "name": "" +}, { + "location": { + "latitude": 418811433, + "longitude": -741718005 + }, + "name": "213 Bush Road, Stone Ridge, NY 12484, USA" +}, { + "location": { + "latitude": 415034302, + "longitude": -743850945 + }, + "name": "" +}, { + "location": { + "latitude": 411349992, + "longitude": -743694161 + }, + "name": "" +}, { + "location": { + "latitude": 404839914, + "longitude": -744759616 + }, + "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" +}, { + "location": { + "latitude": 414638017, + "longitude": -745957854 + }, + "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" +}, { + "location": { + "latitude": 412127800, + "longitude": -740173578 + }, + "name": "" +}, { + "location": { + "latitude": 401263460, + "longitude": -747964303 + }, + "name": "" +}, { + "location": { + "latitude": 412843391, + "longitude": -749086026 + }, + "name": "" +}, { + "location": { + "latitude": 418512773, + "longitude": -743067823 + }, + "name": "" +}, { + "location": { + "latitude": 404318328, + "longitude": -740835638 + }, + "name": "42-102 Main Street, Belford, NJ 07718, USA" +}, { + "location": { + "latitude": 419020746, + "longitude": -741172328 + }, + "name": "" +}, { + "location": { + "latitude": 404080723, + "longitude": -746119569 + }, + "name": "" +}, { + "location": { + "latitude": 401012643, + "longitude": -744035134 + }, + "name": "" +}, { + "location": { + "latitude": 404306372, + "longitude": -741079661 + }, + "name": "" +}, { + "location": { + "latitude": 403966326, + "longitude": -748519297 + }, + "name": "" +}, { + "location": { + "latitude": 405002031, + "longitude": -748407866 + }, + "name": "" +}, { + "location": { + "latitude": 409532885, + "longitude": -742200683 + }, + "name": "" +}, { + "location": { + "latitude": 416851321, + "longitude": -742674555 + }, + "name": "" +}, { + "location": { + "latitude": 406411633, + "longitude": -741722051 + }, + "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" +}, { + "location": { + "latitude": 413069058, + "longitude": -744597778 + }, + "name": "261 Van Sickle Road, Goshen, NY 10924, USA" +}, { + "location": { + "latitude": 418465462, + "longitude": -746859398 + }, + "name": "" +}, { + "location": { + "latitude": 411733222, + "longitude": -744228360 + }, + "name": "" +}, { + "location": { + "latitude": 410248224, + "longitude": -747127767 + }, + "name": "3 Hasta Way, Newton, NJ 07860, USA" +}] diff --git a/python/route_guide/route_guide_pb2.py b/python/route_guide/route_guide_pb2.py new file mode 100644 index 00000000000..2a4532bb750 --- /dev/null +++ b/python/route_guide/route_guide_pb2.py @@ -0,0 +1,370 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: route_guide.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='route_guide.proto', + package='', + serialized_pb=_b('\n\x11route_guide.proto\",\n\x05Point\x12\x10\n\x08latitude\x18\x01 \x01(\x05\x12\x11\n\tlongitude\x18\x02 \x01(\x05\"3\n\tRectangle\x12\x12\n\x02lo\x18\x01 \x01(\x0b\x32\x06.Point\x12\x12\n\x02hi\x18\x02 \x01(\x0b\x32\x06.Point\"1\n\x07\x46\x65\x61ture\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x18\n\x08location\x18\x02 \x01(\x0b\x32\x06.Point\"6\n\tRouteNote\x12\x18\n\x08location\x18\x01 \x01(\x0b\x32\x06.Point\x12\x0f\n\x07message\x18\x02 \x01(\t\"b\n\x0cRouteSummary\x12\x13\n\x0bpoint_count\x18\x01 \x01(\x05\x12\x15\n\rfeature_count\x18\x02 \x01(\x05\x12\x10\n\x08\x64istance\x18\x03 \x01(\x05\x12\x14\n\x0c\x65lapsed_time\x18\x04 \x01(\x05\x32\xad\x01\n\nRouteGuide\x12 \n\nGetFeature\x12\x06.Point\x1a\x08.Feature\"\x00\x12(\n\x0cListFeatures\x12\n.Rectangle\x1a\x08.Feature\"\x00\x30\x01\x12(\n\x0bRecordRoute\x12\x06.Point\x1a\r.RouteSummary\"\x00(\x01\x12)\n\tRouteChat\x12\n.RouteNote\x1a\n.RouteNote\"\x00(\x01\x30\x01') +) +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + + + + +_POINT = _descriptor.Descriptor( + name='Point', + full_name='Point', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='latitude', full_name='Point.latitude', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='longitude', full_name='Point.longitude', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=21, + serialized_end=65, +) + + +_RECTANGLE = _descriptor.Descriptor( + name='Rectangle', + full_name='Rectangle', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='lo', full_name='Rectangle.lo', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='hi', full_name='Rectangle.hi', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=67, + serialized_end=118, +) + + +_FEATURE = _descriptor.Descriptor( + name='Feature', + full_name='Feature', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='Feature.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='location', full_name='Feature.location', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=120, + serialized_end=169, +) + + +_ROUTENOTE = _descriptor.Descriptor( + name='RouteNote', + full_name='RouteNote', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='location', full_name='RouteNote.location', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='message', full_name='RouteNote.message', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=171, + serialized_end=225, +) + + +_ROUTESUMMARY = _descriptor.Descriptor( + name='RouteSummary', + full_name='RouteSummary', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='point_count', full_name='RouteSummary.point_count', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='feature_count', full_name='RouteSummary.feature_count', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='distance', full_name='RouteSummary.distance', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='elapsed_time', full_name='RouteSummary.elapsed_time', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=227, + serialized_end=325, +) + +_RECTANGLE.fields_by_name['lo'].message_type = _POINT +_RECTANGLE.fields_by_name['hi'].message_type = _POINT +_FEATURE.fields_by_name['location'].message_type = _POINT +_ROUTENOTE.fields_by_name['location'].message_type = _POINT +DESCRIPTOR.message_types_by_name['Point'] = _POINT +DESCRIPTOR.message_types_by_name['Rectangle'] = _RECTANGLE +DESCRIPTOR.message_types_by_name['Feature'] = _FEATURE +DESCRIPTOR.message_types_by_name['RouteNote'] = _ROUTENOTE +DESCRIPTOR.message_types_by_name['RouteSummary'] = _ROUTESUMMARY + +Point = _reflection.GeneratedProtocolMessageType('Point', (_message.Message,), dict( + DESCRIPTOR = _POINT, + __module__ = 'route_guide_pb2' + # @@protoc_insertion_point(class_scope:Point) + )) +_sym_db.RegisterMessage(Point) + +Rectangle = _reflection.GeneratedProtocolMessageType('Rectangle', (_message.Message,), dict( + DESCRIPTOR = _RECTANGLE, + __module__ = 'route_guide_pb2' + # @@protoc_insertion_point(class_scope:Rectangle) + )) +_sym_db.RegisterMessage(Rectangle) + +Feature = _reflection.GeneratedProtocolMessageType('Feature', (_message.Message,), dict( + DESCRIPTOR = _FEATURE, + __module__ = 'route_guide_pb2' + # @@protoc_insertion_point(class_scope:Feature) + )) +_sym_db.RegisterMessage(Feature) + +RouteNote = _reflection.GeneratedProtocolMessageType('RouteNote', (_message.Message,), dict( + DESCRIPTOR = _ROUTENOTE, + __module__ = 'route_guide_pb2' + # @@protoc_insertion_point(class_scope:RouteNote) + )) +_sym_db.RegisterMessage(RouteNote) + +RouteSummary = _reflection.GeneratedProtocolMessageType('RouteSummary', (_message.Message,), dict( + DESCRIPTOR = _ROUTESUMMARY, + __module__ = 'route_guide_pb2' + # @@protoc_insertion_point(class_scope:RouteSummary) + )) +_sym_db.RegisterMessage(RouteSummary) + + +import abc +from grpc._adapter import fore +from grpc._adapter import rear +from grpc.framework.assembly import implementations +from grpc.framework.assembly import utilities +class EarlyAdopterRouteGuideServicer(object): + """""" + __metaclass__ = abc.ABCMeta + @abc.abstractmethod + def GetFeature(self, request): + raise NotImplementedError() + @abc.abstractmethod + def ListFeatures(self, request): + raise NotImplementedError() + @abc.abstractmethod + def RecordRoute(self, request_iterator): + raise NotImplementedError() + @abc.abstractmethod + def RouteChat(self, request_iterator): + raise NotImplementedError() +class EarlyAdopterRouteGuideServer(object): + """""" + __metaclass__ = abc.ABCMeta + @abc.abstractmethod + def start(self): + raise NotImplementedError() + @abc.abstractmethod + def stop(self): + raise NotImplementedError() +class EarlyAdopterRouteGuideStub(object): + """""" + __metaclass__ = abc.ABCMeta + @abc.abstractmethod + def GetFeature(self, request): + raise NotImplementedError() + GetFeature.async = None + @abc.abstractmethod + def ListFeatures(self, request): + raise NotImplementedError() + ListFeatures.async = None + @abc.abstractmethod + def RecordRoute(self, request_iterator): + raise NotImplementedError() + RecordRoute.async = None + @abc.abstractmethod + def RouteChat(self, request_iterator): + raise NotImplementedError() + RouteChat.async = None +def early_adopter_create_RouteGuide_server(servicer, port, root_certificates, key_chain_pairs): + method_implementations = { + "GetFeature": utilities.unary_unary_inline(servicer.GetFeature), + "ListFeatures": utilities.unary_stream_inline(servicer.ListFeatures), + "RecordRoute": utilities.stream_unary_inline(servicer.RecordRoute), + "RouteChat": utilities.stream_stream_inline(servicer.RouteChat), + } + import route_guide_pb2 + import route_guide_pb2 + import route_guide_pb2 + import route_guide_pb2 + request_deserializers = { + "GetFeature": route_guide_pb2.Point.FromString, + "ListFeatures": route_guide_pb2.Rectangle.FromString, + "RecordRoute": route_guide_pb2.Point.FromString, + "RouteChat": route_guide_pb2.RouteNote.FromString, + } + response_serializers = { + "GetFeature": lambda x: x.SerializeToString(), + "ListFeatures": lambda x: x.SerializeToString(), + "RecordRoute": lambda x: x.SerializeToString(), + "RouteChat": lambda x: x.SerializeToString(), + } + link = fore.activated_fore_link(port, request_deserializers, response_serializers, root_certificates, key_chain_pairs) + return implementations.assemble_service(method_implementations, link) +def early_adopter_create_RouteGuide_stub(host, port): + method_implementations = { + "GetFeature": utilities.unary_unary_inline(None), + "ListFeatures": utilities.unary_stream_inline(None), + "RecordRoute": utilities.stream_unary_inline(None), + "RouteChat": utilities.stream_stream_inline(None), + } + import route_guide_pb2 + import route_guide_pb2 + import route_guide_pb2 + import route_guide_pb2 + response_deserializers = { + "GetFeature": route_guide_pb2.Feature.FromString, + "ListFeatures": route_guide_pb2.Feature.FromString, + "RecordRoute": route_guide_pb2.RouteSummary.FromString, + "RouteChat": route_guide_pb2.RouteNote.FromString, + } + request_serializers = { + "GetFeature": lambda x: x.SerializeToString(), + "ListFeatures": lambda x: x.SerializeToString(), + "RecordRoute": lambda x: x.SerializeToString(), + "RouteChat": lambda x: x.SerializeToString(), + } + link = rear.activated_rear_link(host, port, request_serializers, response_deserializers) + return implementations.assemble_dynamic_inline_stub(method_implementations, link) +# @@protoc_insertion_point(module_scope) diff --git a/python/route_guide/route_guide_resources.py b/python/route_guide/route_guide_resources.py new file mode 100755 index 00000000000..30c7711019f --- /dev/null +++ b/python/route_guide/route_guide_resources.py @@ -0,0 +1,53 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Common resources used in the gRPC route guide example.""" + +import json + +import route_guide_pb2 + + +def read_route_guide_database(): + """Reads the route guide database. + + Returns: + The full contents of the route guide database as a sequence of + route_guide_pb2.Features. + """ + feature_list = [] + with open("route_guide_db.json") as route_guide_db_file: + for item in json.load(route_guide_db_file): + feature = route_guide_pb2.Feature( + name=item["name"], + location=route_guide_pb2.Point( + latitude=item["location"]["latitude"], + longitude=item["location"]["longitude"])) + feature_list.append(feature) + return feature_list diff --git a/python/route_guide/route_guide_server.py b/python/route_guide/route_guide_server.py new file mode 100644 index 00000000000..eff167ec3da --- /dev/null +++ b/python/route_guide/route_guide_server.py @@ -0,0 +1,138 @@ +# Copyright 2015, Google Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""The Python implementation of the gRPC route guide server.""" + +import time +import math + +import route_guide_pb2 +import route_guide_resources + +_ONE_DAY_IN_SECONDS = 60 * 60 * 24 + + +def get_feature(feature_db, point): + """Returns Feature at given location or None.""" + for feature in feature_db: + if feature.location == point: + return feature + return None + + +def get_distance(start, end): + """Distance between two points.""" + coord_factor = 10000000.0 + lat_1 = start.latitude / coord_factor + lat_2 = end.latitude / coord_factor + lon_1 = start.latitude / coord_factor + lon_2 = end.longitude / coord_factor + lat_rad_1 = math.radians(lat_1) + lat_rad_2 = math.radians(lat_2) + delta_lat_rad = math.radians(lat_2 - lat_1) + delta_lon_rad = math.radians(lon_2 - lon_1) + + a = (pow(math.sin(delta_lat_rad / 2), 2) + + (math.cos(lat_rad_1) * math.cos(lat_rad_2) * + pow(math.sin(delta_lon_rad / 2), 2))) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + R = 6371000; # metres + return R * c; + +class RouteGuideServicer(route_guide_pb2.EarlyAdopterRouteGuideServicer): + """Provides methods that implement functionality of route guide server.""" + + def __init__(self): + self.db = route_guide_resources.read_route_guide_database() + + def GetFeature(self, request, context): + feature = get_feature(self.db, request) + if not feature: + feature = route_guide_pb2.Feature( + name="", + location=route_guide_pb2.Point( + latitude=request.latitude, longitude=request.longitude)) + return feature + + def ListFeatures(self, request, context): + lo = request.lo + hi = request.hi + left = min(lo.longitude, hi.longitude) + right = max(lo.longitude, hi.longitude) + top = max(lo.latitude, hi.latitude) + bottom = min(lo.latitude, hi.latitude) + for feature in self.db: + if (feature.location.longitude >= left and + feature.location.longitude <= right and + feature.location.latitude >= bottom and + feature.location.latitude <= top): + yield feature + + def RecordRoute(self, request_iterator, context): + point_count = 0 + feature_count = 0 + distance = 0.0 + prev_point = None + + start_time = time.time() + for point in request_iterator: + point_count += 1 + if get_feature(self.db, point): + feature_count += 1 + if prev_point: + distance += get_distance(prev_point, point) + prev_point = point + + elapsed_time = time.time() - start_time + return route_guide_pb2.RouteSummary(point_count=point_count, + feature_count=feature_count, + distance=int(distance), + elapsed_time=int(elapsed_time)) + + def RouteChat(self, request_iterator, context): + prev_notes = [] + for new_note in request_iterator: + for prev_note in prev_notes: + if prev_note.location == new_note.location: + yield prev_note + prev_notes.append(new_note) + + +def serve(): + server = route_guide_pb2.early_adopter_create_RouteGuide_server( + RouteGuideServicer(), 50051, None, None) + server.start() + try: + while True: + time.sleep(_ONE_DAY_IN_SECONDS) + except KeyboardInterrupt: + server.stop() + +if __name__ == '__main__': + serve() diff --git a/python/route_guide/run_client.sh b/python/route_guide/run_client.sh new file mode 100755 index 00000000000..d2552c2858c --- /dev/null +++ b/python/route_guide/run_client.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# This is where you have cloned out the https://github.com/grpc/grpc repository +# And built gRPC Python. +# ADJUST THIS PATH TO WHERE YOUR ACTUAL LOCATION IS +GRPC_ROOT=~/github/grpc + +$GRPC_ROOT/python2.7_virtual_environment/bin/python -B route_guide_client.py diff --git a/python/route_guide/run_codegen.sh b/python/route_guide/run_codegen.sh new file mode 100755 index 00000000000..689e0978de6 --- /dev/null +++ b/python/route_guide/run_codegen.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# Runs the protoc with gRPC plugin to generate protocol messages and gRPC stubs. +protoc -I . --python_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_python_plugin` route_guide.proto diff --git a/python/route_guide/run_server.sh b/python/route_guide/run_server.sh new file mode 100755 index 00000000000..8f759250c85 --- /dev/null +++ b/python/route_guide/run_server.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# This is where you have cloned out the https://github.com/grpc/grpc repository +# And built gRPC Python. +# ADJUST THIS PATH TO WHERE YOUR ACTUAL LOCATION IS +GRPC_ROOT=~/github/grpc + +$GRPC_ROOT/python2.7_virtual_environment/bin/python -B route_guide_server.py diff --git a/ruby/README.md b/ruby/README.md index c5902a3039b..57b0f45e63b 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -9,24 +9,28 @@ PREREQUISITES ------------- This requires Ruby 2.1, as the gRPC API surface uses keyword args. - If you don't have that installed locally, you can use [RVM](https://www.rvm.io/) to use Ruby 2.1 for testing without upgrading the version of Ruby on your whole system. +RVM is also useful if you don't have the necessary privileges to update your system's Ruby. ```sh -$ command curl -sSL https://rvm.io/mpapis.asc | gpg --import - +$ # RVM installation as specified at https://rvm.io/rvm/install +$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 $ \curl -sSL https://get.rvm.io | bash -s stable --ruby=ruby-2.1 $ $ # follow the instructions to ensure that your're using the latest stable version of Ruby $ # and that the rvm command is installed ``` -- Make sure your run `source $HOME/.rvm/scripts/rvm` as instructed to complete the set up of RVM +- *N.B* Make sure your run `source $HOME/.rvm/scripts/rvm` as instructed to complete the set-up of RVM. INSTALL ------- - Clone this repository. - Follow the instructions in [INSTALL](https://github.com/grpc/grpc/blob/master/INSTALL) to install the gRPC C core. -- *Temporary* Install the full gRPC distribution from source on your local machine and update path: in [Gemfile](https://github.com/grpc/grpc-common/blob/master/ruby/Gemfile) to refer src/ruby within it. - - this is necessary until the gRPC ruby gem is published +- *Temporary* + - Install the full gRPC distribution from source on your local machine + - Build gRPC Ruby as described in [installing from source](https://github.com/grpc/grpc/blob/master/src/ruby/README.md#installing-from-source) + - update `path:` in [Gemfile](https://github.com/grpc/grpc-common/blob/master/ruby/Gemfile) to refer to src/ruby within the gRPC directory + - N.B: these steps are necessary until the gRPC ruby gem is published - Use bundler to install ```sh $ # from this directory @@ -47,3 +51,8 @@ $ bundle exec ./greeter_server.rb & $ # from this directory $ bundle exec ./greeter_client.rb ``` + +Tutorial +-------- + +You can find a more detailed tutorial in [gRPC Basics: Ruby](https://github.com/grpc/grpc-common/blob/master/ruby/route_guide/README.md) diff --git a/ruby/route_guide/README.md b/ruby/route_guide/README.md new file mode 100644 index 00000000000..71003b52eb2 --- /dev/null +++ b/ruby/route_guide/README.md @@ -0,0 +1,285 @@ +#gRPC Basics: Ruby + +This tutorial provides a basic Ruby programmer's introduction to working with gRPC. By walking through this example you'll learn how to: + +- Define a service in a .proto file. +- Generate server and client code using the protocol buffer compiler. +- Use the Ruby gRPC API to write a simple client and server for your service. + +It assumes that you have read the [Getting started](https://github.com/grpc/grpc-common) guide and are familiar with [protocol buffers] (https://developers.google.com/protocol-buffers/docs/overview). Note that the example in this tutorial uses the proto3 version of the protocol buffers language, which is currently in alpha release: you can see the [release notes](https://github.com/google/protobuf/releases) for the new version in the protocol buffers Github repository. + +This isn't a comprehensive guide to using gRPC in Ruby: more reference documentation is coming soon. + +## Why use gRPC? + +Our example is a simple route mapping application that lets clients get 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 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 + +The example code for our tutorial is in [grpc/grpc-common/ruby/route_guide](https://github.com/grpc/grpc-common/tree/master/ruby/route_guide). To download the example, clone the `grpc-common` repository by running the following command: +```shell +$ git clone https://github.com/google/grpc-common.git +``` + +Then change your current directory to `grpc-common/ruby/route_guide`: +```shell +$ cd grpc-common/ruby/route_guide +``` + +You also should have the relevant tools installed to generate the server and client interface code - if you don't already, follow the setup instructions in [the Ruby quick start guide](https://github.com/grpc/grpc-common/tree/master/ruby). + + +## Defining the service + +Our first step (as you'll know from [Getting started](https://github.com/grpc/grpc-common)) is to define the gRPC *service* and the method *request* and *response* types using [protocol buffers] (https://developers.google.com/protocol-buffers/docs/overview). You can see the complete .proto file in [`grpc-common/protos/route_guide.proto`](https://github.com/grpc/grpc-common/blob/master/protos/route_guide.proto). + +To define a service, you specify a named `service` in your .proto file: + +```protobuf +service RouteGuide { + ... +} +``` + +Then you define `rpc` methods inside your service definition, specifying their request and response types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide` service: + +- A *simple RPC* where the client sends a request to the server using the stub and waits for a response to come back, just like a normal function call. +```protobuf + // Obtains the feature at a given position. + rpc GetFeature(Point) returns (Feature) {} +``` + +- A *server-side streaming RPC* where the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. As you can see in our example, you specify a server-side streaming method by placing the `stream` keyword before the *response* type. +```protobuf + // Obtains the Features available within the given Rectangle. Results are + // streamed rather than returned at once (e.g. in a response message with a + // repeated field), as the rectangle may cover a large area and contain a + // huge number of features. + rpc ListFeatures(Rectangle) returns (stream Feature) {} +``` + +- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them all and return its response. You specify a server-side streaming method by placing the `stream` keyword before the *request* type. +```protobuf + // Accepts a stream of Points on a route being traversed, returning a + // RouteSummary when traversal is completed. + rpc RecordRoute(stream Point) returns (RouteSummary) {} +``` + +- A *bidirectional streaming RPC* where both sides send a sequence of messages using a read-write stream. The two streams operate independently, so clients and servers can read and write in whatever order they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. You specify this type of method by placing the `stream` keyword before both the request and the response. +```protobuf + // Accepts a stream of RouteNotes sent while a route is being traversed, + // while receiving other RouteNotes (e.g. from other users). + rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} +``` + +Our .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 `Point` message type: +```protobuf +// Points are represented as latitude-longitude pairs in the E7 representation +// (degrees multiplied by 10**7 and rounded to the nearest integer). +// Latitudes should be in the range +/- 90 degrees and longitude should be in +// the range +/- 180 degrees (inclusive). +message Point { + int32 latitude = 1; + int32 longitude = 2; +} +``` + + +## Generating client and server code + +Next we need to generate the gRPC client and server interfaces from our .proto service definition. We do this using the protocol buffer compiler `protoc` with a special gRPC Ruby plugin. + +If you want to run this yourself, make sure you've installed protoc and followed the gRPC Ruby plugin [installation instructions](https://github.com/grpc/grpc/blob/master/INSTALL) first): + +Once that's done, the following command can be used to generate the ruby code. + +```shell +$ protoc -I ../../protos --ruby_out=lib --grpc_out=lib --plugin=protoc-gen-grpc=`which grpc_ruby_plugin` ../../protos/route_guide.proto +``` + +Running this command regenerates the following files in the lib directory: +- `lib/route_guide.pb` defines a module `Examples::RouteGuide` + - This contain all the protocol buffer code to populate, serialize, and retrieve our request and response message types +- `lib/route_guide_services.pb`, extends `Examples::RouteGuide` with stub and service classes + - a class `Service` for use as a base class when defining RouteGuide service implementations + - a class `Stub` that can be used to access remote RouteGuide instances + + + +## Creating the server + +First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC clients, you can skip this section and go straight to [Creating the client](#client) (though you might find it interesting anyway!). + +There are two parts to making our `RouteGuide` service do its job: +- Implementing the service interface generated from our service definition: doing the actual "work" of our service. +- Running a gRPC server to listen for requests from clients and return the service responses. + +You can find our example `RouteGuide` server in [grpc-common/ruby/route_guide/route_guide_server.rb](https://github.com/grpc/grpc-common/blob/master/ruby/route_guide/route_guide_server.rb). Let's take a closer look at how it works. + +### Implementing RouteGuide + +As you can see, our server has a `ServerImpl` class that extends the generated `RouteGuide::Service`: + +```ruby +# ServerImpl provides an implementation of the RouteGuide service. +class ServerImpl < RouteGuide::Service +``` + +`ServerImpl` implements all our service methods. Let's look at the simplest type first, `GetFeature`, which just gets a `Point` from the client and returns the corresponding feature information from its database in a `Feature`. + +```ruby + def get_feature(point, _call) + name = @feature_db[{ + 'longitude' => point.longitude, + 'latitude' => point.latitude }] || '' + Feature.new(location: point, name: name) + end +``` + +The method is passed a _call for the RPC, the client's `Point` protocol buffer request, and returns a `Feature` protocol buffer. In the method we create the `Feature` with the appropriate information, and then `return` it. + +Now let's look at something a bit more complicated - a streaming RPC. `ListFeatures` is a server-side streaming RPC, so we need to send back multiple `Feature`s to our client. + +```ruby +# in ServerImpl + + def list_features(rectangle, _call) + RectangleEnum.new(@feature_db, rectangle).each + end +``` + +As you can see, here the request object is a `Rectangle` in which our client wants to find `Feature`s, but instead of returning a simple response we need to return an [Enumerator](http://ruby-doc.org//core-2.2.0/Enumerator.html) that yields the responses. In the method, we use a helper class `RectangleEnum`, to act as an Enumerator implementation. + +Similarly, the client-side streaming method `record_route` uses an [Enumerable](http://ruby-doc.org//core-2.2.0/Enumerable.html), but here it's obtained from the call object, which we've ignored in the earlier examples. `call.each_remote_read` yields each message sent by the client in turn. + +```ruby + call.each_remote_read do |point| + ... + end +``` +Finally, let's look at our bidirectional streaming RPC `route_chat`. + +```ruby + def route_chat(notes) + q = EnumeratorQueue.new(self) + t = Thread.new do + begin + notes.each do |n| + ... + end + end + q = EnumeratorQueue.new(self) + ... + return q.each_item + end +``` + +Here the method receives an [Enumerable](http://ruby-doc.org//core-2.2.0/Enumerable.html), but also returns an [Enumerator](http://ruby-doc.org//core-2.2.0/Enumerator.html) that yields the responses. The implementation demonstrates how to set these up so that the requests and responses can be handled concurrently. Although each side will always get the other's messages in the order they were written, both the client and server can read and write in any order — the streams operate completely independently. + +### Starting the server + +Once we've implemented all our methods, we also need to start up a gRPC server so that clients can actually use our service. The following snippet shows how we do this for our `RouteGuide` service: + +```ruby + s = GRPC::RpcServer.new + s.add_http2_port(port) + logger.info("... running insecurely on #{port}") + s.handle(ServerImpl.new(feature_db)) + s.run +``` +As you can see, we build and start our server using a `GRPC::RpcServer`. To do this, we: + +1. Create an instance of our service implementation class `ServerImpl`. +2. Specify the address and port we want to use to listen for client requests using the builder's `add_http2_port` method. +3. Register our service implementation with the `GRPC::RpcServer`. +4. Call `run` on the`GRPC::RpcServer` to create and start an RPC server for our service. + + +## Creating the client + +In this section, we'll look at creating a Rubyclient for our `RouteGuide` service. You can see our complete example client code in [grpc-common/ruby/route_guide/route_guide_client.rb](https://github.com/grpc/grpc-common/blob/master/ruby/route_guide/route_guide_client.rb). + +### Creating a stub + +To call service methods, we first need to create a *stub*. + +We use the `Stub` class of the `RouteGuide` module generated from our .proto. + +```ruby + stub = RouteGuide::Stub.new('localhost:50051') +``` + +### Calling service methods + +Now let's look at how we call our service methods. Note that the gRPC Ruby only provides *blocking/synchronous* versions of each method: this means that the RPC call waits for the server to respond, and will either return a response or raise an exception. + +#### Simple RPC + +Calling the simple RPC `GetFeature` is nearly as straightforward as calling a local method. + +```ruby +GET_FEATURE_POINTS = [ + Point.new(latitude: 409_146_138, longitude: -746_188_906), + Point.new(latitude: 0, longitude: 0) +] +.. + GET_FEATURE_POINTS.each do |pt| + resp = stub.get_feature(pt) + ... + p "- found '#{resp.name}' at #{pt.inspect}" + end +``` + +As you can see, we create and populate a request protocol buffer object (in our case `Point`), and create a response protocol buffer object for the server to fill in. Finally, we call the method on the stub, passing it the context, request, and response. If the method returns `OK`, then we can read the response information from the server from our response object. + + +#### Streaming RPCs + +Now let's look at our streaming methods. If you've already read [Creating the server](#server) some of this may look very familiar - streaming RPCs are implemented in a similar way on both sides. Here's where we call the server-side streaming method `list_features`, which returns an `Enumerable` of `Features` + +```ruby + resps = stub.list_features(LIST_FEATURES_RECT) + resps.each do |r| + p "- found '#{r.name}' at #{r.location.inspect}" + end +``` + +The client-side streaming method `record_route` is similar, except there we pass the server an `Enumerable`. + +```ruby + ... + reqs = RandomRoute.new(features, points_on_route) + resp = stub.record_route(reqs.each, deadline) + ... +``` + +Finally, let's look at our bidirectional streaming RPC `route_chat`. In this case, we pass `Enumerable` to the method and get back an `Enumerable`. + +```ruby + resps = stub.route_chat(ROUTE_CHAT_NOTES) + resps.each { |r| p "received #{r.inspect}" } +``` + +Although it's not shown well by this example, each enumerable is independent of the other - both the client and server can read and write in any order — the streams operate completely independently. + +## Try it out! + +Build client and server: + +```shell +$ # from grpc-common/ruby +$ gem install bundler && bundle install +``` +Run the server, which will listen on port 50051: +```shell +$ # from grpc-common/ruby +$ bundle exec route_guide/route_guide_server.rb ../node/route_guide/route_guide_db.json & +``` +Run the client (in a different terminal): +```shell +$ # from grpc-common/ruby +$ bundle exec route_guide/route_guide_client.rb ../node/route_guide/route_guide_db.json & +``` +