├── .gitignore ├── .travis.yml ├── README.md ├── eureka-server ├── pom.xml └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── exampledriven │ │ │ └── eureka │ │ │ └── customer │ │ │ └── server │ │ │ └── Application.java │ └── resources │ │ └── application.yml │ └── test │ └── java │ └── org │ └── exampledriven │ └── eureka │ └── customer │ └── shared │ └── ApplicationTests.java ├── jmeter └── rest-vs-grpc.jmx ├── pom.xml ├── spring-boot-grpc-client-Example2 ├── pom.xml └── src │ └── main │ ├── java │ └── route_guide │ │ └── RouteGuideClient.java │ ├── proto │ └── route_guide.proto │ └── resources │ └── application.properties ├── spring-boot-grpc-client ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── exampledriven │ │ ├── SpringBootGrpcClientApplication.java │ │ ├── grpc │ │ ├── BookGrpcController.java │ │ ├── BookServiceGrpcClient.java │ │ └── TestDataUtil.java │ │ └── rest │ │ ├── Book.java │ │ ├── BookRestController.java │ │ └── TestDataUtil.java │ ├── proto │ └── BookService.proto │ └── resources │ └── application.yml ├── spring-boot-grpc-server-Example2 ├── pom.xml └── src │ └── main │ ├── java │ └── route_guide │ │ ├── RouteGuideServer.java │ │ ├── RouteGuideService.java │ │ └── RouteGuideUtil.java │ ├── proto │ └── route_guide.proto │ └── resources │ └── application.properties └── spring-boot-grpc-server ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── exampledriven │ │ ├── BookGrpcService.java │ │ ├── BookUtil.java │ │ ├── GrpcExampleApplication.java │ │ └── rest │ │ ├── Book.java │ │ └── BooksRestController.java ├── proto │ └── BookService.proto └── resources │ └── application.yml └── test └── java └── org └── exampledriven └── GrpcExampleApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk8 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/ExampleDriven/spring-boot-grpc-example.svg?branch=master)](https://travis-ci.org/ExampleDriven/spring-boot-grpc-example) 2 | 3 | ## Overview 4 | 5 | Example project to demostraing spring-boot integration with gRpc. Additonal to a gRpc client and server it has a traditional Spring MVC rest client using very similar payload. The performance of the two technologies can be compared usin the included JMeter file. 6 | 7 | ## Test URLs 8 | 9 | Description | URL 10 | --- | --- 11 | GRPC client test compact output | http://localhost:8080/test_grpc?compact=true 12 | GRPC client test verbose output | http://localhost:8080/test_grpc 13 | REST client test compact output | http://localhost:8080/test_rest/compact 14 | REST client test verbose output | http://localhost:8080/test_rest 15 | 16 | ## How to measure performance 17 | - The jmeter directory contains the jmeter test definition 18 | - use the compact endpoints 19 | - To eliminate "noise" turn off logging by commenting out the appropriate lines in application.yaml both for the server and the client 20 | 21 | 22 | ## Useful resources 23 | 24 | - https://www.youtube.com/watch?v=xpmFhTMqWhc 25 | - http://www.ustream.tv/recorded/86187859 26 | - https://github.com/LogNet/grpc-spring-boot-starter 27 | - http://www.grpc.io/docs/ 28 | -------------------------------------------------------------------------------- /eureka-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | eureka-server 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | Eureka Server 11 | Eureka Server 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 1.4.1.RELEASE 17 | 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-eureka-server 23 | 24 | 25 | junit 26 | junit 27 | 4.13.1 28 | test 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.springframework.cloud 41 | spring-cloud-dependencies 42 | Camden.RELEASE 43 | pom 44 | import 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | org.springframework.boot 53 | spring-boot-maven-plugin 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-compiler-plugin 58 | 3.3 59 | 60 | 1.8 61 | 1.8 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /eureka-server/src/main/java/org/exampledriven/eureka/customer/server/Application.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.eureka.customer.server; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 7 | 8 | @SpringBootApplication 9 | @EnableEurekaServer 10 | @EnableDiscoveryClient 11 | public class Application { 12 | 13 | public static void main(String[] args) throws Exception { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /eureka-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | eureka: 5 | client: 6 | registerWithEureka: false 7 | fetchRegistry: false 8 | server: 9 | enableSelfPreservation: false -------------------------------------------------------------------------------- /eureka-server/src/test/java/org/exampledriven/eureka/customer/shared/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.eureka.customer.shared; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Map; 6 | 7 | import org.exampledriven.eureka.customer.server.Application; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.context.embedded.LocalServerPort; 13 | import org.springframework.boot.test.IntegrationTest; 14 | import org.springframework.boot.test.SpringApplicationConfiguration; 15 | import org.springframework.boot.test.web.client.TestRestTemplate; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.http.HttpStatus; 18 | import org.springframework.http.ResponseEntity; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | import org.springframework.test.context.web.WebAppConfiguration; 21 | 22 | @RunWith(SpringJUnit4ClassRunner.class) 23 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class) 24 | public class ApplicationTests { 25 | 26 | @Value("${local.server.port}") 27 | private int port = 9999; 28 | 29 | @Test 30 | public void catalogLoads() { 31 | @SuppressWarnings("rawtypes") 32 | ResponseEntity entity = new TestRestTemplate().getForEntity("http://localhost:" + port + "/eureka/apps", Map.class); 33 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 34 | } 35 | 36 | @Test 37 | public void adminLoads() { 38 | @SuppressWarnings("rawtypes") 39 | ResponseEntity entity = new TestRestTemplate().getForEntity("http://localhost:" + port + "/env", Map.class); 40 | assertEquals(HttpStatus.OK, entity.getStatusCode()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /jmeter/rest-vs-grpc.jmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | continue 16 | 17 | false 18 | 10 19 | 20 | 20 21 | 10 22 | 1475835197000 23 | 1475835197000 24 | false 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | localhost 34 | 8080 35 | 36 | 37 | 38 | 39 | test_rest/compact 40 | GET 41 | true 42 | false 43 | true 44 | false 45 | false 46 | 47 | 48 | 49 | 50 | 51 | 200 52 | 53 | Assertion.response_code 54 | false 55 | 2 56 | 57 | 58 | 59 | 60 | SUCCESS 61 | 62 | Assertion.response_data 63 | false 64 | 8 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | localhost 73 | 8080 74 | 75 | 76 | 77 | 78 | /test_grpc?compact=true 79 | GET 80 | true 81 | false 82 | true 83 | false 84 | false 85 | 86 | 87 | 88 | 89 | 90 | 200 91 | 92 | Assertion.response_code 93 | false 94 | 2 95 | 96 | 97 | 98 | 99 | SUCCESS 100 | 101 | Assertion.response_data 102 | false 103 | 8 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | localhost 112 | 8083 113 | 114 | 115 | 116 | 117 | /test_thrift?compact=true 118 | GET 119 | true 120 | false 121 | true 122 | false 123 | false 124 | 125 | 126 | 127 | 128 | 129 | 200 130 | 131 | Assertion.response_code 132 | false 133 | 2 134 | 135 | 136 | 137 | 138 | SUCCESS 139 | 140 | Assertion.response_data 141 | false 142 | 8 143 | 144 | 145 | 146 | 147 | false 148 | 149 | saveConfig 150 | 151 | 152 | true 153 | true 154 | true 155 | 156 | true 157 | true 158 | true 159 | true 160 | false 161 | true 162 | true 163 | false 164 | false 165 | false 166 | true 167 | false 168 | false 169 | false 170 | true 171 | 0 172 | true 173 | true 174 | true 175 | 176 | 177 | 178 | 179 | 180 | 181 | false 182 | 183 | saveConfig 184 | 185 | 186 | true 187 | true 188 | true 189 | 190 | true 191 | true 192 | true 193 | true 194 | false 195 | true 196 | true 197 | false 198 | false 199 | false 200 | true 201 | false 202 | false 203 | false 204 | true 205 | 0 206 | true 207 | true 208 | true 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.exampledriven 7 | spring-boot-grpc-example 8 | 0.0.1-SNAPSHOT 9 | pom 10 | Spring grpc example aggregator 11 | 12 | 13 | spring-boot-grpc-server 14 | spring-boot-grpc-client 15 | eureka-server 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /spring-boot-grpc-client-Example2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.4.RELEASE 9 | 10 | 11 | com.dfs 12 | dfs 13 | 0.0.1-SNAPSHOT 14 | Distributed File Server 15 | Secure RPC Distributed File Server 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | 29 | 30 | io.grpc 31 | grpc-netty 32 | 1.32.1 33 | 34 | 35 | io.grpc 36 | grpc-protobuf 37 | 1.32.1 38 | 39 | 40 | io.grpc 41 | grpc-stub 42 | 1.32.1 43 | 44 | 45 | org.apache.tomcat 46 | annotations-api 47 | 6.0.53 48 | provided 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.junit.vintage 59 | junit-vintage-engine 60 | 61 | 62 | 63 | 64 | 65 | 66 | com.google.protobuf 67 | protobuf-java-util 68 | 4.0.0-rc-2 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-maven-plugin 81 | 82 | 83 | 84 | org.xolstice.maven.plugins 85 | protobuf-maven-plugin 86 | 0.6.1 87 | 88 | com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} 89 | grpc-java 90 | io.grpc:protoc-gen-grpc-java:1.32.1:exe:${os.detected.classifier} 91 | 92 | 93 | 94 | 95 | compile 96 | compile-custom 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | kr.motd.maven 105 | os-maven-plugin 106 | 1.6.2 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /spring-boot-grpc-client-Example2/src/main/java/route_guide/RouteGuideClient.java: -------------------------------------------------------------------------------- 1 | package route_guide; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.protobuf.Message; 5 | import io.grpc.Channel; 6 | import io.grpc.ManagedChannel; 7 | import io.grpc.ManagedChannelBuilder; 8 | import io.grpc.Status; 9 | import io.grpc.StatusRuntimeException; 10 | import io.grpc.stub.StreamObserver; 11 | 12 | import java.io.IOException; 13 | import java.util.List; 14 | 15 | import java.util.Iterator; 16 | import java.util.Random; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.TimeUnit; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | 22 | /** 23 | * Sample client code that makes gRPC calls to the server. 24 | */ 25 | public class RouteGuideClient { 26 | private static final Logger logger = Logger.getLogger(RouteGuideClient.class.getName()); 27 | 28 | private final RouteGuideGrpc.RouteGuideBlockingStub blockingStub; 29 | private final RouteGuideGrpc.RouteGuideStub asyncStub; 30 | 31 | private Random random = new Random(); 32 | private TestHelper testHelper; 33 | 34 | /** Construct client for accessing RouteGuide server using the existing channel. */ 35 | public RouteGuideClient(Channel channel) { 36 | blockingStub = RouteGuideGrpc.newBlockingStub(channel); 37 | asyncStub = RouteGuideGrpc.newStub(channel); 38 | } 39 | 40 | /** 41 | * Blocking unary call example. Calls getFeature and prints the response. 42 | */ 43 | public void getFeature(int lat, int lon) { 44 | info("*** GetFeature: lat={0} lon={1}", lat, lon); 45 | 46 | Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build(); 47 | 48 | Feature feature; 49 | try { 50 | feature = blockingStub.getFeature(request); 51 | if (testHelper != null) { 52 | testHelper.onMessage(feature); 53 | } 54 | } catch (StatusRuntimeException e) { 55 | warning("RPC failed: {0}", e.getStatus()); 56 | if (testHelper != null) { 57 | testHelper.onRpcError(e); 58 | } 59 | return; 60 | } 61 | if (RouteGuideUtil.exists(feature)) { 62 | info("Found feature called \"{0}\" at {1}, {2}", 63 | feature.getName(), 64 | RouteGuideUtil.getLatitude(feature.getLocation()), 65 | RouteGuideUtil.getLongitude(feature.getLocation())); 66 | } else { 67 | info("Found no feature at {0}, {1}", 68 | RouteGuideUtil.getLatitude(feature.getLocation()), 69 | RouteGuideUtil.getLongitude(feature.getLocation())); 70 | } 71 | } 72 | 73 | /** 74 | * Blocking server-streaming example. Calls listFeatures with a rectangle of interest. Prints each 75 | * response feature as it arrives. 76 | */ 77 | public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon) { 78 | info("*** ListFeatures: lowLat={0} lowLon={1} hiLat={2} hiLon={3}", lowLat, lowLon, hiLat, 79 | hiLon); 80 | 81 | Rectangle request = 82 | Rectangle.newBuilder() 83 | .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build()) 84 | .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build(); 85 | Iterator features; 86 | try { 87 | features = blockingStub.listFeatures(request); 88 | for (int i = 1; features.hasNext(); i++) { 89 | Feature feature = features.next(); 90 | info("Result #" + i + ": {0}", feature); 91 | if (testHelper != null) { 92 | testHelper.onMessage(feature); 93 | } 94 | } 95 | } catch (StatusRuntimeException e) { 96 | warning("RPC failed: {0}", e.getStatus()); 97 | if (testHelper != null) { 98 | testHelper.onRpcError(e); 99 | } 100 | } 101 | } 102 | 103 | /** 104 | * Async client-streaming example. Sends {@code numPoints} randomly chosen points from {@code 105 | * features} with a variable delay in between. Prints the statistics when they are sent from the 106 | * server. 107 | */ 108 | public void recordRoute(List features, int numPoints) throws InterruptedException { 109 | info("*** RecordRoute"); 110 | final CountDownLatch finishLatch = new CountDownLatch(1); 111 | StreamObserver responseObserver = new StreamObserver <>() { 112 | @Override 113 | public void onNext(RouteSummary summary) { 114 | info("Finished trip with {0} points. Passed {1} features. " 115 | + "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(), 116 | summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime()); 117 | if(testHelper != null) { 118 | testHelper.onMessage(summary); 119 | } 120 | } 121 | 122 | @Override 123 | public void onError(Throwable t) { 124 | warning("RecordRoute Failed: {0}", Status.fromThrowable(t)); 125 | if(testHelper != null) { 126 | testHelper.onRpcError(t); 127 | } 128 | finishLatch.countDown(); 129 | } 130 | 131 | @Override 132 | public void onCompleted() { 133 | info("Finished RecordRoute"); 134 | finishLatch.countDown(); 135 | } 136 | }; 137 | 138 | StreamObserver requestObserver = asyncStub.recordRoute(responseObserver); 139 | try { 140 | // Send numPoints points randomly selected from the features list. 141 | for (int i = 0; i < numPoints; ++i) { 142 | int index = random.nextInt(features.size()); 143 | Point point = features.get(index).getLocation(); 144 | info("Visiting point {0}, {1}", RouteGuideUtil.getLatitude(point), 145 | RouteGuideUtil.getLongitude(point)); 146 | requestObserver.onNext(point); 147 | // Sleep for a bit before sending the next one. 148 | Thread.sleep(random.nextInt(1000) + 500); 149 | if (finishLatch.getCount() == 0) { 150 | // RPC completed or error before we finished sending. 151 | // Sending further requests won't error, but they will just be thrown away. 152 | return; 153 | } 154 | } 155 | } catch (RuntimeException e) { 156 | // Cancel RPC 157 | requestObserver.onError(e); 158 | throw e; 159 | } 160 | // Mark the end of requests 161 | requestObserver.onCompleted(); 162 | 163 | // Receiving happens asynchronously 164 | if (!finishLatch.await(1, TimeUnit.MINUTES)) { 165 | warning("recordRoute can not finish within 1 minutes"); 166 | } 167 | } 168 | 169 | /** 170 | * Bi-directional example, which can only be asynchronous. Send some chat messages, and print any 171 | * chat messages that are sent from the server. 172 | */ 173 | public CountDownLatch routeChat() { 174 | info("*** RouteChat"); 175 | final CountDownLatch finishLatch = new CountDownLatch(1); 176 | StreamObserver requestObserver = 177 | asyncStub.routeChat(new StreamObserver <>() { 178 | @Override 179 | public void onNext(RouteNote note) { 180 | info("Got message \"{0}\" at {1}, {2}", note.getMessage(), note.getLocation() 181 | .getLatitude(), note.getLocation().getLongitude()); 182 | if(testHelper != null) { 183 | testHelper.onMessage(note); 184 | } 185 | } 186 | 187 | @Override 188 | public void onError(Throwable t) { 189 | warning("RouteChat Failed: {0}", Status.fromThrowable(t)); 190 | if(testHelper != null) { 191 | testHelper.onRpcError(t); 192 | } 193 | finishLatch.countDown(); 194 | } 195 | 196 | @Override 197 | public void onCompleted() { 198 | info("Finished RouteChat"); 199 | finishLatch.countDown(); 200 | } 201 | }); 202 | 203 | try { 204 | RouteNote[] requests = 205 | {newNote("First message", 0, 0), newNote("Second message", 0, 10_000_000), 206 | newNote("Third message", 10_000_000, 0), newNote("Fourth message", 10_000_000, 10_000_000)}; 207 | 208 | for (RouteNote request : requests) { 209 | info("Sending message \"{0}\" at {1}, {2}", request.getMessage(), request.getLocation() 210 | .getLatitude(), request.getLocation().getLongitude()); 211 | requestObserver.onNext(request); 212 | } 213 | } catch (RuntimeException e) { 214 | // Cancel RPC 215 | requestObserver.onError(e); 216 | throw e; 217 | } 218 | // Mark the end of requests 219 | requestObserver.onCompleted(); 220 | 221 | // return the latch while receiving happens asynchronously 222 | return finishLatch; 223 | } 224 | 225 | /** Issues several different requests and then exits. */ 226 | public static void main(String[] args) throws InterruptedException { 227 | 228 | String target = "localhost:8000"; 229 | if (args.length > 0) { 230 | if ("--help".equals(args[0])) { 231 | System.err.println("Usage: [target]"); 232 | System.err.println(" target The server to connect to. Defaults to " + target); 233 | System.exit(1); 234 | } 235 | target = args[0]; 236 | } 237 | 238 | List features; 239 | try { 240 | features = (List ) RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile()); 241 | } catch (IOException ex) { 242 | ex.printStackTrace(); 243 | return; 244 | } 245 | 246 | ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build(); 247 | try { 248 | RouteGuideClient client = new RouteGuideClient(channel); 249 | // Looking for a valid feature 250 | client.getFeature(409146138, -746188906); 251 | 252 | // Feature missing. 253 | client.getFeature(0, 0); 254 | 255 | // Looking for features between 40, -75 and 42, -73. 256 | client.listFeatures(400000000, -750000000, 420000000, -730000000); 257 | 258 | // Record a few randomly selected points from the features file. 259 | client.recordRoute(features, 10); 260 | 261 | // Send and receive some notes. 262 | CountDownLatch finishLatch = client.routeChat(); 263 | 264 | if (!finishLatch.await(1, TimeUnit.MINUTES)) { 265 | client.warning("routeChat can not finish within 1 minutes"); 266 | } 267 | } finally { 268 | channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS); 269 | } 270 | } 271 | 272 | private void info(String msg, Object... params) { 273 | logger.log(Level.INFO, msg, params); 274 | } 275 | 276 | private void warning(String msg, Object... params) { 277 | logger.log(Level.WARNING, msg, params); 278 | } 279 | 280 | private RouteNote newNote(String message, int lat, int lon) { 281 | return RouteNote.newBuilder().setMessage(message) 282 | .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build(); 283 | } 284 | 285 | /** 286 | * Only used for unit test, as we do not want to introduce randomness in unit test. 287 | */ 288 | @VisibleForTesting 289 | void setRandom(Random random) { 290 | this.random = random; 291 | } 292 | 293 | /** 294 | * Only used for helping unit test. 295 | */ 296 | @VisibleForTesting 297 | interface TestHelper { 298 | /** 299 | * Used for verify/inspect message received from server. 300 | */ 301 | void onMessage(Message message); 302 | 303 | /** 304 | * Used for verify/inspect error received from server. 305 | */ 306 | void onRpcError(Throwable exception); 307 | } 308 | 309 | @VisibleForTesting 310 | void setTestHelper(TestHelper testHelper) { 311 | this.testHelper = testHelper; 312 | } 313 | } -------------------------------------------------------------------------------- /spring-boot-grpc-client-Example2/src/main/proto/route_guide.proto: -------------------------------------------------------------------------------- 1 | // it must be placed in proto folder (case sensitive) 2 | 3 | // define the package we want to use for our generated Java classes 4 | syntax = "proto3"; 5 | option java_multiple_files = true; 6 | option java_package = "route_guide"; 7 | 8 | // Our RouteGuide Service Definition 9 | service RouteGuide{ 10 | 11 | // A simple RPC where the client sends a request to the server using the stub 12 | // and waits for a response to come back, just like a normal function call. 13 | rpc GetFeature(Point) returns (Feature) {} 14 | 15 | // Server-side streaming RPC where the client sends a request to the server 16 | // and gets a stream to read a sequence of messages back. The client reads from the 17 | // returned stream until there are no more messages 18 | rpc ListFeatures (Rectangle) returns (stream Feature) {} 19 | 20 | // A client-side streaming RPC where the client writes a sequence of messages 21 | // and sends them to the server, again using a provided stream. Once the client has 22 | // finished writing the messages, it waits for the server to read them all and return 23 | // its response. 24 | // Accepts a stream of Points on a route being traversed, returning a 25 | // RouteSummary when traversal is completed. 26 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 27 | 28 | // A bidirectional streaming RPC where both sides send a sequence of messages using a read-write stream 29 | // The two streams operate independently, so clients and servers can read and write in whatever order 30 | // they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. 31 | // Accepts a stream of RouteNotes sent while a route is being traversed, 32 | // while receiving other RouteNotes (e.g. from other users). 33 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 34 | 35 | } 36 | 37 | // Latitudes should be in the range +/- 90 degrees and longitude should be in the range +/- 180 degrees (inclusive). 38 | message Point { 39 | int32 latitude = 1; 40 | int32 longitude = 2; 41 | } 42 | 43 | // A latitude-longitude rectangle, represented as two diagonally opposite points "lo" and "hi" (Corners). 44 | message Rectangle { 45 | Point lo = 1; 46 | Point hi = 2; 47 | } 48 | 49 | // A feature names something at a given point. If a feature could not be named, the name is empty. 50 | message Feature { 51 | string name = 1; 52 | Point location = 2; 53 | } 54 | 55 | // Not used in the RPC. Instead, this is here for the form serialized to disk. 56 | message FeatureDatabase { 57 | repeated Feature feature = 1; 58 | } 59 | 60 | // A RouteNote is a message sent while at a given point. The location from which the message is sent. 61 | message RouteNote { 62 | Point location = 1; 63 | string message = 2; 64 | } 65 | 66 | // A RouteSummary is received in response to a RecordRoute rpc. 67 | // It contains the number of individual points received, the number of detected features, and the total distance covered as the cumulative sum of 68 | // the distance between each point. 69 | message RouteSummary { 70 | int32 point_count = 1; 71 | int32 feature_count = 2; 72 | int32 distance = 3; 73 | int32 elapsed_time = 4; 74 | } 75 | 76 | // for compilation use mvn compile after pressing ctrl twice -------------------------------------------------------------------------------- /spring-boot-grpc-client-Example2/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | MASTER_SERVER_PORT = 8000 2 | SHARED_DIRECTORY = ./MyFiles/ 3 | 4 | # Rest of the arguments are provided at the runtime 5 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.exampledriven 7 | spring-boot-grpc-client 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-grpc-client 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-test 36 | test 37 | 38 | 39 | 40 | com.github.exampledriven 41 | grpc-eureka-java-starter 42 | 1.0.0-SNAPSHOT 43 | 44 | 45 | 46 | io.grpc 47 | grpc-stub 48 | 1.0.1 49 | 50 | 51 | io.grpc 52 | grpc-protobuf 53 | 1.0.1 54 | 55 | 56 | io.grpc 57 | grpc-netty 58 | 1.0.1 59 | 60 | 61 | org.springframework.cloud 62 | spring-cloud-starter-eureka 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-actuator 67 | 68 | 69 | 70 | com.google.inject 71 | guice 72 | 4.1.0 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | org.springframework.cloud 81 | spring-cloud-dependencies 82 | Camden.RELEASE 83 | pom 84 | import 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | kr.motd.maven 95 | os-maven-plugin 96 | 1.4.1.Final 97 | 98 | 99 | 100 | 101 | org.springframework.boot 102 | spring-boot-maven-plugin 103 | 104 | 105 | 106 | org.xolstice.maven.plugins 107 | protobuf-maven-plugin 108 | 0.5.0 109 | 110 | 115 | com.google.protobuf:protoc:3.0.2:exe:${os.detected.classifier} 116 | grpc-java 117 | io.grpc:protoc-gen-grpc-java:1.0.1:exe:${os.detected.classifier} 118 | 119 | 120 | 121 | 122 | compile 123 | compile-custom 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/SpringBootGrpcClientApplication.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 7 | 8 | @SpringBootApplication 9 | @EnableDiscoveryClient 10 | public class SpringBootGrpcClientApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(SpringBootGrpcClientApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/grpc/BookGrpcController.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.grpc; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import com.google.protobuf.util.JsonFormat; 5 | import org.exampledriven.grpc.services.BookList; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class BookGrpcController { 16 | 17 | @Autowired 18 | BookServiceGrpcClient bookServiceGrpcClient; 19 | 20 | @RequestMapping(value = "/test_grpc", produces = MediaType.APPLICATION_JSON_VALUE) 21 | public ResponseEntity test(@RequestParam(value = "compact", required = false, defaultValue = "false") boolean compact) { 22 | 23 | BookList books = bookServiceGrpcClient.createBooks(TestDataUtil.getGrpcTestData()); 24 | 25 | if (compact) { 26 | 27 | return new ResponseEntity<>("SUCCESS", HttpStatus.OK); 28 | } else { 29 | 30 | String jsonString = ""; 31 | try { 32 | jsonString = JsonFormat.printer().print(books); 33 | } catch (InvalidProtocolBufferException e) { 34 | e.printStackTrace(); 35 | } 36 | 37 | return new ResponseEntity<>(jsonString, HttpStatus.OK); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/grpc/BookServiceGrpcClient.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.grpc; 2 | 3 | import com.netflix.discovery.EurekaClientConfig; 4 | import io.grpc.ManagedChannel; 5 | import org.exampledriven.grpc.services.Book; 6 | import org.exampledriven.grpc.services.BookList; 7 | import org.exampledriven.grpc.services.BookServiceGrpc; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import javax.annotation.PostConstruct; 14 | import java.util.List; 15 | 16 | @Service 17 | public class BookServiceGrpcClient { 18 | 19 | @Autowired 20 | EurekaClientConfig eurekaClientConfig; 21 | 22 | @Autowired 23 | ManagedChannel managedChannel; 24 | 25 | private Logger logger = LoggerFactory.getLogger(BookServiceGrpcClient.class); 26 | 27 | private BookServiceGrpc.BookServiceBlockingStub bookServiceBlockingStub; 28 | 29 | public BookList createBooks(List bookList) { 30 | 31 | BookList.Builder builder = BookList.newBuilder(); 32 | bookList.forEach(builder::addBook); 33 | BookList request = builder.build(); 34 | 35 | if (logger.isDebugEnabled()) { 36 | logger.debug("Request " + request); 37 | } 38 | BookList response = bookServiceBlockingStub.createBooks(request); 39 | if (logger.isDebugEnabled()) { 40 | logger.debug("Response " + response); 41 | } 42 | return response; 43 | 44 | } 45 | 46 | @PostConstruct 47 | private void initializeClient() { 48 | 49 | bookServiceBlockingStub = BookServiceGrpc.newBlockingStub(managedChannel); 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/grpc/TestDataUtil.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.grpc; 2 | 3 | import org.exampledriven.grpc.services.Book; 4 | import org.exampledriven.grpc.services.BookType; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | public class TestDataUtil { 10 | 11 | private static List grpcBookList = new LinkedList<>(); 12 | 13 | static { 14 | 15 | for (int i = 0; i < 10; i++) { 16 | Book.Builder builder = Book.newBuilder() 17 | .setAuthor(randomString()) 18 | .setBookType(BookType.COMIC_BOOK); 19 | 20 | for (int j = 0; j < 3; j++) { 21 | builder.addKeyword(randomString()); 22 | } 23 | 24 | builder 25 | .setPage((int)(Math.random() * 100)) 26 | .setTitle(randomString()); 27 | 28 | grpcBookList.add(builder.build()); 29 | 30 | } 31 | 32 | } 33 | 34 | private static String randomString() { 35 | return UUID.randomUUID().toString(); 36 | } 37 | 38 | public static List getGrpcTestData() { 39 | return grpcBookList; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/rest/Book.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.rest; 2 | 3 | import java.util.List; 4 | 5 | public class Book { 6 | public enum BookType {COMIC_BOOK, NEWS_PAPER, BOOK}; 7 | 8 | private String ISBN; 9 | private String author; 10 | private String title; 11 | private int page; 12 | private List keyword; 13 | private BookType bookType; 14 | 15 | public String getISBN() { 16 | return ISBN; 17 | } 18 | 19 | public void setISBN(String ISBN) { 20 | this.ISBN = ISBN; 21 | } 22 | 23 | public String getAuthor() { 24 | return author; 25 | } 26 | 27 | public void setAuthor(String author) { 28 | this.author = author; 29 | } 30 | 31 | public String getTitle() { 32 | return title; 33 | } 34 | 35 | public void setTitle(String title) { 36 | this.title = title; 37 | } 38 | 39 | public int getPage() { 40 | return page; 41 | } 42 | 43 | public void setPage(int page) { 44 | this.page = page; 45 | } 46 | 47 | public List getKeyword() { 48 | return keyword; 49 | } 50 | 51 | public void setKeyword(List keyword) { 52 | this.keyword = keyword; 53 | } 54 | 55 | public BookType getBookType() { 56 | return bookType; 57 | } 58 | 59 | public void setBookType(BookType bookType) { 60 | this.bookType = bookType; 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/rest/BookRestController.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.rest; 2 | 3 | import org.springframework.core.ParameterizedTypeReference; 4 | import org.springframework.http.HttpEntity; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import org.springframework.web.client.RestTemplate; 10 | import java.util.List; 11 | 12 | @RestController 13 | public class BookRestController { 14 | 15 | @RequestMapping("/test_rest") 16 | public List testVerbose() { 17 | HttpEntity> entity = new HttpEntity<>(TestDataUtil.getRestTestData(), null); 18 | 19 | ResponseEntity> responseEntity = 20 | new RestTemplate().exchange("http://localhost:8081/book", 21 | HttpMethod.POST, entity, new ParameterizedTypeReference>() {}); 22 | 23 | return responseEntity.getBody(); 24 | } 25 | 26 | 27 | @RequestMapping("/test_rest/compact") 28 | public String testCompact() { 29 | 30 | testVerbose(); 31 | 32 | return "SUCCESS"; 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/java/org/exampledriven/rest/TestDataUtil.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.rest; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.UUID; 6 | 7 | public class TestDataUtil { 8 | 9 | private static List restBookList = new LinkedList<>(); 10 | 11 | static { 12 | 13 | for (int i = 0; i < 10; i++) { 14 | org.exampledriven.rest.Book book = new org.exampledriven.rest.Book(); 15 | book.setAuthor(randomString()); 16 | book.setBookType(org.exampledriven.rest.Book.BookType.COMIC_BOOK); 17 | 18 | book.setKeyword(new LinkedList<>()); 19 | for (int j = 0; j < 3; j++) { 20 | book.getKeyword().add(randomString()); 21 | } 22 | 23 | book.setPage((int)(Math.random() * 100)); 24 | book.setTitle(randomString()); 25 | 26 | restBookList.add(book); 27 | 28 | } 29 | 30 | 31 | } 32 | 33 | private static String randomString() { 34 | return UUID.randomUUID().toString(); 35 | } 36 | 37 | public static List getRestTestData() { 38 | return restBookList; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/proto/BookService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | package org.exampledriven.grpc.services; 5 | 6 | enum BookType { 7 | BOOK = 0; 8 | NEWS_PAPER = 1; 9 | COMIC_BOOK = 2; 10 | } 11 | 12 | message Book { 13 | string ISBN = 1; 14 | string title = 2; 15 | string author = 3; 16 | int32 page = 4; 17 | repeated string keyword = 5; 18 | BookType bookType = 6; 19 | } 20 | 21 | message BookList { 22 | repeated Book book = 1; 23 | } 24 | 25 | //message ISBN { 26 | // string code = 1; 27 | //} 28 | 29 | service BookService { 30 | rpc createBooks(BookList) returns (BookList); 31 | // rpc findBookByISBN(ISBN) returns (Book); 32 | } -------------------------------------------------------------------------------- /spring-boot-grpc-client/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | #logging: 5 | # level: 6 | # org.exampledriven: DEBUG 7 | 8 | eureka: 9 | instance: 10 | leaseRenewalIntervalInSeconds: 1 11 | leaseExpirationDurationInSeconds: 2 12 | client: 13 | serviceUrl: 14 | defaultZone: http://127.0.0.1:8761/eureka/ 15 | healthcheck: 16 | enabled: true 17 | lease: 18 | duration: 5 19 | 20 | spring: 21 | application: 22 | name: grpc-client 23 | 24 | grpc: 25 | eureka: 26 | service-id: grpc-server -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.3.4.RELEASE 9 | 10 | 11 | com.dfs 12 | dfs 13 | 0.0.1-SNAPSHOT 14 | Distributed File Server 15 | Secure RPC Distributed File Server 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter 26 | 27 | 28 | 29 | 30 | io.grpc 31 | grpc-netty 32 | 1.32.1 33 | 34 | 35 | io.grpc 36 | grpc-protobuf 37 | 1.32.1 38 | 39 | 40 | io.grpc 41 | grpc-stub 42 | 1.32.1 43 | 44 | 45 | org.apache.tomcat 46 | annotations-api 47 | 6.0.53 48 | provided 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.junit.vintage 59 | junit-vintage-engine 60 | 61 | 62 | 63 | 64 | 65 | 66 | com.google.protobuf 67 | protobuf-java-util 68 | 4.0.0-rc-2 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-maven-plugin 81 | 82 | 83 | 84 | org.xolstice.maven.plugins 85 | protobuf-maven-plugin 86 | 0.6.1 87 | 88 | com.google.protobuf:protoc:3.12.0:exe:${os.detected.classifier} 89 | grpc-java 90 | io.grpc:protoc-gen-grpc-java:1.32.1:exe:${os.detected.classifier} 91 | 92 | 93 | 94 | 95 | compile 96 | compile-custom 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | kr.motd.maven 105 | os-maven-plugin 106 | 1.6.2 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/src/main/java/route_guide/RouteGuideServer.java: -------------------------------------------------------------------------------- 1 | package route_guide; 2 | 3 | import io.grpc.Server; 4 | import io.grpc.ServerBuilder; 5 | 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.logging.Logger; 10 | 11 | public class RouteGuideServer { 12 | 13 | private static final Logger logger = Logger.getLogger(RouteGuideServer.class.getName()); 14 | private final int port; 15 | private final Server server; 16 | 17 | public RouteGuideServer(int port) throws IOException { 18 | this.port = port; 19 | URL featureFile = RouteGuideUtil.getDefaultFeaturesFile(); 20 | server = ServerBuilder.forPort(port).addService(new RouteGuideService(RouteGuideUtil.parseFeatures(featureFile))).build(); 21 | } 22 | 23 | /* Start serving requests. */ 24 | public void start() throws IOException { 25 | server.start(); 26 | logger.info("Server started, listening on " + port); 27 | // Add shutDown hook to shut down gracefully 28 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 29 | // Use stderr here since the logger may have been reset by its JVM shutdown hook. 30 | System.err.println("*** shutting down gRPC server since JVM is shutting down"); 31 | try { 32 | server.shutdown().awaitTermination(30, TimeUnit.SECONDS); 33 | } catch (InterruptedException e) { 34 | e.printStackTrace(System.err); 35 | } 36 | System.err.println("*** server shut down"); 37 | })); 38 | } 39 | 40 | /* Await termination on the main thread since the grpc library uses daemon threads. */ 41 | private void blockUntilShutdown() throws InterruptedException { 42 | if (server != null) { 43 | server.awaitTermination(); 44 | } 45 | } 46 | 47 | public static void main(String[] args) throws Exception { 48 | RouteGuideServer server = new RouteGuideServer(8000); 49 | server.start(); 50 | server.blockUntilShutdown(); 51 | } 52 | } -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/src/main/java/route_guide/RouteGuideService.java: -------------------------------------------------------------------------------- 1 | package route_guide; 2 | 3 | import io.grpc.stub.StreamObserver; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.ConcurrentMap; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | 15 | import static java.lang.Math.atan2; 16 | import static java.lang.Math.cos; 17 | import static java.lang.Math.max; 18 | import static java.lang.Math.min; 19 | import static java.lang.Math.sin; 20 | import static java.lang.Math.sqrt; 21 | import static java.lang.Math.toRadians; 22 | import static java.util.concurrent.TimeUnit.NANOSECONDS; 23 | 24 | class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase { 25 | 26 | private static final Logger logger = (Logger) LoggerFactory.getLogger(RouteGuideService.class.getName()); 27 | 28 | private final Collection features; 29 | private final ConcurrentMap > routeNotes = new ConcurrentHashMap <>(); 30 | 31 | RouteGuideService(Collection features) { 32 | this.features = features; 33 | } 34 | 35 | /* Gets the feature at the given point. No feature was found, return an unnamed feature. */ 36 | private Feature checkFeature(Point location) { 37 | for (Feature feature : features) { 38 | if (feature.getLocation().getLatitude() == location.getLatitude() 39 | && feature.getLocation().getLongitude() == location.getLongitude()) { 40 | return feature; 41 | } 42 | } 43 | return Feature.newBuilder().setName("").setLocation(location).build(); 44 | } 45 | 46 | /*responseObserver the observer that will receive the feature at the requested point. */ 47 | @Override 48 | public void getFeature(Point request, StreamObserver responseObserver) { 49 | responseObserver.onNext(checkFeature(request)); 50 | responseObserver.onCompleted(); 51 | } 52 | 53 | /** 54 | * Gets all features contained within the given bounding {@link Rectangle}. 55 | * 56 | * @param request the bounding rectangle for the requested features. 57 | * @param responseObserver the observer that will receive the features. 58 | */ 59 | @Override 60 | public void listFeatures(Rectangle request, StreamObserver responseObserver) { 61 | int left = min(request.getLo().getLongitude(), request.getHi().getLongitude()); 62 | int right = max(request.getLo().getLongitude(), request.getHi().getLongitude()); 63 | int top = max(request.getLo().getLatitude(), request.getHi().getLatitude()); 64 | int bottom = min(request.getLo().getLatitude(), request.getHi().getLatitude()); 65 | 66 | for (Feature feature : features) { 67 | if (!RouteGuideUtil.exists(feature)) { 68 | continue; 69 | } 70 | int lat = feature.getLocation().getLatitude(); 71 | int lon = feature.getLocation().getLongitude(); 72 | if (lon >= left && lon <= right && lat >= bottom && lat <= top) { 73 | responseObserver.onNext(feature); 74 | } 75 | } 76 | responseObserver.onCompleted(); 77 | } 78 | 79 | /** 80 | * Gets a stream of points, and responds with statistics about the "trip": number of points, number of known features visited, total distance traveled, and total time spent. 81 | * @param responseObserver an observer to receive the response summary. 82 | * @return an observer to receive the requested route points. 83 | */ 84 | 85 | @Override 86 | public StreamObserver recordRoute(final StreamObserver responseObserver) { 87 | return new StreamObserver <>() { 88 | int pointCount, featureCount, distance; 89 | Point previous; 90 | final long startTime = System.nanoTime(); 91 | 92 | @Override 93 | public void onNext(Point point) { 94 | pointCount++; 95 | if(RouteGuideUtil.exists(checkFeature(point))) { 96 | featureCount++; 97 | } 98 | // For each point after the first, add the incremental distance from the previous point to the total distance value. 99 | if(previous != null) { 100 | distance += calcDistance(previous, point); 101 | } 102 | previous = point; 103 | } 104 | 105 | @Override 106 | public void onError(Throwable t) { 107 | logger.log(Level.WARNING, "recordRoute cancelled"); 108 | } 109 | 110 | @Override 111 | public void onCompleted() { 112 | long seconds = NANOSECONDS.toSeconds(System.nanoTime() - startTime); 113 | // return the final response after processing the stream 114 | responseObserver.onNext(RouteSummary.newBuilder().setPointCount(pointCount) 115 | .setFeatureCount(featureCount).setDistance(distance) 116 | .setElapsedTime((int) seconds).build()); 117 | // send "no more to send" ack 118 | responseObserver.onCompleted(); 119 | } 120 | }; 121 | } 122 | 123 | /** 124 | * Receives a stream of message/location pairs, and responds with a stream of all previous 125 | * messages at each of those locations. 126 | * 127 | * @param responseObserver an observer to receive the stream of previous messages. 128 | * @return an observer to handle requested message/location pairs. 129 | */ 130 | @Override 131 | public StreamObserver routeChat(final StreamObserver responseObserver) { 132 | return new StreamObserver() { 133 | @Override 134 | public void onNext(RouteNote note) { 135 | List notes = getOrCreateNotes(note.getLocation()); 136 | 137 | // Respond with all previous notes at this location. 138 | for (RouteNote prevNote : notes.toArray(new RouteNote[0])) { 139 | responseObserver.onNext(prevNote); 140 | } 141 | 142 | // Now add the new note to the list 143 | notes.add(note); 144 | } 145 | 146 | @Override 147 | public void onError(Throwable t) { 148 | logger.log(Level.WARNING, "routeChat cancelled"); 149 | } 150 | 151 | @Override 152 | public void onCompleted() { 153 | responseObserver.onCompleted(); 154 | } 155 | }; 156 | } 157 | 158 | /** 159 | * Get the notes list for the given location. If missing, create it. 160 | */ 161 | private List getOrCreateNotes(Point location) { 162 | List notes = Collections.synchronizedList(new ArrayList ()); 163 | List prevNotes = routeNotes.putIfAbsent(location, notes); 164 | return prevNotes != null ? prevNotes : notes; 165 | } 166 | 167 | 168 | 169 | /** 170 | * Calculate the distance between two points using the "haversine" formula. 171 | * The formula is based on http://mathforum.org/library/drmath/view/51879.html. 172 | * 173 | * @param start The starting point 174 | * @param end The end point 175 | * @return The distance between the points in meters 176 | */ 177 | private static int calcDistance(Point start, Point end) { 178 | int r = 6371000; // earth radius in meters 179 | double lat1 = toRadians(RouteGuideUtil.getLatitude(start)); 180 | double lat2 = toRadians(RouteGuideUtil.getLatitude(end)); 181 | double lon1 = toRadians(RouteGuideUtil.getLongitude(start)); 182 | double lon2 = toRadians(RouteGuideUtil.getLongitude(end)); 183 | double deltaLat = lat2 - lat1; 184 | double deltaLon = lon2 - lon1; 185 | 186 | double a = sin(deltaLat / 2) * sin(deltaLat / 2) 187 | + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2); 188 | double c = 2 * atan2(sqrt(a), sqrt(1 - a)); 189 | 190 | return (int) (r * c); 191 | } 192 | } -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/src/main/java/route_guide/RouteGuideUtil.java: -------------------------------------------------------------------------------- 1 | package route_guide; 2 | 3 | import com.google.protobuf.util.JsonFormat; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.InputStreamReader; 8 | import java.io.Reader; 9 | import java.net.URL; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.Collection; 12 | 13 | public class RouteGuideUtil { 14 | 15 | private static final double COORD_FACTOR = 1e7; 16 | 17 | /* Gets the latitude for the given point. */ 18 | public static double getLatitude(Point location) { 19 | return location.getLatitude() / COORD_FACTOR; 20 | } 21 | 22 | /* Gets the longitude for the given point.*/ 23 | public static double getLongitude(Point location) { 24 | return location.getLongitude() / COORD_FACTOR; 25 | } 26 | 27 | /* Indicates whether the given feature exists (i.e. has a valid name) */ 28 | public static boolean exists(Feature feature) { 29 | return feature != null && !feature.getName().isEmpty(); 30 | } 31 | 32 | /* Gets the default features file from classpath. */ 33 | public static URL getDefaultFeaturesFile() { 34 | // getResource() method of java Class class is used to return the resources of the module in which this class exists. 35 | // finds resource relative to the class location, if not found then return NULL 36 | return RouteGuideServer.class.getResource("route_guide_db.json"); 37 | } 38 | 39 | /* Parses the JSON input file containing the list of features. */ 40 | public static Collection parseFeatures(URL file) throws IOException { 41 | try (InputStream input = file.openStream()) { 42 | try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { 43 | FeatureDatabase.Builder database = FeatureDatabase.newBuilder(); 44 | JsonFormat.parser().merge(reader, database); 45 | return database.getFeatureList(); 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/src/main/proto/route_guide.proto: -------------------------------------------------------------------------------- 1 | // it must be placed in proto folder (case sensitive) 2 | 3 | // define the package we want to use for our generated Java classes 4 | syntax = "proto3"; 5 | option java_multiple_files = true; 6 | option java_package = "route_guide"; 7 | 8 | // Our RouteGuide Service Definition 9 | service RouteGuide{ 10 | 11 | // A simple RPC where the client sends a request to the server using the stub 12 | // and waits for a response to come back, just like a normal function call. 13 | rpc GetFeature(Point) returns (Feature) {} 14 | 15 | // Server-side streaming RPC where the client sends a request to the server 16 | // and gets a stream to read a sequence of messages back. The client reads from the 17 | // returned stream until there are no more messages 18 | rpc ListFeatures (Rectangle) returns (stream Feature) {} 19 | 20 | // A client-side streaming RPC where the client writes a sequence of messages 21 | // and sends them to the server, again using a provided stream. Once the client has 22 | // finished writing the messages, it waits for the server to read them all and return 23 | // its response. 24 | // Accepts a stream of Points on a route being traversed, returning a 25 | // RouteSummary when traversal is completed. 26 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 27 | 28 | // A bidirectional streaming RPC where both sides send a sequence of messages using a read-write stream 29 | // The two streams operate independently, so clients and servers can read and write in whatever order 30 | // they like: for example, the server could wait to receive all the client messages before writing its responses, or it could alternately read a message then write a message, or some other combination of reads and writes. The order of messages in each stream is preserved. 31 | // Accepts a stream of RouteNotes sent while a route is being traversed, 32 | // while receiving other RouteNotes (e.g. from other users). 33 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 34 | 35 | } 36 | 37 | // Latitudes should be in the range +/- 90 degrees and longitude should be in the range +/- 180 degrees (inclusive). 38 | message Point { 39 | int32 latitude = 1; 40 | int32 longitude = 2; 41 | } 42 | 43 | // A latitude-longitude rectangle, represented as two diagonally opposite points "lo" and "hi" (Corners). 44 | message Rectangle { 45 | Point lo = 1; 46 | Point hi = 2; 47 | } 48 | 49 | // A feature names something at a given point. If a feature could not be named, the name is empty. 50 | message Feature { 51 | string name = 1; 52 | Point location = 2; 53 | } 54 | 55 | // Not used in the RPC. Instead, this is here for the form serialized to disk. 56 | message FeatureDatabase { 57 | repeated Feature feature = 1; 58 | } 59 | 60 | // A RouteNote is a message sent while at a given point. The location from which the message is sent. 61 | message RouteNote { 62 | Point location = 1; 63 | string message = 2; 64 | } 65 | 66 | // A RouteSummary is received in response to a RecordRoute rpc. 67 | // It contains the number of individual points received, the number of detected features, and the total distance covered as the cumulative sum of 68 | // the distance between each point. 69 | message RouteSummary { 70 | int32 point_count = 1; 71 | int32 feature_count = 2; 72 | int32 distance = 3; 73 | int32 elapsed_time = 4; 74 | } 75 | 76 | // for compilation use mvn compile after pressing ctrl twice -------------------------------------------------------------------------------- /spring-boot-grpc-server-Example2/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | MASTER_SERVER_PORT = 8000 2 | SHARED_DIRECTORY = ./MyFiles/ 3 | 4 | # Rest of the arguments are provided at the runtime 5 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.exampledriven 7 | spring-boot-grpc-server 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-grpc-server 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | 44 | io.grpc 45 | grpc-stub 46 | 1.0.1 47 | 48 | 49 | io.grpc 50 | grpc-protobuf 51 | 1.0.1 52 | 53 | 54 | io.grpc 55 | grpc-netty 56 | 1.0.1 57 | 58 | 59 | 60 | org.lognet 61 | grpc-spring-boot-starter 62 | 0.0.6 63 | 64 | 65 | io.grpc 66 | grpc-netty 67 | 68 | 69 | org.springframework.boot 70 | spring-boot-starter 71 | 72 | 73 | 74 | 75 | 76 | org.springframework.cloud 77 | spring-cloud-starter-eureka 78 | 79 | 80 | org.springframework.boot 81 | spring-boot-starter-actuator 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | org.springframework.cloud 90 | spring-cloud-dependencies 91 | Camden.RELEASE 92 | pom 93 | import 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | kr.motd.maven 104 | os-maven-plugin 105 | 1.4.1.Final 106 | 107 | 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-maven-plugin 112 | 113 | 114 | 115 | org.xolstice.maven.plugins 116 | protobuf-maven-plugin 117 | 0.5.0 118 | 119 | 124 | com.google.protobuf:protoc:3.0.2:exe:${os.detected.classifier} 125 | grpc-java 126 | io.grpc:protoc-gen-grpc-java:1.0.1:exe:${os.detected.classifier} 127 | 128 | 129 | 130 | 131 | compile 132 | compile-custom 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | false 144 | 145 | bintray-lognet-maven 146 | bintray 147 | http://dl.bintray.com/lognet/maven 148 | 149 | 150 | 151 | 152 | 153 | false 154 | 155 | bintray-lognet-maven 156 | bintray-plugins 157 | http://dl.bintray.com/lognet/maven 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/java/org/exampledriven/BookGrpcService.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven; 2 | 3 | import io.grpc.stub.StreamObserver; 4 | import org.exampledriven.grpc.services.Book; 5 | import org.exampledriven.grpc.services.BookList; 6 | import org.exampledriven.grpc.services.BookServiceGrpc; 7 | import org.lognet.springboot.grpc.GRpcService; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | @GRpcService 15 | public class BookGrpcService extends BookServiceGrpc.BookServiceImplBase { 16 | 17 | private Logger logger = LoggerFactory.getLogger(BookGrpcService.class); 18 | 19 | @Override 20 | public void createBooks(BookList request, StreamObserver responseObserver) { 21 | logger.debug("Request " + request); 22 | 23 | BookList.Builder responseBuilder = BookList.newBuilder(); 24 | 25 | BookUtil.assignISBN(request.getBookList()).forEach(responseBuilder::addBook); 26 | 27 | BookList bookListResponse = responseBuilder.build(); 28 | 29 | logger.debug("Response " + bookListResponse); 30 | 31 | responseObserver.onNext(bookListResponse); 32 | responseObserver.onCompleted(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/java/org/exampledriven/BookUtil.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven; 2 | 3 | import org.exampledriven.grpc.services.Book; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | import java.util.UUID; 7 | 8 | 9 | public class BookUtil { 10 | 11 | public static List assignISBN(List books) { 12 | 13 | List result = new LinkedList<>(); 14 | for (Book book : books) { 15 | Book bookWithISBN = Book.newBuilder(book) 16 | .setISBN(generateISBN()) 17 | .build(); 18 | 19 | result.add(bookWithISBN); 20 | } 21 | 22 | return result; 23 | } 24 | 25 | public static String generateISBN() { 26 | return UUID.randomUUID().toString().replaceAll("/", "").substring(0,12); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/java/org/exampledriven/GrpcExampleApplication.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @SpringBootApplication 8 | @EnableEurekaClient 9 | public class GrpcExampleApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(GrpcExampleApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/java/org/exampledriven/rest/Book.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.rest; 2 | 3 | import java.util.List; 4 | 5 | public class Book { 6 | public enum BookType {COMIC_BOOK, NEWS_PAPER, BOOK}; 7 | 8 | private String ISBN; 9 | private String author; 10 | private String title; 11 | private int page; 12 | private List keyword; 13 | private BookType bookType; 14 | 15 | public String getISBN() { 16 | return ISBN; 17 | } 18 | 19 | public void setISBN(String ISBN) { 20 | this.ISBN = ISBN; 21 | } 22 | 23 | public String getAuthor() { 24 | return author; 25 | } 26 | 27 | public void setAuthor(String author) { 28 | this.author = author; 29 | } 30 | 31 | public String getTitle() { 32 | return title; 33 | } 34 | 35 | public void setTitle(String title) { 36 | this.title = title; 37 | } 38 | 39 | public int getPage() { 40 | return page; 41 | } 42 | 43 | public void setPage(int page) { 44 | this.page = page; 45 | } 46 | 47 | public List getKeyword() { 48 | return keyword; 49 | } 50 | 51 | public void setKeyword(List keyword) { 52 | this.keyword = keyword; 53 | } 54 | 55 | public BookType getBookType() { 56 | return bookType; 57 | } 58 | 59 | public void setBookType(BookType bookType) { 60 | this.bookType = bookType; 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/java/org/exampledriven/rest/BooksRestController.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven.rest; 2 | 3 | import org.exampledriven.BookUtil; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.autoconfigure.web.HttpMessageConverters; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestBody; 9 | import org.springframework.web.bind.annotation.RestController; 10 | import java.util.List; 11 | 12 | @RestController 13 | public class BooksRestController { 14 | @Autowired 15 | HttpMessageConverters httpMessageConverters; 16 | 17 | @PostMapping("book") 18 | public List createBooks(@RequestBody List books) { 19 | 20 | books.forEach(book -> { 21 | book.setISBN(BookUtil.generateISBN()); 22 | }); 23 | 24 | return books; 25 | } 26 | 27 | @GetMapping() 28 | public void test() { 29 | httpMessageConverters.getConverters().forEach(System.out::println); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/proto/BookService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | package org.exampledriven.grpc.services; 5 | 6 | enum BookType { 7 | BOOK = 0; 8 | NEWS_PAPER = 1; 9 | COMIC_BOOK = 2; 10 | } 11 | 12 | message Book { 13 | string ISBN = 1; 14 | string title = 2; 15 | string author = 3; 16 | int32 page = 4; 17 | repeated string keyword = 5; 18 | BookType bookType = 6; 19 | } 20 | 21 | message BookList { 22 | repeated Book book = 1; 23 | } 24 | 25 | //message ISBN { 26 | // string code = 1; 27 | //} 28 | 29 | service BookService { 30 | rpc createBooks(BookList) returns (BookList); 31 | // rpc findBookByISBN(ISBN) returns (Book); 32 | } -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9081 3 | grpc: 4 | port : 7565 5 | 6 | logging: 7 | level: 8 | org.exampledriven: DEBUG 9 | 10 | eureka: 11 | instance: 12 | leaseRenewalIntervalInSeconds: 1 13 | leaseExpirationDurationInSeconds: 2 14 | metadataMap: 15 | grpc.port: 7565 16 | client: 17 | serviceUrl: 18 | defaultZone: http://127.0.0.1:8761/eureka/ 19 | healthcheck: 20 | enabled: true 21 | lease: 22 | duration: 5 23 | 24 | spring: 25 | application: 26 | name: grpc-server -------------------------------------------------------------------------------- /spring-boot-grpc-server/src/test/java/org/exampledriven/GrpcExampleApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.exampledriven; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class GrpcExampleApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | --------------------------------------------------------------------------------