From 72b2202d73c2699073cb29211a62ed2443f25b16 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 17:38:23 -0700 Subject: [PATCH 01/16] checkpoint --- examples/BUILD.bazel | 18 ++ examples/README.md | 2 + examples/build.gradle | 16 ++ .../grpc/examples/multiplex/EchoServer.java | 99 +++++++++ .../multiplex/MultiplexingServer.java | 104 ++++++++++ .../examples/multiplex/SharingClient.java | 190 ++++++++++++++++++ examples/src/main/proto/echo.proto | 48 +++++ 7 files changed, 477 insertions(+) create mode 100644 examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java create mode 100644 examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java create mode 100644 examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java create mode 100644 examples/src/main/proto/echo.proto diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 2a5ed52b35c..2634f56fdc3 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -35,6 +35,22 @@ java_grpc_library( deps = [":hello_streaming_java_proto"], ) +proto_library( + name = "echo_proto", + srcs = ["src/main/proto/echo.proto"], +) + +java_proto_library( + name = "echo_java_proto", + deps = [":recho_proto"], +) + +java_grpc_library( + name = "echo_java_grpc", + srcs = [":echo_proto"], + deps = [":echo_java_proto"], +) + proto_library( name = "route_guide_proto", srcs = ["src/main/proto/route_guide.proto"], @@ -68,6 +84,8 @@ java_library( ":hello_streaming_java_proto", ":helloworld_java_grpc", ":helloworld_java_proto", + ":echo_java_grpc", + ":echo_java_proto", ":route_guide_java_grpc", ":route_guide_java_proto", "@com_google_protobuf//:protobuf_java", diff --git a/examples/README.md b/examples/README.md index f57ad169897..94ca35b6cf1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -19,6 +19,8 @@ before trying out the examples. - [Error handling](src/main/java/io/grpc/examples/errorhandling) +- [Client and Server Sharing](src/main/java/io/grpc/examples/multiplex) + - [Compression](src/main/java/io/grpc/examples/experimental) - [Flow control](src/main/java/io/grpc/examples/manualflowcontrol) diff --git a/examples/build.gradle b/examples/build.gradle index c45e8e9da54..62a731c9cf0 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,6 +167,20 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } +task nameResolveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' + applicationName = 'multiplexing-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task nameResolveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.SharingClient' + applicationName = 'sharing-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + applicationDistribution.into('bin') { from(routeGuideServer) from(routeGuideClient) @@ -183,5 +197,7 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) + from(multiplexing-server) + from(sharing-client) fileMode = 0755 } diff --git a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java new file mode 100644 index 00000000000..4b48ba0f0d8 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java @@ -0,0 +1,99 @@ +/* + * Copyright 2015 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.multiplex; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Server that manages startup/shutdown of a {@code Greeter} server. + */ +public class EchoServer extends EchoGrpc.EchoImplBase{ + private static final Logger logger = Logger.getLogger(EchoServer.class.getName()); + + @Override + public void UnaryEcho(EchoRequest request, + StreamObserver responseObserver) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + } + + @Override + public void ServerStreamingEcho(EchoRequest request, + StreamObserver responseObserver) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public StreamObserver ClientStreamingEcho( + final StreamObserver responseObserver) { + return new StreamObserver() { + List requestList = new ArrayList<>(); + + @Override + public void onNext(EchoRequest request) { + requestList.add(request.getMessage()); + } + + @Override + public void onError(Throwable t) { + logger.log(Level.WARNING, "echo stream cancelled"); + } + + @Override + public void onCompleted() { + String reply = requestList.stream().collect(Collectors.joining(", ")); + EchoResponse response = EchoResponse.newBuilder().setMessage(reply).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + }; + } + + @Override + public StreamObserver BidirectionalStreamingEcho( + final StreamObserver responseObserver) { + return new StreamObserver() { + @Override + public void onNext(EchoRequest request) { + EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); + responseObserver.onNext(response); + } + + @Override + public void onError(Throwable t) { + logger.log(Level.WARNING, "echo stream cancelled"); + } + + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + }; + } +} diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java new file mode 100644 index 00000000000..8ec93efde8c --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.multiplex; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusRuntimeException +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * A sample gRPC server that serves both the Greeting and Echo services. + */ +public class MultiplexingServer { + + private static final Logger logger = Logger.getLogger(MultiplexingServer.class.getName()); + + private final int port; + private final Server server; + + public MultiplexingServer(int port) throws IOException { + this.port = port; + } + private void start() throws IOException { + /* The port on which the server should run */ + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new io.grpc.examples.multiplex.MultiplexingServer.GreeterImpl()) + .addService(new io.grpc.examples.multiplex.EchoServer()) + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + io.grpc.examples.multiplex.MultiplexingServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final MultiplexingServer server = new io.grpc.examples.multiplex.MultiplexingServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } + } diff --git a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java new file mode 100644 index 00000000000..81206f6f542 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java @@ -0,0 +1,190 @@ + +/* + * Copyright 2015 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.multiplex; + +import com.google.common.collect.ImmutableList; +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; +import io.grpc.examples.helloworld.HelloWorldClient; +import io.grpc.examples.routeguide.RouteGuideUtil; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A client that shares a channel across multiple stubs to a single service and across services + * being provided by one server process. + */ +public class SharingClient { + private static final Logger logger = Logger.getLogger( + HelloWorldClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub greeterStub1; + private final GreeterGrpc.GreeterBlockingStub greeterStub2; + private final EchoGrpc.EchoStub echoStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public SharingClient(Channel channel) { + // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to + // shut it down. + + // Passing Channels to code makes code easier to test and makes it easier to reuse Channels. + greeterStub1 = GreeterGrpc.newBlockingStub(channel); + greeterStub2 = GreeterGrpc.newBlockingStub(channel); + echoStub = EchoGrpc.newStub(channel); + } + + /** Say hello to server. */ + private void greet(String name, GreeterGrpc.GreeterBlockingStub stub, String stubName) { + logger.info("Will try to greet " + name + " using " + stubName); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = stub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + public void greet1(String name) { + greet(name, greeterStub1, "greeter #1"); + } + + public void greet2(String name) { + greet(name, greeterStub2, "greeter #2"); + } + + public CountDownLatch initiateEchos(List valuesToSend, final List valuesReceived) { + final CountDownLatch finishLatch = new CountDownLatch(1); + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onNext(EchoResponse response) { + valuesReceived.add(response.getMessage()); + } + + @Override + public void onError(Throwable t) { + logger.warning("Echo Failed: {0}", Status.fromThrowable(t)); + finishLatch.countDown(); + } + + @Override + public void onCompleted() { + logger.info("Finished RecordRoute"); + finishLatch.countDown(); + } + }; + + StreamObserver requestObserver = + echoStub.BidirectionalStreamingEcho(responseObserver); + try { + // Send numPoints points randomly selected from the features list. + for (String curValue : valuesToSend) { + int index = random.nextInt(features.size()); + EchoRequest req = EchoRequest.newBuilder().setName(curValue).build(); + requestObserver.onNext(req); + // Sleep for a bit before sending the next one. + Thread.sleep(random.nextInt(1000) + 500); + if (finishLatch.getCount() == 0) { + // RPC completed or errored before we finished sending. + // Sending further requests won't error, but they will just be thrown away. + return finishLatch; + } + } + } catch (RuntimeException e) { + // Cancel RPC + requestObserver.onError(e); + throw e; + } + // Mark the end of requests + requestObserver.onCompleted(); + + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + String user = "world"; + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + // Allow passing in the user and target strings as command line arguments + if (args.length > 0) { + if ("--help".equals(args[0])) { + System.err.println("Usage: [name [target]]"); + System.err.println(""); + System.err.println(" name The name you wish to be greeted by. Defaults to " + user); + System.err.println(" target The server to connect to. Defaults to " + target); + System.exit(1); + } + user = args[0]; + } + if (args.length > 1) { + target = args[1]; + } + + // Create a communication channel to the server, known as a Channel. Channels are thread-safe + // and reusable. It is common to create channels at the beginning of your application and reuse + // them until the application shuts down. + // + // For the example we use plaintext insecure credentials to avoid needing TLS certificates. To + // use TLS, use TlsChannelCredentials instead. + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); + List echoInput = ImmutableList.of("some", "thing", "wicked", "this", "way", "comes"); + List echoOutput = new ArrayList<>(); + try { + SharingClient client = new SharingClient(channel); + + CountDownLatch finishLatch = client.initiateEchos(echoInput, echoOutput); + client.greet1(user + " the great"); + client.greet2(user + " the lesser"); + client.greet1(user + " the humble"); + // Receiving happens asynchronously + if (!finishLatch.await(1, TimeUnit.MINUTES)) { + logger.warning("Echo did not finish within 1 minute"); + } + + System.out.println("The echo requests and results were:"); + System.out.println(echoInput.toString()); + System.out.println(echoOutput.toString()); + } finally { + // ManagedChannels use resources like threads and TCP connections. To prevent leaking these + // resources the channel should be shut down when it will no longer be used. If it may be used + // again leave it running. + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/proto/echo.proto b/examples/src/main/proto/echo.proto new file mode 100644 index 00000000000..0668eb55bcc --- /dev/null +++ b/examples/src/main/proto/echo.proto @@ -0,0 +1,48 @@ +/* + * + * Copyright 2023 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.echo"; +option java_outer_classname = "EchoProto"; +option objc_class_prefix = "ECHO"; + +package echo; + +// EchoRequest is the request for echo. +message EchoRequest { + string message = 1; +} + +// EchoResponse is the response for echo. +message EchoResponse { + string message = 1; +} + +// Echo is the echo service. +service Echo { + // UnaryEcho is unary echo. + rpc UnaryEcho(EchoRequest) returns (EchoResponse) {} + // ServerStreamingEcho is server side streaming. + rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {} + // ClientStreamingEcho is client side streaming. + rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {} + // BidirectionalStreamingEcho is bidi streaming. + rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {} +} \ No newline at end of file From 53222d66089179ada69ace90ad0454fae0d77196 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 17:41:42 -0700 Subject: [PATCH 02/16] checkpoint --- examples/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 62a731c9cf0..e5ad7e131ca 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,14 +167,14 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } -task nameResolveClient(type: CreateStartScripts) { +task multiplexingServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' applicationName = 'multiplexing-server' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } -task nameResolveClient(type: CreateStartScripts) { +task sharingClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.SharingClient' applicationName = 'sharing-client' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) @@ -197,7 +197,7 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) - from(multiplexing-server) - from(sharing-client) + from(multiplexingServer) + from(sharingClient) fileMode = 0755 } From b1f784ad12bc269dba88c5459ef09a90d70d450f Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 18:43:14 -0700 Subject: [PATCH 03/16] add imports --- .../src/main/java/io/grpc/examples/multiplex/EchoServer.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java index 4b48ba0f0d8..97104b78eb5 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java @@ -19,6 +19,9 @@ import io.grpc.Grpc; import io.grpc.InsecureServerCredentials; import io.grpc.Server; +import io.grpc.examples.echo.EchoGrpc; +import io.grpc.examples.echo.EchoRequest; +import io.grpc.examples.echo.EchoResponse; import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.ArrayList; From 7b5e7cb3abc2fb6b3f546b59c6f272aabda43d04 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Sat, 18 Mar 2023 01:44:16 +0000 Subject: [PATCH 04/16] fixes --- .../java/io/grpc/examples/multiplex/MultiplexingServer.java | 2 +- .../src/main/java/io/grpc/examples/multiplex/SharingClient.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java index 8ec93efde8c..ede13fc8d97 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -20,7 +20,7 @@ import io.grpc.InsecureServerCredentials; import io.grpc.Server; import io.grpc.ServerBuilder; -import io.grpc.StatusRuntimeException +import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; diff --git a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java index 81206f6f542..e9284633d44 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java @@ -23,7 +23,7 @@ import io.grpc.InsecureChannelCredentials; import io.grpc.ManagedChannel; import io.grpc.Status; -import io.grpc.StatusRuntimeException +import io.grpc.StatusRuntimeException; import io.grpc.examples.helloworld.GreeterGrpc; import io.grpc.examples.helloworld.HelloReply; import io.grpc.examples.helloworld.HelloRequest; From e256da6727924518774f652594f21f32589702f2 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 19:01:11 -0700 Subject: [PATCH 05/16] fixes --- .../java/io/grpc/examples/multiplex/EchoServer.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java index 97104b78eb5..cf8f6fdb7fe 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/EchoServer.java @@ -23,10 +23,8 @@ import io.grpc.examples.echo.EchoRequest; import io.grpc.examples.echo.EchoResponse; import io.grpc.stub.StreamObserver; -import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -37,15 +35,15 @@ public class EchoServer extends EchoGrpc.EchoImplBase{ private static final Logger logger = Logger.getLogger(EchoServer.class.getName()); - @Override - public void UnaryEcho(EchoRequest request, + @Override + public void unaryEcho(EchoRequest request, StreamObserver responseObserver) { EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); responseObserver.onNext(response); } @Override - public void ServerStreamingEcho(EchoRequest request, + public void serverStreamingEcho(EchoRequest request, StreamObserver responseObserver) { EchoResponse response = EchoResponse.newBuilder().setMessage(request.getMessage()).build(); responseObserver.onNext(response); @@ -53,7 +51,7 @@ public void ServerStreamingEcho(EchoRequest request, } @Override - public StreamObserver ClientStreamingEcho( + public StreamObserver clientStreamingEcho( final StreamObserver responseObserver) { return new StreamObserver() { List requestList = new ArrayList<>(); @@ -79,7 +77,7 @@ public void onCompleted() { } @Override - public StreamObserver BidirectionalStreamingEcho( + public StreamObserver bidirectionalStreamingEcho( final StreamObserver responseObserver) { return new StreamObserver() { @Override From 2abc9eaeb08f10ddaf7a76752f11832802bb9647 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 19:12:53 -0700 Subject: [PATCH 06/16] fixes syntax errors --- .../io/grpc/examples/multiplex/MultiplexingServer.java | 2 +- .../java/io/grpc/examples/multiplex/SharingClient.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java index ede13fc8d97..1cf1b99d20a 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -87,7 +87,7 @@ private void blockUntilShutdown() throws InterruptedException { * Main launches the server from the command line. */ public static void main(String[] args) throws IOException, InterruptedException { - final MultiplexingServer server = new io.grpc.examples.multiplex.MultiplexingServer(); + final MultiplexingServer server = new MultiplexingServer(500051); server.start(); server.blockUntilShutdown(); } diff --git a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java index e9284633d44..5af43011f96 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java @@ -31,10 +31,11 @@ import io.grpc.examples.echo.EchoRequest; import io.grpc.examples.echo.EchoResponse; import io.grpc.examples.helloworld.HelloWorldClient; -import io.grpc.examples.routeguide.RouteGuideUtil; +import io.grpc.stub.StreamObserver; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -52,6 +53,8 @@ public class SharingClient { private final GreeterGrpc.GreeterBlockingStub greeterStub2; private final EchoGrpc.EchoStub echoStub; + private Random random = new Random(); + /** Construct client for accessing HelloWorld server using the existing channel. */ public SharingClient(Channel channel) { // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to @@ -95,7 +98,7 @@ public void onNext(EchoResponse response) { @Override public void onError(Throwable t) { - logger.warning("Echo Failed: {0}", Status.fromThrowable(t)); + logger.warning("Echo Failed: {0}" + Status.fromThrowable(t)); finishLatch.countDown(); } @@ -111,7 +114,6 @@ public void onCompleted() { try { // Send numPoints points randomly selected from the features list. for (String curValue : valuesToSend) { - int index = random.nextInt(features.size()); EchoRequest req = EchoRequest.newBuilder().setName(curValue).build(); requestObserver.onNext(req); // Sleep for a bit before sending the next one. From 8352f99c252914d27f134e3db85c2275eafcf3f1 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Sat, 18 Mar 2023 02:36:50 +0000 Subject: [PATCH 07/16] fix last of the syntax errors --- .../io/grpc/examples/multiplex/MultiplexingServer.java | 2 +- .../java/io/grpc/examples/multiplex/SharingClient.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java index 1cf1b99d20a..261abb99d56 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -40,7 +40,7 @@ public class MultiplexingServer { private static final Logger logger = Logger.getLogger(MultiplexingServer.class.getName()); private final int port; - private final Server server; + private Server server = null; public MultiplexingServer(int port) throws IOException { this.port = port; diff --git a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java index 5af43011f96..dd087f4a04b 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/SharingClient.java @@ -110,11 +110,10 @@ public void onCompleted() { }; StreamObserver requestObserver = - echoStub.BidirectionalStreamingEcho(responseObserver); + echoStub.bidirectionalStreamingEcho(responseObserver); try { - // Send numPoints points randomly selected from the features list. for (String curValue : valuesToSend) { - EchoRequest req = EchoRequest.newBuilder().setName(curValue).build(); + EchoRequest req = EchoRequest.newBuilder().setMessage(curValue).build(); requestObserver.onNext(req); // Sleep for a bit before sending the next one. Thread.sleep(random.nextInt(1000) + 500); @@ -128,10 +127,14 @@ public void onCompleted() { // Cancel RPC requestObserver.onError(e); throw e; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + requestObserver.onError(e); } // Mark the end of requests requestObserver.onCompleted(); + return finishLatch; } /** From e46bdfc7d86ff378114770c79d65ed3c691e168f Mon Sep 17 00:00:00 2001 From: larry-safran Date: Mon, 20 Mar 2023 18:43:40 +0000 Subject: [PATCH 08/16] fix port # --- .../java/io/grpc/examples/multiplex/MultiplexingServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java index 261abb99d56..f5607c287f6 100644 --- a/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java +++ b/examples/src/main/java/io/grpc/examples/multiplex/MultiplexingServer.java @@ -87,7 +87,7 @@ private void blockUntilShutdown() throws InterruptedException { * Main launches the server from the command line. */ public static void main(String[] args) throws IOException, InterruptedException { - final MultiplexingServer server = new MultiplexingServer(500051); + final MultiplexingServer server = new MultiplexingServer(50051); server.start(); server.blockUntilShutdown(); } From fca1f2973db0fc3270ca666a96f193577c12a12b Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 17 Mar 2023 15:09:58 -0700 Subject: [PATCH 09/16] buildscript: iterate all example folder and build (#9961) --- buildscripts/kokoro/unix.sh | 31 +++++++++------------------ examples/example-servlet/build.gradle | 2 +- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh index 828a599ff7c..d5a85c7404a 100755 --- a/buildscripts/kokoro/unix.sh +++ b/buildscripts/kokoro/unix.sh @@ -64,28 +64,17 @@ if [[ -z "${SKIP_TESTS:-}" ]]; then # --batch-mode reduces log spam mvn verify --batch-mode popd - pushd examples/example-alts - ../gradlew build $GRADLE_FLAGS - popd - pushd examples/example-hostname - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-tls - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-jwt-auth - ../gradlew build $GRADLE_FLAGS - mvn verify --batch-mode - popd - pushd examples/example-xds - ../gradlew build $GRADLE_FLAGS - popd + for f in examples/example-* + do + pushd "$f" + ../gradlew build $GRADLE_FLAGS + if [ -f "pom.xml" ]; then + # --batch-mode reduces log spam + mvn verify --batch-mode + fi + popd + done # TODO(zpencer): also build the GAE examples - pushd examples/example-orca - ../gradlew build $GRADLE_FLAGS - popd fi LOCAL_MVN_TEMP=$(mktemp -d) diff --git a/examples/example-servlet/build.gradle b/examples/example-servlet/build.gradle index 64b0a8eac10..f6772cda51d 100644 --- a/examples/example-servlet/build.gradle +++ b/examples/example-servlet/build.gradle @@ -23,7 +23,7 @@ dependencies { "io.grpc:grpc-servlet:${grpcVersion}", "io.grpc:grpc-stub:${grpcVersion}" - providedImplementation "javax.servlet:javax.servlet-api:4.0.1", + compileOnly "javax.servlet:javax.servlet-api:4.0.1", "org.apache.tomcat:annotations-api:6.0.53" } From b73a020b4925f010b8fb7c49725fb94f81e24de4 Mon Sep 17 00:00:00 2001 From: DNVindhya Date: Fri, 17 Mar 2023 15:53:46 -0700 Subject: [PATCH 10/16] gcp-observability: Update logging fields for GA and use custom BatchingSettings (#9959) This commit updates the following in gcp observability logging schema * `payload.status_code` will be of type `google.rpc.Code` instead of `uint32`. * names in enum `Address.TYPE` Use custom batching settings for [LoggingOptions](https://javadoc.io/doc/com.google.cloud/google-cloud-logging/latest/com/google/cloud/logging/LoggingOptions.html) Note: Upgraded `com.google.cloud:google-cloud-logging` from `3.6.1` to `3.14.5`. --- gcp-observability/build.gradle | 16 ++++++------- .../observability/interceptors/LogHelper.java | 9 +++---- .../gcp/observability/logging/GcpLogSink.java | 24 ++++++++++++++++++- .../v1/observabilitylog.proto | 9 +++---- .../interceptors/LogHelperTest.java | 7 +++--- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/gcp-observability/build.gradle b/gcp-observability/build.gradle index 85e6e9eeb01..63850efc4e8 100644 --- a/gcp-observability/build.gradle +++ b/gcp-observability/build.gradle @@ -20,11 +20,13 @@ tasks.named("compileJava").configure { } dependencies { - def cloudLoggingVersion = '3.6.1' + def cloudLoggingVersion = '3.14.5' annotationProcessor libraries.auto.value api project(':grpc-api') - + + // TODO(dnvindhya): Prefer using our own libraries, update the dependencies + // in gradle/libs.versions instead implementation project(':grpc-protobuf'), project(':grpc-stub'), project(':grpc-alts'), @@ -35,12 +37,10 @@ dependencies { libraries.opencensus.exporter.trace.stackdriver, project(':grpc-xds'), // Align grpc versions project(':grpc-services'), // Align grpc versions - libraries.animalsniffer.annotations, // Prefer our version - libraries.google.auth.credentials, // Prefer our version - libraries.protobuf.java.util, // Prefer our version - libraries.gson, // Prefer our version - libraries.perfmark.api, // Prefer our version - libraries.re2j, // Prefer our version + ('com.google.protobuf:protobuf-java:3.21.12'), + ('com.google.api.grpc:proto-google-common-protos:2.14.2'), + ('com.google.auth:google-auth-library-oauth2-http:1.16.0'), + ('io.opencensus:opencensus-api:0.31.1'), ('com.google.guava:guava:31.1-jre') runtimeOnly libraries.opencensus.impl diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java index 9b46699efaf..abd44c43650 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/interceptors/LogHelper.java @@ -23,6 +23,7 @@ import com.google.common.base.Joiner; import com.google.protobuf.ByteString; import com.google.protobuf.Duration; +import com.google.rpc.Code; import io.grpc.Attributes; import io.grpc.Deadline; import io.grpc.Grpc; @@ -182,7 +183,7 @@ void logTrailer( PayloadBuilderHelper pair = createMetadataProto(metadata, maxHeaderBytes); - pair.payloadBuilder.setStatusCode(status.getCode().value()); + pair.payloadBuilder.setStatusCode(Code.forNumber(status.getCode().value())); String statusDescription = status.getDescription(); if (statusDescription != null) { pair.payloadBuilder.setStatusMessage(statusDescription); @@ -404,10 +405,10 @@ static Address socketAddressToProto(SocketAddress address) { if (address instanceof InetSocketAddress) { InetAddress inetAddress = ((InetSocketAddress) address).getAddress(); if (inetAddress instanceof Inet4Address) { - builder.setType(Address.Type.TYPE_IPV4) + builder.setType(Address.Type.IPV4) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else if (inetAddress instanceof Inet6Address) { - builder.setType(Address.Type.TYPE_IPV6) + builder.setType(Address.Type.IPV6) .setAddress(InetAddressUtil.toAddrString(inetAddress)); } else { logger.log(Level.SEVERE, "unknown type of InetSocketAddress: {}", address); @@ -417,7 +418,7 @@ static Address socketAddressToProto(SocketAddress address) { } else if (address.getClass().getName().equals("io.netty.channel.unix.DomainSocketAddress")) { // To avoid a compiled time dependency on grpc-netty, we check against the // runtime class name. - builder.setType(Address.Type.TYPE_UNIX) + builder.setType(Address.Type.UNIX) .setAddress(address.toString()); } else { builder.setType(Address.Type.TYPE_UNKNOWN).setAddress(address.toString()); diff --git a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java index e91f310e647..02ff4049eb4 100644 --- a/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java +++ b/gcp-observability/src/main/java/io/grpc/gcp/observability/logging/GcpLogSink.java @@ -18,12 +18,15 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.api.gax.batching.BatchingSettings; +import com.google.api.gax.batching.FlowController; import com.google.cloud.MonitoredResource; import com.google.cloud.logging.LogEntry; import com.google.cloud.logging.Logging; import com.google.cloud.logging.LoggingOptions; import com.google.cloud.logging.Payload.JsonPayload; import com.google.cloud.logging.Severity; +import com.google.cloud.logging.v2.stub.LoggingServiceV2StubSettings; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; @@ -41,6 +44,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import org.threeten.bp.Duration; /** * Sink for Google Cloud Logging. @@ -102,6 +106,7 @@ public void write(GrpcLogRecord logProto) { if (servicesToExclude.contains(logProto.getServiceName())) { return; } + LogEntry grpcLogEntry = null; try { GrpcLogRecord.EventType eventType = logProto.getType(); // TODO(DNVindhya): make sure all (int, long) values are not displayed as double @@ -117,11 +122,18 @@ public void write(GrpcLogRecord logProto) { if (!customTags.isEmpty()) { grpcLogEntryBuilder.setLabels(customTags); } - LogEntry grpcLogEntry = grpcLogEntryBuilder.build(); + grpcLogEntry = grpcLogEntryBuilder.build(); synchronized (this) { logger.log(Level.FINEST, "Writing gRPC event : {0} to Cloud Logging", eventType); gcpLoggingClient.write(Collections.singleton(grpcLogEntry)); } + } catch (FlowController.FlowControlRuntimeException e) { + String grpcLogEntryString = null; + if (grpcLogEntry != null) { + grpcLogEntryString = grpcLogEntry.toStructuredJsonString(); + } + logger.log(Level.SEVERE, "Limit exceeded while writing log entry to cloud logging"); + logger.log(Level.SEVERE, "Log entry = ", grpcLogEntryString); } catch (Exception e) { logger.log(Level.SEVERE, "Caught exception while writing to Cloud Logging", e); } @@ -132,6 +144,16 @@ Logging createLoggingClient() { if (!Strings.isNullOrEmpty(projectId)) { builder.setProjectId(projectId); } + BatchingSettings loggingDefaultBatchingSettings = LoggingServiceV2StubSettings.newBuilder() + .writeLogEntriesSettings().getBatchingSettings(); + // Custom batching settings + BatchingSettings grpcLoggingVBatchingSettings = loggingDefaultBatchingSettings.toBuilder() + .setDelayThreshold(Duration.ofSeconds(1L)).setFlowControlSettings( + loggingDefaultBatchingSettings.getFlowControlSettings().toBuilder() + .setMaxOutstandingRequestBytes(52428800L) //50 MiB + .setLimitExceededBehavior(FlowController.LimitExceededBehavior.ThrowException) + .build()).build(); + builder.setBatchingSettings(grpcLoggingVBatchingSettings); return builder.build().getService(); } diff --git a/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto b/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto index 85ef00ac2dd..7d278aa08a9 100644 --- a/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto +++ b/gcp-observability/src/main/proto/grpc/observabilitylog/v1/observabilitylog.proto @@ -20,6 +20,7 @@ package grpc.observabilitylog.v1; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; +import "google/rpc/code.proto"; option java_multiple_files = true; option java_package = "io.grpc.observabilitylog.v1"; @@ -97,7 +98,7 @@ message Payload { // the RPC timeout value google.protobuf.Duration timeout = 2; // The gRPC status code - uint32 status_code = 3; + google.rpc.Code status_code = 3; // The gRPC status message string status_message = 4; // The value of the grpc-status-details-bin metadata key, if any. @@ -115,9 +116,9 @@ message Payload { message Address { enum Type { TYPE_UNKNOWN = 0; - TYPE_IPV4 = 1; // in 1.2.3.4 form - TYPE_IPV6 = 2; // IPv6 canonical form (RFC5952 section 4) - TYPE_UNIX = 3; // UDS string + IPV4 = 1; // in 1.2.3.4 form + IPV6 = 2; // IPv6 canonical form (RFC5952 section 4) + UNIX = 3; // UDS string } Type type = 1; string address = 2; diff --git a/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java index 73704eb4181..a6d9fab702f 100644 --- a/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java +++ b/gcp-observability/src/test/java/io/grpc/gcp/observability/interceptors/LogHelperTest.java @@ -29,6 +29,7 @@ import com.google.protobuf.ByteString; import com.google.protobuf.Duration; import com.google.protobuf.util.Durations; +import com.google.rpc.Code; import io.grpc.Attributes; import io.grpc.Grpc; import io.grpc.Metadata; @@ -94,7 +95,7 @@ public void socketToProto_ipv4() throws Exception { assertThat(LogHelper.socketAddressToProto(socketAddress)) .isEqualTo(Address .newBuilder() - .setType(Address.Type.TYPE_IPV4) + .setType(Address.Type.IPV4) .setAddress("127.0.0.1") .setIpPort(12345) .build()); @@ -109,7 +110,7 @@ public void socketToProto_ipv6() throws Exception { assertThat(LogHelper.socketAddressToProto(socketAddress)) .isEqualTo(Address .newBuilder() - .setType(Address.Type.TYPE_IPV6) + .setType(Address.Type.IPV6) .setAddress("2001:db8::2:1") // RFC 5952 section 4: ipv6 canonical form required .setIpPort(12345) .build()); @@ -454,7 +455,7 @@ public void logTrailer() throws Exception { builder.setPeer(LogHelper.socketAddressToProto(peer)); builder.setPayload( builder.getPayload().toBuilder() - .setStatusCode(Status.INTERNAL.getCode().value()) + .setStatusCode(Code.forNumber(Status.INTERNAL.getCode().value())) .setStatusMessage("test description") .build()); GrpcLogRecord base = builder.build(); From 972a4b03fa3dc1cf8052c8b2a2d1e8c025f9756a Mon Sep 17 00:00:00 2001 From: Larry Safran <107004254+larry-safran@users.noreply.github.com> Date: Fri, 17 Mar 2023 16:26:06 -0700 Subject: [PATCH 11/16] examples: Add an example for doing debug (#9957) Extensive README, a server that exposes channelz and has pauses, and a client that uses multiple channels also exposes channelz service and has a 30 second delay to allow people to run the grpcdebug tool. Fixit b/259286633 --- examples/example-debug/README.md | 239 ++++++++++++++++++ examples/example-debug/build.gradle | 76 ++++++ examples/example-debug/pom.xml | 118 +++++++++ examples/example-debug/settings.gradle | 1 + .../debug/HelloWorldDebuggableClient.java | 140 ++++++++++ .../debug/HostnameDebuggableServer.java | 91 +++++++ .../grpc/examples/debug/HostnameGreeter.java | 73 ++++++ .../main/proto/helloworld/helloworld.proto | 37 +++ 8 files changed, 775 insertions(+) create mode 100644 examples/example-debug/README.md create mode 100644 examples/example-debug/build.gradle create mode 100644 examples/example-debug/pom.xml create mode 100644 examples/example-debug/settings.gradle create mode 100644 examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java create mode 100644 examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java create mode 100644 examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java create mode 100644 examples/example-debug/src/main/proto/helloworld/helloworld.proto diff --git a/examples/example-debug/README.md b/examples/example-debug/README.md new file mode 100644 index 00000000000..05ae14a189e --- /dev/null +++ b/examples/example-debug/README.md @@ -0,0 +1,239 @@ +# gRPC Debug Example + +The debug example uses a Hello World style server whose response includes its +hostname. It demonstrates usage of the AdminInterface and the grpcdebug +commandline tool. + +The example requires grpc-java to already be built. You are strongly encouraged +to check out a git release tag, since there will already be a build of grpc +available. Otherwise, you must follow [COMPILING](../../COMPILING.md). + +### Build the example + +1. Optional: Build the hello-world-debug example client. + See [the examples README](../README.md) + +2. Build the debuggable server and client. From the + `grpc-java/examples/examples-debug` directory run: + +```bash +$ ../gradlew installDist +``` + +This creates the +scripts `build/install/debug/bin/hostname-debuggable-server/bin/hostname-debuggable-server` +that +runs the example. + +To run the debug example, run: + +```bash +$ ./build/install/debug/bin/hostname-debuggable-server/bin/hostname-debuggable-server +``` + +And in a different terminal window run the client. + +Note: You can use the standard hello-world client with no debugging enabled and +still see results on the server. However, if you want to get debug information +about the client you need to run the hello-world-debuggable client. + +Simple client + +```bash +$ ../build/install/examples/bin/hello-world-client +``` + +debug enabled client + +```bash +$ ./build/install/examples-debug/bin/hello-world-debuggable-client +``` + +### Maven + +If you prefer to use Maven: + +1. Build the hello-world example client. See [the examples README](../README.md) + +2. Run in this directory: + +```bash +$ mvn verify +$ # Run the server (from the examples-debug directory) +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.debug.HostnameServer +$ # In another terminal run the client (from the examples directory) +$ cd .. +$ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldClient +``` + +## Using grpcdebug + +grpcdebug is a tool that has been created to access the metrics from the +channelz and health services. + +### Installing the grpcdebug tool + +The source code is located in a github project +[grpc-ecosystem/grpcdebug](https://github.com/grpc-ecosystem/grpcdebug). You +can either download [the latest built version] +(https://github.com/grpc-ecosystem/grpcdebug/releases/latest) (recommended) or +follow the README.md to build it yourself. + +### Running the grpcdebug tool +#### Usage +`grpcdebug [flags] channelz [argument]` + + +| Command | Argument | Description | +|:-----------|:--------------------:|:--------------------------------------------------| +| channel | \ | Display channel states in a human readable way. | +| channels | | Lists client channels for the target application. | +| server | \ | Displays server state in a human readable way. | +| servers | | Lists servers in a human readable way. | +| socket | \ | Displays socket states in a human readable way. | +| subchannel | \ | Display subchannel states in human readable way. | + +Generally, you will start with either `servers` or `channels` and then work down +to the details +

+ +#### Getting overall server info +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz servers +``` +This will show you the server ids with their activity +```text +Server ID Listen Addresses Calls(Started/Succeeded/Failed) Last Call Started +2 [[::]:50051] 38/34/3 now +``` +
+ +#### Getting details for a service +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz server 2 +``` + +The output will include more communication details and will show socket ids for +currently connected clients + +```text +Server Id: 2 +Listen Addresses: [[::]:50051] +Calls Started: 33 +Calls Succeeded: 29 +Calls Failed: 3 +Last Call Started: now +--- +Socket ID Local->Remote Streams(Started/Succeeded/Failed) Messages(Sent/Received) +19 [::1]:50051->[::1]:39834 4/3/0 3/4 +``` + +#### Displaying detailed info for a server side connection (socket) + +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz socket 19 +``` + +This will show a lot of gRPC internal information + +```text +Socket ID: 19 +Address: [::1]:50051->[::1]:50094 +Streams Started: 1 +Streams Succeeded: 0 +Streams Failed: 0 +Messages Sent: 0 +Messages Received: 1 +Keep Alives Sent: 0 +Last Local Stream Created: +Last Remote Stream Created: now +Last Message Sent Created: +Last Message Received Created: now +Local Flow Control Window: 65535 +Remote Flow Control Window: 1048569 +--- +Socket Options Name Value +SO_LINGER [type.googleapis.com/grpc.channelz.v1.SocketOptionLinger]:{} +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_CORK false +WRITE_BUFFER_HIGH_WATER_MARK 65536 +WRITE_BUFFER_LOW_WATER_MARK 32768 +IP_TOS 0 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPCNT 9 +SINGLE_EVENTEXECUTOR_PER_GROUP true +SO_SNDBUF 2626560 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_NOTSENT_LOWAT 0 +WRITE_BUFFER_WATER_MARK WriteBufferWaterMark(low: 32768, high: 65536) +TCP_NODELAY true +SO_RCVBUF 131072 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#SO_BUSY_POLL 0 +IP_TRANSPARENT false +SO_KEEPALIVE true +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_QUICKACK false +ALLOCATOR PooledByteBufAllocator(directByDefault: true) +TCP_FASTOPEN_CONNECT false +MESSAGE_SIZE_ESTIMATOR io.grpc.netty.shaded.io.netty.channel.DefaultMessageSizeEstimator@48d475b6 +WRITE_SPIN_COUNT 16 +SO_REUSEADDR true +CONNECT_TIMEOUT_MILLIS 30000 +ALLOW_HALF_CLOSURE false +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#EPOLL_MODE EDGE_TRIGGERED +MAX_MESSAGES_PER_READ 16 +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPIDLE 7200 +AUTO_CLOSE true +io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption#TCP_KEEPINTVL 75 +MAX_MESSAGES_PER_WRITE 2147483647 +AUTO_READ true +TCP_MD5SIG null +RCVBUF_ALLOCATOR io.grpc.netty.shaded.io.netty.channel.AdaptiveRecvByteBufAllocator@360691a0 +``` +#### Displaying the list of gRPC client channels +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz channels +``` +Output +```text +Channel ID Target State Calls(Started/Succeeded/Failed) Created Time +1 localhost:50051 READY 34/34/0 +3 localhost:50051 READY 16/16/0 +``` +Note: If you have a simple server that doesn't use gRPC clients to contact other +servers, then this table will be empty. + +#### Displaying details of a gRPC client channel +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz channel 3 +``` +Output +```text +Channel ID: 3 +Target: localhost:50051 +State: READY +Calls Started: 16 +Calls Succeeded: 16 +Calls Failed: 0 +Created Time: +--- +Subchannel ID Target State Calls(Started/Succeeded/Failed) CreatedTime +10 [[[localhost/127.0.0.1:50051]/{}], [[localhost/0:0 READY 16/16/0 +``` + +#### Displaying details of a gRPC client subchannel +Command +```bash +bin/grpcdebug/grpcdebug localhost:50051 channelz subchannel 10 +``` +Output +```text +Subchannel ID: 10 +Target: [[[localhost/127.0.0.1:50051]/{}], [[localhost/0:0:0:0:0:0:0:1:50051]/{}]] +State: READY +Calls Started: 16 +Calls Succeeded: 16 +Calls Failed: 0 +Created Time: +--- +Socket ID Local->Remote Streams(Started/Succeeded/Failed) Messages(Sent/Received) +11 127.0.0.1:48536->127.0.0.1:50051 16/16/0 12/12 +``` \ No newline at end of file diff --git a/examples/example-debug/build.gradle b/examples/example-debug/build.gradle new file mode 100644 index 00000000000..6cdfd9ccdd8 --- /dev/null +++ b/examples/example-debug/build.gradle @@ -0,0 +1,76 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + id 'java' + + id "com.google.protobuf" version "0.8.17" + + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" } + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you +// are looking at a tagged version of the example and not "master"! + +// Feel free to delete the comment at the next line. It is just for safely +// updating the version in our release process. +def grpcVersion = '1.55.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protobufVersion = '3.21.7' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + runtimeOnly "io.grpc:grpc-netty-shaded:${grpcVersion}" + + testImplementation 'junit:junit:4.13.2' + testImplementation "io.grpc:grpc-testing:${grpcVersion}" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protobufVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } +} + +startScripts.enabled = false + +task HelloWorldDebuggableClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.debug.HelloWorldDebuggableClient' + applicationName = 'hello-world-debuggable-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +task HostnameDebuggableServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.debug.HostnameDebuggableServer' + applicationName = 'hostname-debuggable-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(HelloWorldDebuggableClient) + from(HostnameDebuggableServer) + fileMode = 0755 +} diff --git a/examples/example-debug/pom.xml b/examples/example-debug/pom.xml new file mode 100644 index 00000000000..fbfef978a5b --- /dev/null +++ b/examples/example-debug/pom.xml @@ -0,0 +1,118 @@ + + 4.0.0 + io.grpc + example-debug + jar + + 1.55.0-SNAPSHOT + example-debug + https://github.com/grpc/grpc-java + + + UTF-8 + 1.55.0-SNAPSHOT + 3.21.7 + + 1.8 + 1.8 + + + + + + io.grpc + grpc-bom + ${grpc.version} + pom + import + + + + + + + io.grpc + grpc-protobuf + + + io.grpc + grpc-stub + + + io.grpc + grpc-services + + + org.apache.tomcat + annotations-api + 6.0.53 + provided + + + io.grpc + grpc-netty-shaded + runtime + + + junit + junit + 4.13.2 + test + + + io.grpc + grpc-testing + test + + + + + + + kr.motd.maven + os-maven-plugin + 1.7.1 + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.6.1 + + com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 1.4.1 + + + enforce + + enforce + + + + + + + + + + + + diff --git a/examples/example-debug/settings.gradle b/examples/example-debug/settings.gradle new file mode 100644 index 00000000000..3700c983b6c --- /dev/null +++ b/examples/example-debug/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-debug' diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java new file mode 100644 index 00000000000..61391b60415 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HelloWorldDebuggableClient.java @@ -0,0 +1,140 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.debug; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.InsecureServerCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.services.AdminInterface; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A client that creates a channelz service and then requests a greeting 50 times. + * It uses 2 channels to communicate with the server one of which is shared by 2 stubs and + * one of which has only 1 stub. The requests are split over the 3 channels. + * Once completed, there is a 30 second sleep to allow more time to run the commandline debugger. + */ +public class HelloWorldDebuggableClient { + + private static final Logger logger = Logger.getLogger(HelloWorldDebuggableClient.class.getName()); + public static final int NUM_ITERATIONS = 50; + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the specified channel. */ + public HelloWorldDebuggableClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + String user = "world"; + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + int debugPort = 51051; + // Allow passing in the user and target strings as command line arguments + if (args.length > 0) { + if ("--help".equals(args[0])) { + System.err.println("Usage: [name [target]]"); + System.err.println(""); + System.err.println(" name The name you wish to be greeted by. Defaults to " + user); + System.err.println(" target The server to connect to. Defaults to " + target); + System.exit(1); + } + user = args[0]; + } + if (args.length > 1) { + target = args[1]; + } + + // Create a pair of communication channels to the server. Channels are thread-safe + // and reusable. + ManagedChannel channel1 = Grpc.newChannelBuilder(target, + InsecureChannelCredentials.create()).build(); + ManagedChannel channel2 = Grpc.newChannelBuilder(target, + InsecureChannelCredentials.create()).build(); + Server server = null; + try { + // Create a service from which grpcdebug can request debug info + server = Grpc.newServerBuilderForPort(debugPort, InsecureServerCredentials.create()) + .addServices(AdminInterface.getStandardServices()) + .build() + .start(); + + // Create the 3 clients + HelloWorldDebuggableClient client1 = new HelloWorldDebuggableClient(channel1); + HelloWorldDebuggableClient client2 = new HelloWorldDebuggableClient(channel1); + HelloWorldDebuggableClient client3 = new HelloWorldDebuggableClient(channel2); + + // Do the client requests spreadying them over the 3 clients + for (int i=0; i < NUM_ITERATIONS; i++) { + switch (i % 3) { + case 0: + client1.greet(user); + break; + case 1: + client2.greet(user); + break; + case 2: + client3.greet(user); + break; + } + } + System.out.println("Completed " + NUM_ITERATIONS + + " requests, will now sleep for 30 seconds to give some time for command line calls"); + Thread.sleep(30000); // Give some time for running grpcdebug + } finally { + // ManagedChannels use resources like threads and TCP connections. To prevent leaking these + // resources the channel should be shut down when it will no longer be used. If it may be used + // again leave it running. + channel1.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + channel2.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + + if (server != null) { + server.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } + } +} diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java new file mode 100644 index 00000000000..89ffc39b599 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameDebuggableServer.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.debug; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.health.v1.HealthCheckResponse.ServingStatus; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.services.AdminInterface; +import io.grpc.services.HealthStatusManager; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * A server that hosts HostnameGreeter, plus the channelz service which grpcdebug uses. + */ +public final class HostnameDebuggableServer { + static int port = 50051; + static String hostname = null; + + public static void main(String[] args) throws IOException, InterruptedException { + parseArgs(args); // sets port and hostname + + final Server server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new HostnameGreeter(hostname)) + .addServices(AdminInterface.getStandardServices()) // the key add for enabling grpcdebug + .build() + .start(); + + System.out.println("Listening on port " + port); + + addShutdownHook(server); // Configures cleanup + server.awaitTermination(); // Block until shutdown + } + + private static void parseArgs(String[] args) { + if (args.length >= 1) { + try { + port = Integer.parseInt(args[0]); + } catch (NumberFormatException ex) { + System.err.println("Usage: [port [hostname]]"); + System.err.println(""); + System.err.println(" port The listen port. Defaults to " + port); + System.err.println(" hostname The name clients will see in greet responses. "); + System.err.println(" Defaults to the machine's hostname"); + System.exit(1); + } + } + if (args.length >= 2) { + hostname = args[1]; + } + } + + private static void addShutdownHook(final Server server) { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Start graceful shutdown + server.shutdown(); + try { + // Wait for RPCs to complete processing + if (!server.awaitTermination(30, TimeUnit.SECONDS)) { + // That was plenty of time. Let's cancel the remaining RPCs + server.shutdownNow(); + // shutdownNow isn't instantaneous, so give a bit of time to clean resources up + // gracefully. Normally this will be well under a second. + server.awaitTermination(5, TimeUnit.SECONDS); + } + } catch (InterruptedException ex) { + server.shutdownNow(); + } + } + }); + } +} diff --git a/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java new file mode 100644 index 00000000000..7146f30cfe6 --- /dev/null +++ b/examples/example-debug/src/main/java/io/grpc/examples/debug/HostnameGreeter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.debug; + +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** Greeter implementation which replies identifying itself with its hostname. */ +public final class HostnameGreeter extends GreeterGrpc.GreeterImplBase { + private static final Logger logger = Logger.getLogger(HostnameGreeter.class.getName()); + + private AtomicInteger callCount = new AtomicInteger(); + + private final String serverName; + + public HostnameGreeter(String serverName) { + if (serverName == null) { + serverName = determineHostname(); + } + this.serverName = serverName; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + int curCount = callCount.incrementAndGet(); + HelloReply reply = HelloReply.newBuilder() + .setMessage(String.format("Hello %s, from %s. You are requester number %d.", + req.getName(), serverName, curCount)) + .build(); + // Add a pause so that there is time to run debug commands + try { + int sleep_interval = (curCount % 10) * 100; // 0 - 1 second + Thread.sleep(sleep_interval); + } catch (InterruptedException e) { + responseObserver.onError(e); + } + // Send the response + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + + private static String determineHostname() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (IOException ex) { + logger.log(Level.INFO, "Failed to determine hostname. Will generate one", ex); + } + // Strange. Well, let's make an identifier for ourselves. + return "generated-" + new Random().nextInt(); + } +} diff --git a/examples/example-debug/src/main/proto/helloworld/helloworld.proto b/examples/example-debug/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..c60d9416f1f --- /dev/null +++ b/examples/example-debug/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2015 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 625f65c0b0b869bb38f1be5ea47f83c1c6e30f1a Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Fri, 17 Mar 2023 16:27:33 -0700 Subject: [PATCH 12/16] examples: add keepalive example (#9956) --- examples/README.md | 2 + examples/build.gradle | 16 +++ examples/logging.properties | 8 ++ .../examples/keepalive/KeepAliveClient.java | 95 ++++++++++++++ .../examples/keepalive/KeepAliveServer.java | 118 ++++++++++++++++++ 5 files changed, 239 insertions(+) create mode 100644 examples/logging.properties create mode 100644 examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java create mode 100644 examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java diff --git a/examples/README.md b/examples/README.md index 94ca35b6cf1..ee43667a326 100644 --- a/examples/README.md +++ b/examples/README.md @@ -119,6 +119,8 @@ before trying out the examples. +- [Keep Alive](src/main/java/io/grpc/examples/keepalive) + ### To build the examples 1. **[Install gRPC Java library SNAPSHOT locally, including code generation plugin](../COMPILING.md) (Only need this step for non-released versions, e.g. master HEAD).** diff --git a/examples/build.gradle b/examples/build.gradle index e5ad7e131ca..333ba1f890d 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,6 +167,7 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } +<<<<<<< HEAD task multiplexingServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' applicationName = 'multiplexing-server' @@ -174,12 +175,25 @@ task multiplexingServer(type: CreateStartScripts) { classpath = startScripts.classpath } +task keepAliveServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' + applicationName = 'keep-alive-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + task sharingClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.SharingClient' applicationName = 'sharing-client' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } +task keepAliveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' + applicationName = 'keep-alive-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} applicationDistribution.into('bin') { from(routeGuideServer) @@ -199,5 +213,7 @@ applicationDistribution.into('bin') { from(nameResolveClient) from(multiplexingServer) from(sharingClient) + from(keepAliveServer) + from(keepAliveClient) fileMode = 0755 } diff --git a/examples/logging.properties b/examples/logging.properties new file mode 100644 index 00000000000..b807613adcd --- /dev/null +++ b/examples/logging.properties @@ -0,0 +1,8 @@ +# Create a file called logging.properties with the following contents. +handlers=java.util.logging.ConsoleHandler +io.grpc.level=FINE +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter + +# Pass the location of the file to JVM via this command-line flag +JAVA_OPTS=-Djava.util.logging.config.file=logging.properties diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java new file mode 100644 index 00000000000..a7c59c3952f --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.keepalive; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A simple client that requests a greeting from the {@link KeepAliveServer}. + */ +public class KeepAliveClient { + private static final Logger logger = Logger.getLogger(KeepAliveClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public KeepAliveClient(Channel channel) { + // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to + // shut it down. + + // Passing Channels to code makes code easier to test and makes it easier to reuse Channels. + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public void greet(String name) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return; + } + logger.info("Greeting: " + response.getMessage()); + } + + /** + * Greet server. + */ + public static void main(String[] args) throws Exception { + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + + // Create a channel with the following keep alive configurations (demo only, you should set + // more appropriate values based on your environment): + // keepAliveTime: Send pings every 10 seconds if there is no activity. Set to an appropriate + // value in reality, e.g. (5, TimeUnit.MINUTES). + // keepAliveTimeout: Wait 1 second for ping ack before considering the connection dead. Set to a + // larger value in reality, e.g. (10, TimeUnit.SECONDS). You should only set such a small value, + // e.g. (1, TimeUnit.SECONDS) in certain low latency environments. + // keepAliveWithoutCalls: Send pings even without active streams. Normally disable it. + // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see the keep alive ping + // frames. + // More details see: https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .keepAliveTime(5, TimeUnit.MINUTES) + .keepAliveTime(10, TimeUnit.SECONDS) // Change to a larger value, e.g. 5min. + .keepAliveTimeout(1, TimeUnit.SECONDS) // Change to a larger value, e.g. 10s. + .keepAliveWithoutCalls(true)// You should normally avoid enabling this. + .build(); + + try { + KeepAliveClient client = new KeepAliveClient(channel); + client.greet("Keep-alive Demo"); + Thread.sleep(30000); + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java new file mode 100644 index 00000000000..884bbfea532 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.keepalive; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Server that manages startup/shutdown of a keep alive server. + */ +public class KeepAliveServer { + private static final Logger logger = Logger.getLogger(KeepAliveServer.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + + // Start a server with the following configurations (demo only, you should set more appropriate + // values based on your real environment): + // keepAliveTime: Ping the client if it is idle for 5 seconds to ensure the connection is + // still active. Set to an appropriate value in reality, e.g. in minutes. + // keepAliveTimeout: Wait 1 second for the ping ack before assuming the connection is dead. + // Set to an appropriate value in reality, e.g. (10, TimeUnit.SECONDS). + // permitKeepAliveTime: If a client pings more than once every 5 seconds, terminate the + // connection. + // permitKeepAliveWithoutCalls: Allow pings even when there are no active streams. + // maxConnectionIdle: If a client is idle for 15 seconds, send a GOAWAY. + // maxConnectionAge: If any connection is alive for more than 30 seconds, send a GOAWAY. + // maxConnectionAgeGrace: Allow 5 seconds for pending RPCs to complete before forcibly closing + // connections. + // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see keep alive ping + // frames. + // More details see: https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new GreeterImpl()) + .keepAliveTime(5, TimeUnit.SECONDS) + .keepAliveTimeout(1, TimeUnit.SECONDS) + .permitKeepAliveTime(5, TimeUnit.SECONDS) + .permitKeepAliveWithoutCalls(true) + .maxConnectionIdle(15, TimeUnit.SECONDS) + .maxConnectionAge(30, TimeUnit.SECONDS) + .maxConnectionAgeGrace(5, TimeUnit.SECONDS) + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + KeepAliveServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final KeepAliveServer server = new KeepAliveServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} From 267e403b3047b24c6da764665985e0ba2dc216cd Mon Sep 17 00:00:00 2001 From: Terry Wilson Date: Fri, 17 Mar 2023 19:39:04 -0700 Subject: [PATCH 13/16] examples: deadline example (#9958) This provides an example on how a client can specify a deadline for an RPC. Also covers how deadlines are propagated to further RPCs a server might make. --- examples/BUILD.bazel | 17 +++ examples/build.gradle | 22 +++- .../examples/deadline/DeadlineClient.java | 110 ++++++++++++++++ .../examples/deadline/DeadlineServer.java | 119 ++++++++++++++++++ 4 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java create mode 100644 examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 2634f56fdc3..98b5c223ed3 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -190,3 +190,20 @@ java_binary( ], ) +java_binary( + name = "deadline-server", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineServer", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "deadline-client", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineClient", + runtime_deps = [ + ":examples", + ], +) diff --git a/examples/build.gradle b/examples/build.gradle index 333ba1f890d..3c6aec2252f 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,10 +167,15 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } -<<<<<<< HEAD +//<<<<<<< HEAD task multiplexingServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' applicationName = 'multiplexing-server' +//======= +task deadlineServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineServer' + applicationName = 'deadline-server' +//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } @@ -182,12 +187,22 @@ task keepAliveServer(type: CreateStartScripts) { classpath = startScripts.classpath } +//<<<<<<< HEAD task sharingClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.SharingClient' applicationName = 'sharing-client' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } +//======= +task deadlineClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineClient' + applicationName = 'deadline-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) task keepAliveClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' applicationName = 'keep-alive-client' @@ -211,8 +226,13 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) +//<<<<<<< HEAD from(multiplexingServer) from(sharingClient) +//======= + from(deadlineServer) + from(deadlineClient) +//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) from(keepAliveServer) from(keepAliveClient) fileMode = 0755 diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java new file mode 100644 index 00000000000..378688ee1cc --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.deadline; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.helloworld.HelloWorldServer; +import java.util.concurrent.TimeUnit; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * A simple client that requests a greeting from the {@link HelloWorldServer}. + * + *

This is based off the client in the helloworld example with some deadline logic added. + */ +public class DeadlineClient { + private static final Logger logger = Logger.getLogger(DeadlineClient.class.getName()); + + private final GreeterGrpc.GreeterBlockingStub blockingStub; + + /** Construct client for accessing HelloWorld server using the existing channel. */ + public DeadlineClient(Channel channel) { + blockingStub = GreeterGrpc.newBlockingStub(channel); + } + + /** Say hello to server. */ + public Status greet(String name, long timeoutMillis) { + logger.info("Will try to greet " + name + " ..."); + HelloRequest request = HelloRequest.newBuilder().setName(name).build(); + HelloReply response; + try { + response = blockingStub.withDeadlineAfter(timeoutMillis, TimeUnit.MILLISECONDS) + .sayHello(request); + } catch (StatusRuntimeException e) { + logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus()); + return e.getStatus(); + } + logger.info("Greeting: " + response.getMessage()); + return Status.OK; + } + + /** + * Greet server. If provided, the first element of {@code args} is the name to use in the + * greeting. The second argument is the target server. + */ + public static void main(String[] args) throws Exception { + System.setProperty("java.util.logging.SimpleFormatter.format", + "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); + + // Access a service running on the local machine on port 50051 + String target = "localhost:50051"; + + ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()) + .build(); + try { + DeadlineClient client = new DeadlineClient(channel); + + // The server takes 500ms to process the call, so setting a deadline further in the future we + // should get a successful response. + logger.info("Calling server with a generous deadline, expected to work"); + client.greet("deadline client", 1000); + + // A smaller deadline will result in us getting a DEADLINE_EXCEEDED error. + logger.info( + "Calling server with an unrealistic (300ms) deadline, expecting a DEADLINE_EXCEEDED"); + client.greet("deadline client", 300); + + // Including the "propagate" magic string in the request will cause the server to call itself + // to simulate a situation where a server needs to call another server to satisfy the original + // request. This will double the time it takes to respond to the client request, but with + // an increased deadline we should get a successful response. + logger.info("Calling server with propagation and a generous deadline, expected to work"); + client.greet("deadline client [propagate]", 2000); + + // With this propagated call we reduce the deadline making it impossible for the both the + // first server call and the propagated one to succeed. You should see the call fail with + // DEADLINE_EXCEEDED, and you should also see DEADLINE_EXCEEDED in the server output as it + // runs out of time waiting for the propagated call to finish. + logger.info( + "Calling server with propagation and a generous deadline, expecting a DEADLINE_EXCEEDED"); + client.greet("deadline client [propagate]", 1000); + } finally { + channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); + } + } +} diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java new file mode 100644 index 00000000000..49214fc77d5 --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java @@ -0,0 +1,119 @@ + +/* + * Copyright 2023 The gRPC Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.grpc.examples.deadline; + +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +public class DeadlineServer { + private static final Logger logger = Logger.getLogger(DeadlineServer.class.getName()); + + private Server server; + + + private void start() throws IOException { + int port = 50051; + SlowGreeter slowGreeter = new SlowGreeter(); + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(slowGreeter) + .build() + .start(); + logger.info("Server started, listening on " + port); + + // Create a channel to this same server so we can make a recursive call to demonstrate deadline + // propagation. + String target = "localhost:50051"; + slowGreeter.setClientStub(GreeterGrpc.newBlockingStub( + Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build())); + + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + DeadlineServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + System.setProperty("java.util.logging.SimpleFormatter.format", + "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n"); + + final DeadlineServer server = new DeadlineServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class SlowGreeter extends GreeterGrpc.GreeterImplBase { + private GreeterGrpc.GreeterBlockingStub clientStub; + + void setClientStub(GreeterGrpc.GreeterBlockingStub clientStub) { + this.clientStub = clientStub; + } + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + + if (req.getName().contains("propagate")) { + clientStub.sayHello(HelloRequest.newBuilder().setName("Server").build()); + } + + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} From 7d605ffde8d2f2df9742983f151956656c1d0f4e Mon Sep 17 00:00:00 2001 From: yifeizhuang Date: Mon, 20 Mar 2023 08:54:44 -0700 Subject: [PATCH 14/16] examples: add reflection example (#9955) --- examples/example-reflection/README.md | 106 ++++++++++++++++++ examples/example-reflection/build.gradle | 54 +++++++++ examples/example-reflection/settings.gradle | 1 + .../examples/reflection/ReflectionServer.java | 81 +++++++++++++ .../main/proto/helloworld/helloworld.proto | 37 ++++++ 5 files changed, 279 insertions(+) create mode 100644 examples/example-reflection/README.md create mode 100644 examples/example-reflection/build.gradle create mode 100644 examples/example-reflection/settings.gradle create mode 100644 examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java create mode 100644 examples/example-reflection/src/main/proto/helloworld/helloworld.proto diff --git a/examples/example-reflection/README.md b/examples/example-reflection/README.md new file mode 100644 index 00000000000..9bd91f3edb0 --- /dev/null +++ b/examples/example-reflection/README.md @@ -0,0 +1,106 @@ +gRPC Reflection Example +================ + +The reflection example has a Hello World server with `ProtoReflectionService` registered. + +### Build the example + +To build the example server, from the `grpc-java/examples/examples-reflection` +directory: +``` +$ ../gradlew installDist +``` + +This creates the scripts `build/install/example-reflection/bin/reflection-server`. + +### Run the example + +gRPC Server Reflection provides information about publicly-accessible gRPC services on a server, +and assists clients at runtime to construct RPC requests and responses without precompiled +service information. It is used by gRPCurl, which can be used to introspect server protos and +send/receive test RPCs. + +1. To start the reflection example server on its default port of 50051, run: +``` +$ ./build/install/example-reflection/bin/reflection-server +``` + +2. After enabling Server Reflection in a server application, you can use gRPCurl to check its +services. Instructions on how to install and use gRPCurl can be found at [gRPCurl Installation](https://github.com/fullstorydev/grpcurl#installation) + +After installing gRPCurl, open a new terminal and run the commands from the new terminal. + +### List all the services exposed at a given port + + ``` + $ grpcurl -plaintext localhost:50051 list + ``` + +Output + + ``` + grpc.reflection.v1alpha.ServerReflection + helloworld.Greeter + ``` + +### List all the methods of a service + ``` + $ grpcurl -plaintext localhost:50051 helloworld.Greeter + ``` +Output + ``` + helloworld.Greeter.SayHello + ``` + +### Describe services and methods + +The describe command inspects a method given its full name(in the format of +`..`). + + ``` +$ grpcurl -plaintext localhost:50051 describe helloworld.Greeter.SayHello + ``` + +Output + + ``` + helloworld.Greeter.SayHello is a method: + rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply ); + ``` + +### Inspect message types + +We can use the describe command to inspect request/response types given the full name of the type +(in the format of `.`). + +Get information about the request type: + + ``` +$ grpcurl -plaintext localhost:50051 describe helloworld.HelloRequest + ``` + +Output + + ``` + helloworld.HelloRequest is a message: + message HelloRequest { + string name = 1; + } + ``` + +### Call a remote method + +We can send RPCs to a server and get responses using the full method name +(in the format of `..`). The `-d ` flag represents the request data +and the -format text flag indicates that the request data is in text format. + + ``` + $ grpcurl -plaintext -format text -d 'name: "gRPCurl"' \ + localhost:50051 helloworld.Greeter.SayHello + ``` + +Output + + ``` + message: "Hello gRPCurl" + ``` diff --git a/examples/example-reflection/build.gradle b/examples/example-reflection/build.gradle new file mode 100644 index 00000000000..d6aee016c16 --- /dev/null +++ b/examples/example-reflection/build.gradle @@ -0,0 +1,54 @@ +plugins { + id 'application' // Provide convenience executables for trying out the examples. + // ASSUMES GRADLE 5.6 OR HIGHER. Use plugin version 0.8.10 with earlier gradle versions + id 'com.google.protobuf' version '0.8.17' + // Generate IntelliJ IDEA's .idea & .iml project files + id 'idea' + id 'java' +} + +repositories { + maven { // The google mirror is less flaky than mavenCentral() + url "https://maven-central.storage-download.googleapis.com/maven2/" } + mavenCentral() + mavenLocal() +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +def grpcVersion = '1.55.0-SNAPSHOT' // CURRENT_GRPC_VERSION +def protocVersion = '3.21.7' + +dependencies { + implementation "io.grpc:grpc-protobuf:${grpcVersion}" + implementation "io.grpc:grpc-services:${grpcVersion}" + implementation "io.grpc:grpc-stub:${grpcVersion}" + implementation "io.grpc:grpc-netty-shaded:${grpcVersion}" + compileOnly "org.apache.tomcat:annotations-api:6.0.53" + +} + +protobuf { + protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" } + plugins { + grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" } + } + generateProtoTasks { + all()*.plugins { grpc {} } + } +} + +startScripts.enabled = false + +task ReflectionServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.reflection.ReflectionServer' + applicationName = 'reflection-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + +applicationDistribution.into('bin') { + from(ReflectionServer) + fileMode = 0755 +} diff --git a/examples/example-reflection/settings.gradle b/examples/example-reflection/settings.gradle new file mode 100644 index 00000000000..dccb973085e --- /dev/null +++ b/examples/example-reflection/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'example-reflection' diff --git a/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java b/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java new file mode 100644 index 00000000000..ad702247ba7 --- /dev/null +++ b/examples/example-reflection/src/main/java/io/grpc/examples/reflection/ReflectionServer.java @@ -0,0 +1,81 @@ + +package io.grpc.examples.reflection; + +import io.grpc.Grpc; +import io.grpc.InsecureServerCredentials; +import io.grpc.Server; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.protobuf.services.ProtoReflectionService; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Logger; + +/** + * Server that manages startup/shutdown of a {@code Greeter} server. + */ +public class ReflectionServer { + private static final Logger logger = Logger.getLogger(ReflectionServer.class.getName()); + + private Server server; + + private void start() throws IOException { + /* The port on which the server should run */ + int port = 50051; + server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create()) + .addService(new GreeterImpl()) + .addService(ProtoReflectionService.newInstance()) // add reflection service + .build() + .start(); + logger.info("Server started, listening on " + port); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + try { + ReflectionServer.this.stop(); + } catch (InterruptedException e) { + e.printStackTrace(System.err); + } + System.err.println("*** server shut down"); + } + }); + } + + private void stop() throws InterruptedException { + if (server != null) { + server.shutdown().awaitTermination(30, TimeUnit.SECONDS); + } + } + + /** + * Await termination on the main thread since the grpc library uses daemon threads. + */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** + * Main launches the server from the command line. + */ + public static void main(String[] args) throws IOException, InterruptedException { + final ReflectionServer server = new ReflectionServer(); + server.start(); + server.blockUntilShutdown(); + } + + static class GreeterImpl extends GreeterGrpc.GreeterImplBase { + + @Override + public void sayHello(HelloRequest req, StreamObserver responseObserver) { + HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + } +} diff --git a/examples/example-reflection/src/main/proto/helloworld/helloworld.proto b/examples/example-reflection/src/main/proto/helloworld/helloworld.proto new file mode 100644 index 00000000000..77184aa8326 --- /dev/null +++ b/examples/example-reflection/src/main/proto/helloworld/helloworld.proto @@ -0,0 +1,37 @@ +// Copyright 2022 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.examples.helloworld"; +option java_outer_classname = "HelloWorldProto"; +option objc_class_prefix = "HLW"; + +package helloworld; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} From 4e6596940a030844adca278385490c92834fc3b9 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Fri, 17 Mar 2023 17:38:23 -0700 Subject: [PATCH 15/16] examples: client and server sharing example --- examples/build.gradle | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples/build.gradle b/examples/build.gradle index 3c6aec2252f..180f012f7ee 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -180,6 +180,13 @@ task deadlineServer(type: CreateStartScripts) { classpath = startScripts.classpath } +task multiplexingServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' + applicationName = 'multiplexing-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + task keepAliveServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' applicationName = 'keep-alive-server' @@ -209,6 +216,12 @@ task keepAliveClient(type: CreateStartScripts) { outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } +task sharingClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.SharingClient' + applicationName = 'sharing-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} applicationDistribution.into('bin') { from(routeGuideServer) @@ -235,5 +248,7 @@ applicationDistribution.into('bin') { //>>>>>>> dc313f2e4 (examples: deadline example (#9958)) from(keepAliveServer) from(keepAliveClient) + from(multiplexing-server) + from(sharing-client) fileMode = 0755 } From abf5102eafd88a2bab29030bff98ac876c8ac4c0 Mon Sep 17 00:00:00 2001 From: larry-safran Date: Mon, 20 Mar 2023 21:20:24 +0000 Subject: [PATCH 16/16] fix rebase --- examples/build.gradle | 47 ++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/examples/build.gradle b/examples/build.gradle index 180f012f7ee..4b45e134547 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,41 +167,27 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } -//<<<<<<< HEAD task multiplexingServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' applicationName = 'multiplexing-server' -//======= -task deadlineServer(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.deadline.DeadlineServer' - applicationName = 'deadline-server' -//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } -task multiplexingServer(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.multiplex.MultiplexingServer' - applicationName = 'multiplexing-server' +task sharingClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.multiplex.SharingClient' + applicationName = 'sharing-client' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } -task keepAliveServer(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' - applicationName = 'keep-alive-server' +task deadlineServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineServer' + applicationName = 'deadline-server' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } -//<<<<<<< HEAD -task sharingClient(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.multiplex.SharingClient' - applicationName = 'sharing-client' - outputDir = new File(project.buildDir, 'tmp/scripts/' + name) - classpath = startScripts.classpath -} -//======= task deadlineClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.deadline.DeadlineClient' applicationName = 'deadline-client' @@ -209,16 +195,16 @@ task deadlineClient(type: CreateStartScripts) { classpath = startScripts.classpath } -//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) -task keepAliveClient(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' - applicationName = 'keep-alive-client' +task keepAliveServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' + applicationName = 'keep-alive-server' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } -task sharingClient(type: CreateStartScripts) { - mainClass = 'io.grpc.examples.multiplex.SharingClient' - applicationName = 'sharing-client' + +task keepAliveClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' + applicationName = 'keep-alive-client' outputDir = new File(project.buildDir, 'tmp/scripts/' + name) classpath = startScripts.classpath } @@ -239,16 +225,13 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) -//<<<<<<< HEAD from(multiplexingServer) from(sharingClient) -//======= from(deadlineServer) from(deadlineClient) -//>>>>>>> dc313f2e4 (examples: deadline example (#9958)) from(keepAliveServer) from(keepAliveClient) - from(multiplexing-server) - from(sharing-client) + from(multiplexingServer) + from(sharingClient) fileMode = 0755 }