├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── src └── main │ ├── java │ └── com │ │ └── github │ │ └── simplesteph │ │ └── grpc │ │ ├── blog │ │ ├── client │ │ │ └── BlogClient.java │ │ └── server │ │ │ ├── BlogServer.java │ │ │ └── BlogServiceImpl.java │ │ ├── calculator │ │ ├── client │ │ │ └── CalculatorClient.java │ │ └── server │ │ │ ├── CalculatorServer.java │ │ │ └── CalculatorServiceImpl.java │ │ └── greeting │ │ ├── client │ │ └── GreetingClient.java │ │ └── server │ │ ├── GreetServiceImpl.java │ │ └── GreetingServer.java │ └── proto │ ├── blog │ └── blog.proto │ ├── calculator │ └── calculator.proto │ ├── dummy │ └── dummy.proto │ └── greet │ └── greet.proto └── ssl ├── ca.crt ├── ca.key ├── instructions.sh ├── server.crt ├── server.csr ├── server.key └── server.pem /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | out/ 4 | .classpath 5 | .project 6 | .gradle/ 7 | .idea/ 8 | .settings/ 9 | .vscode/ 10 | target/ 11 | 12 | # Ignore Gradle GUI config 13 | gradle-app.setting 14 | 15 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 16 | !gradle-wrapper.jar 17 | 18 | # Cache of project 19 | .gradletasknamecache 20 | 21 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 22 | # gradle/wrapper/gradle-wrapper.properties -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # this file is only used for GitHub 2 | # it is not related to your learning 3 | 4 | language: java 5 | 6 | script: 7 | # test the java components 8 | - ./gradlew test 9 | 10 | before_cache: 11 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 12 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 13 | cache: 14 | directories: 15 | - $HOME/.gradle/caches/ 16 | - $HOME/.gradle/wrapper/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grpc Java Course 2 | 3 | This is a companion repository for my [GRPC Java course](http://bit.ly/grpc-java-github) 4 | 5 | [![course logo](https://udemy-images.udemy.com/course/480x270/1685664_10e0_4.jpg)](http://bit.ly/grpc-java-github) 6 | 7 | # Content 8 | 9 | - Greeting Service 10 | - Calculator Service 11 | - Unary, Server Streaming, Client Streaming, BiDi Streaming 12 | - Error Handling, Deadlines, SSL Encryption 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // updated October 2019 to include the newer Gradle syntax 2 | // nothing else changes with the course, happy learning! 3 | 4 | plugins { 5 | id 'java' 6 | id 'com.google.protobuf' version '0.8.10' 7 | id 'idea' 8 | } 9 | 10 | group 'com.github.simplesteph.grpc' 11 | version '1.0-SNAPSHOT' 12 | 13 | sourceCompatibility = 1.8 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | protobuf { 20 | protoc { 21 | artifact = "com.google.protobuf:protoc:3.10.0" 22 | } 23 | plugins { 24 | grpc { 25 | artifact = 'io.grpc:protoc-gen-grpc-java:1.24.0' 26 | } 27 | } 28 | generateProtoTasks { 29 | all()*.plugins { 30 | grpc {} 31 | } 32 | } 33 | } 34 | 35 | dependencies { 36 | implementation 'io.grpc:grpc-netty-shaded:1.24.0' 37 | implementation 'io.grpc:grpc-protobuf:1.24.0' 38 | implementation 'io.grpc:grpc-stub:1.24.0' 39 | implementation "io.grpc:grpc-services:1.24.0" // reflection 40 | 41 | testCompile group: 'junit', name: 'junit', version: '4.12' 42 | 43 | // https://mvnrepository.com/artifact/org.mongodb/mongodb-driver-sync 44 | compile group: 'org.mongodb', name: 'mongodb-driver-sync', version: '3.8.2' 45 | } 46 | 47 | // if you have source imports issues, add the below 48 | sourceSets.main.java.srcDir new File(buildDir, 'generated/source') 49 | idea { 50 | module { 51 | // Marks the already(!) added srcDir as "generated" 52 | generatedSourceDirs += file('build/generated/source') 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simplesteph/grpc-java-course/f4352f6460b76a869b28bafbf9fdfc07f2c4b900/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue May 08 09:58:10 IST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-rc-2-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'grpc-java-course' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/blog/client/BlogClient.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.blog.client; 2 | 3 | import com.proto.blog.*; 4 | import io.grpc.ManagedChannel; 5 | import io.grpc.ManagedChannelBuilder; 6 | 7 | public class BlogClient { 8 | 9 | 10 | public static void main(String[] args) { 11 | System.out.println("Hello I'm a gRPC client for Blog"); 12 | 13 | BlogClient main = new BlogClient(); 14 | main.run(); 15 | } 16 | 17 | private void run() { 18 | ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) 19 | .usePlaintext() 20 | .build(); 21 | 22 | BlogServiceGrpc.BlogServiceBlockingStub blogClient = BlogServiceGrpc.newBlockingStub(channel); 23 | 24 | Blog blog = Blog.newBuilder() 25 | .setAuthorId("Stephane") 26 | .setTitle("New blog!") 27 | .setContent("Hello world this is my first blog!") 28 | .build(); 29 | 30 | CreateBlogResponse createResponse = blogClient.createBlog( 31 | CreateBlogRequest.newBuilder() 32 | .setBlog(blog) 33 | .build() 34 | ); 35 | 36 | System.out.println("Received create blog response"); 37 | System.out.println(createResponse.toString()); 38 | 39 | String blogId = createResponse.getBlog().getId(); 40 | 41 | System.out.println("Reading blog...."); 42 | 43 | ReadBlogResponse readBlogResponse = blogClient.readBlog(ReadBlogRequest.newBuilder() 44 | .setBlogId(blogId) 45 | .build()); 46 | 47 | System.out.println(readBlogResponse.toString()); 48 | 49 | // trigger a not found error 50 | // System.out.println("Reading blog with non existing id...."); 51 | // 52 | // ReadBlogResponse readBlogResponseNotFound = blogClient.readBlog(ReadBlogRequest.newBuilder() 53 | // .setBlogId("5bd83b5cf476074e2bff3495") 54 | // .build()); 55 | 56 | Blog newBlog = Blog.newBuilder() 57 | .setId(blogId) 58 | .setAuthorId("Changed Author") 59 | .setTitle("New blog (updated)!") 60 | .setContent("Hello world this is my first blog! I've added some more content") 61 | .build(); 62 | 63 | System.out.println("Updating blog..."); 64 | UpdateBlogResponse updateBlogResponse = blogClient.updateBlog( 65 | UpdateBlogRequest.newBuilder().setBlog(newBlog).build()); 66 | 67 | System.out.println("Updated blog"); 68 | System.out.println(updateBlogResponse.toString()); 69 | 70 | System.out.println("Deleting blog"); 71 | DeleteBlogResponse deleteBlogResponse = blogClient.deleteBlog( 72 | DeleteBlogRequest.newBuilder().setBlogId(blogId).build() 73 | ); 74 | 75 | System.out.println("Deleted blog"); 76 | 77 | 78 | // System.out.println("Reading blog...."); 79 | // // this one should return NOT_FOUND 80 | // ReadBlogResponse readBlogResponseAfterDeletion = blogClient.readBlog(ReadBlogRequest.newBuilder() 81 | // .setBlogId(blogId) 82 | // .build()); 83 | 84 | // we list the blogs in our database 85 | blogClient.listBlog(ListBlogRequest.newBuilder().build()).forEachRemaining( 86 | listBlogResponse -> System.out.println(listBlogResponse.getBlog().toString()) 87 | ); 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/blog/server/BlogServer.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.blog.server; 2 | 3 | import io.grpc.Server; 4 | import io.grpc.ServerBuilder; 5 | import io.grpc.protobuf.services.ProtoReflectionService; 6 | 7 | import java.io.IOException; 8 | 9 | public class BlogServer { 10 | 11 | public static void main(String[] args) throws IOException, InterruptedException { 12 | Server server = ServerBuilder.forPort(50051) 13 | .addService(new BlogServiceImpl()) 14 | .addService(ProtoReflectionService.newInstance()) // reflection 15 | .build(); 16 | 17 | server.start(); 18 | 19 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 20 | System.out.println("Received Shutdown Request"); 21 | server.shutdown(); 22 | System.out.println("Successfully stopped the server"); 23 | })); 24 | 25 | server.awaitTermination(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/blog/server/BlogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.blog.server; 2 | 3 | import com.mongodb.client.MongoClient; 4 | import com.mongodb.client.MongoClients; 5 | import com.mongodb.client.MongoCollection; 6 | import com.mongodb.client.MongoDatabase; 7 | import com.mongodb.client.result.DeleteResult; 8 | import com.proto.blog.*; 9 | import io.grpc.Status; 10 | import io.grpc.stub.StreamObserver; 11 | import org.bson.Document; 12 | import org.bson.types.ObjectId; 13 | 14 | import static com.mongodb.client.model.Filters.eq; 15 | 16 | public class BlogServiceImpl extends BlogServiceGrpc.BlogServiceImplBase { 17 | 18 | private MongoClient mongoClient = MongoClients.create("mongodb://localhost:27017"); 19 | private MongoDatabase database = mongoClient.getDatabase("mydb"); 20 | private MongoCollection collection = database.getCollection("blog"); 21 | 22 | 23 | @Override 24 | public void createBlog(CreateBlogRequest request, StreamObserver responseObserver) { 25 | 26 | System.out.println("Received Create Blog request"); 27 | 28 | Blog blog = request.getBlog(); 29 | 30 | Document doc = new Document("author_id", blog.getAuthorId()) 31 | .append("title", blog.getTitle()) 32 | .append("content", blog.getContent()); 33 | 34 | System.out.println("Inserting blog..."); 35 | // we insert (create) the document in mongoDB 36 | collection.insertOne(doc); 37 | 38 | 39 | // we retrieve the MongoDB generated ID 40 | String id = doc.getObjectId("_id").toString(); 41 | System.out.println("Inserted blog: " + id); 42 | 43 | CreateBlogResponse response = CreateBlogResponse.newBuilder() 44 | .setBlog(blog.toBuilder().setId(id).build()) 45 | .build(); 46 | 47 | responseObserver.onNext(response); 48 | 49 | responseObserver.onCompleted(); 50 | 51 | } 52 | 53 | 54 | @Override 55 | public void readBlog(ReadBlogRequest request, StreamObserver responseObserver) { 56 | System.out.println("Received Read Blog request"); 57 | 58 | String blogId = request.getBlogId(); 59 | 60 | System.out.println("Searching for a blog"); 61 | Document result = null; 62 | 63 | try { 64 | result = collection.find(eq("_id", new ObjectId(blogId))) 65 | .first(); 66 | } catch (Exception e) { 67 | responseObserver.onError( 68 | Status.NOT_FOUND 69 | .withDescription("The blog with the corresponding id was not found") 70 | .augmentDescription(e.getLocalizedMessage()) 71 | .asRuntimeException() 72 | ); 73 | } 74 | 75 | if (result == null) { 76 | System.out.println("Blog not found"); 77 | // we don't have a match 78 | responseObserver.onError( 79 | Status.NOT_FOUND 80 | .withDescription("The blog with the corresponding id was not found") 81 | .asRuntimeException() 82 | ); 83 | } else { 84 | System.out.println("Blog found, sending response"); 85 | Blog blog = documentToBlog(result); 86 | 87 | responseObserver.onNext(ReadBlogResponse.newBuilder().setBlog(blog).build()); 88 | 89 | responseObserver.onCompleted(); 90 | } 91 | 92 | } 93 | 94 | @Override 95 | public void updateBlog(UpdateBlogRequest request, StreamObserver responseObserver) { 96 | System.out.println("Received Update Blog request"); 97 | 98 | Blog blog = request.getBlog(); 99 | 100 | String blogId = blog.getId(); 101 | 102 | System.out.println("Searching for a blog so we can update it"); 103 | Document result = null; 104 | 105 | try { 106 | result = collection.find(eq("_id", new ObjectId(blogId))) 107 | .first(); 108 | } catch (Exception e) { 109 | responseObserver.onError( 110 | Status.NOT_FOUND 111 | .withDescription("The blog with the corresponding id was not found") 112 | .augmentDescription(e.getLocalizedMessage()) 113 | .asRuntimeException() 114 | ); 115 | } 116 | 117 | if (result == null) { 118 | System.out.println("Blog not found"); 119 | // we don't have a match 120 | responseObserver.onError( 121 | Status.NOT_FOUND 122 | .withDescription("The blog with the corresponding id was not found") 123 | .asRuntimeException() 124 | ); 125 | } else { 126 | Document replacement = new Document("author_id", blog.getAuthorId()) 127 | .append("title", blog.getTitle()) 128 | .append("content", blog.getContent()) 129 | .append("_id", new ObjectId(blogId)); 130 | 131 | System.out.println("Replacing blog in database..."); 132 | 133 | collection.replaceOne(eq("_id", result.getObjectId("_id")), replacement); 134 | 135 | System.out.println("Replaced! Sending as a response"); 136 | responseObserver.onNext( 137 | UpdateBlogResponse.newBuilder() 138 | .setBlog(documentToBlog(replacement)) 139 | .build() 140 | ); 141 | 142 | responseObserver.onCompleted(); 143 | } 144 | } 145 | 146 | private Blog documentToBlog(Document document){ 147 | return Blog.newBuilder() 148 | .setAuthorId(document.getString("author_id")) 149 | .setTitle(document.getString("title")) 150 | .setContent(document.getString("content")) 151 | .setId(document.getObjectId("_id").toString()) 152 | .build(); 153 | } 154 | 155 | @Override 156 | public void deleteBlog(DeleteBlogRequest request, StreamObserver responseObserver) { 157 | System.out.println("Received Delete Blog Request"); 158 | 159 | String blogId = request.getBlogId(); 160 | DeleteResult result = null; 161 | try { 162 | result = collection.deleteOne(eq("_id", new ObjectId(blogId))); 163 | } catch (Exception e) { 164 | System.out.println("Blog not found"); 165 | responseObserver.onError( 166 | Status.NOT_FOUND 167 | .withDescription("The blog with the corresponding id was not found") 168 | .augmentDescription(e.getLocalizedMessage()) 169 | .asRuntimeException() 170 | ); 171 | } 172 | 173 | if (result.getDeletedCount() == 0) { 174 | System.out.println("Blog not found"); 175 | responseObserver.onError( 176 | Status.NOT_FOUND 177 | .withDescription("The blog with the corresponding id was not found") 178 | .asRuntimeException() 179 | ); 180 | } else { 181 | System.out.println("Blog was deleted"); 182 | responseObserver.onNext(DeleteBlogResponse.newBuilder() 183 | .setBlogId(blogId) 184 | .build()); 185 | 186 | responseObserver.onCompleted(); 187 | } 188 | 189 | } 190 | 191 | @Override 192 | public void listBlog(ListBlogRequest request, StreamObserver responseObserver) { 193 | System.out.println("Received List Blog Request"); 194 | 195 | collection.find().iterator().forEachRemaining(document -> responseObserver.onNext( 196 | ListBlogResponse.newBuilder().setBlog(documentToBlog(document)).build() 197 | )); 198 | 199 | responseObserver.onCompleted(); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/calculator/client/CalculatorClient.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.calculator.client; 2 | 3 | import com.proto.calculator.*; 4 | import io.grpc.ManagedChannel; 5 | import io.grpc.ManagedChannelBuilder; 6 | import io.grpc.StatusRuntimeException; 7 | import io.grpc.stub.StreamObserver; 8 | 9 | import java.util.Arrays; 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class CalculatorClient { 14 | 15 | 16 | public static void main(String[] args) { 17 | System.out.println("Hello I'm a gRPC client"); 18 | 19 | CalculatorClient main = new CalculatorClient(); 20 | main.run(); 21 | } 22 | 23 | private void run() { 24 | ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50052) 25 | .usePlaintext() 26 | .build(); 27 | 28 | doUnaryCall(channel); 29 | 30 | doServerStreamingCall(channel); 31 | 32 | doClientStreamingCall(channel); 33 | 34 | doBidiStreamingCall(channel); 35 | 36 | doErrorCall(channel); 37 | 38 | 39 | System.out.println("Shutting down channel"); 40 | channel.shutdown(); 41 | 42 | } 43 | 44 | 45 | private void doUnaryCall(ManagedChannel channel){ 46 | // created a greet service client (blocking - synchronous) 47 | 48 | CalculatorServiceGrpc.CalculatorServiceBlockingStub stub = CalculatorServiceGrpc.newBlockingStub(channel); 49 | 50 | SumRequest request = SumRequest.newBuilder() 51 | .setFirstNumber(10) 52 | .setSecondNumber(25) 53 | .build(); 54 | 55 | SumResponse response = stub.sum(request); 56 | 57 | System.out.println(request.getFirstNumber() + " + " + request.getSecondNumber() + " = " + response.getSumResult()); 58 | 59 | } 60 | 61 | private void doServerStreamingCall(ManagedChannel channel) { 62 | CalculatorServiceGrpc.CalculatorServiceBlockingStub stub = CalculatorServiceGrpc.newBlockingStub(channel); 63 | 64 | Long number = 120L; 65 | 66 | stub.primeNumberDecomposition(PrimeNumberDecompositionRequest.newBuilder() 67 | .setNumber(number).build()) 68 | .forEachRemaining(primeNumberDecompositionResponse -> { 69 | System.out.println(primeNumberDecompositionResponse.getPrimeFactor()); 70 | }); 71 | 72 | } 73 | 74 | private void doClientStreamingCall(ManagedChannel channel){ 75 | CalculatorServiceGrpc.CalculatorServiceStub asyncClient = CalculatorServiceGrpc.newStub(channel); 76 | 77 | CountDownLatch latch = new CountDownLatch(1); 78 | 79 | StreamObserver requestObserver = asyncClient.computeAverage(new StreamObserver() { 80 | @Override 81 | public void onNext(ComputeAverageResponse value) { 82 | System.out.println("Received a response from the server"); 83 | System.out.println(value.getAverage()); 84 | } 85 | 86 | @Override 87 | public void onError(Throwable t) { 88 | 89 | } 90 | 91 | @Override 92 | public void onCompleted() { 93 | System.out.println("Server has completed sending us data"); 94 | latch.countDown(); 95 | } 96 | }); 97 | 98 | // we send 10000 messages to our server (client streaming) 99 | for (int i = 0; i < 10000; i++){ 100 | requestObserver.onNext(ComputeAverageRequest.newBuilder() 101 | .setNumber(i) 102 | .build()); 103 | } 104 | 105 | // we expect the average to be 10 / 4 = 2.5 106 | 107 | requestObserver.onCompleted(); 108 | 109 | try { 110 | latch.await(3, TimeUnit.SECONDS); 111 | } catch (InterruptedException e) { 112 | e.printStackTrace(); 113 | } 114 | } 115 | 116 | private void doBidiStreamingCall(ManagedChannel channel){ 117 | CalculatorServiceGrpc.CalculatorServiceStub asyncClient = CalculatorServiceGrpc.newStub(channel); 118 | 119 | CountDownLatch latch = new CountDownLatch(1); 120 | 121 | 122 | StreamObserver requestObserver = asyncClient.findMaximum(new StreamObserver() { 123 | @Override 124 | public void onNext(FindMaximumResponse value) { 125 | System.out.println("Got new maximum from Server: " + value.getMaximum()); 126 | } 127 | 128 | @Override 129 | public void onError(Throwable t) { 130 | latch.countDown(); 131 | } 132 | 133 | @Override 134 | public void onCompleted() { 135 | System.out.println("Server is done sending messages"); 136 | } 137 | }); 138 | 139 | 140 | Arrays.asList(3, 5, 17, 9, 8, 30, 12).forEach( 141 | number -> { 142 | System.out.println("Sending number: " + number); 143 | requestObserver.onNext(FindMaximumRequest.newBuilder() 144 | .setNumber(number) 145 | .build()); 146 | try { 147 | Thread.sleep(100); 148 | } catch (InterruptedException e) { 149 | e.printStackTrace(); 150 | } 151 | } 152 | ); 153 | 154 | requestObserver.onCompleted(); 155 | 156 | try { 157 | latch.await(3, TimeUnit.SECONDS); 158 | } catch (InterruptedException e) { 159 | e.printStackTrace(); 160 | } 161 | } 162 | 163 | private void doErrorCall(ManagedChannel channel) { 164 | CalculatorServiceGrpc.CalculatorServiceBlockingStub blockingStub = CalculatorServiceGrpc.newBlockingStub(channel); 165 | 166 | int number = -1; 167 | 168 | try { 169 | blockingStub.squareRoot(SquareRootRequest.newBuilder() 170 | .setNumber(number) 171 | .build()); 172 | } catch (StatusRuntimeException e) { 173 | System.out.println("Got an exception for square root!"); 174 | e.printStackTrace(); 175 | } 176 | 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/calculator/server/CalculatorServer.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.calculator.server; 2 | 3 | import io.grpc.Server; 4 | import io.grpc.ServerBuilder; 5 | import io.grpc.protobuf.services.ProtoReflectionService; 6 | 7 | import java.io.IOException; 8 | 9 | public class CalculatorServer { 10 | 11 | public static void main(String[] args) throws IOException, InterruptedException { 12 | Server server = ServerBuilder.forPort(50052) 13 | .addService(new CalculatorServiceImpl()) 14 | .addService(ProtoReflectionService.newInstance()) // reflection 15 | .build(); 16 | 17 | server.start(); 18 | 19 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 20 | System.out.println("Received Shutdown Request"); 21 | server.shutdown(); 22 | System.out.println("Successfully stopped the server"); 23 | })); 24 | 25 | server.awaitTermination(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/calculator/server/CalculatorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.calculator.server; 2 | 3 | import com.proto.calculator.*; 4 | import io.grpc.Context; 5 | import io.grpc.Status; 6 | import io.grpc.stub.StreamObserver; 7 | 8 | public class CalculatorServiceImpl extends CalculatorServiceGrpc.CalculatorServiceImplBase { 9 | 10 | @Override 11 | public void sum(SumRequest request, StreamObserver responseObserver) { 12 | 13 | SumResponse sumResponse = SumResponse.newBuilder() 14 | .setSumResult(request.getFirstNumber() + request.getSecondNumber()) 15 | .build(); 16 | 17 | responseObserver.onNext(sumResponse); 18 | 19 | responseObserver.onCompleted(); 20 | 21 | } 22 | 23 | @Override 24 | public void primeNumberDecomposition(PrimeNumberDecompositionRequest request, StreamObserver responseObserver) { 25 | 26 | Long number = request.getNumber(); 27 | Long divisor = 2L; 28 | 29 | while (number > 1) { 30 | if (number % divisor == 0) { 31 | number = number / divisor; 32 | responseObserver.onNext(PrimeNumberDecompositionResponse.newBuilder() 33 | .setPrimeFactor(divisor) 34 | .build()); 35 | } else { 36 | divisor = divisor + 1; 37 | System.out.println(divisor); 38 | } 39 | } 40 | responseObserver.onCompleted(); 41 | 42 | } 43 | 44 | @Override 45 | public StreamObserver computeAverage(StreamObserver responseObserver) { 46 | 47 | StreamObserver requestObserver = new StreamObserver() { 48 | // running sum and count 49 | int sum = 0; 50 | int count = 0; 51 | 52 | @Override 53 | public void onNext(ComputeAverageRequest value) { 54 | // increment the sum 55 | sum += value.getNumber(); 56 | // increment the count 57 | count += 1; 58 | } 59 | 60 | @Override 61 | public void onError(Throwable t) { 62 | 63 | } 64 | 65 | @Override 66 | public void onCompleted() { 67 | // compute average 68 | double average = (double) sum / count; 69 | responseObserver.onNext( 70 | ComputeAverageResponse.newBuilder() 71 | .setAverage(average) 72 | .build() 73 | ); 74 | responseObserver.onCompleted(); 75 | } 76 | }; 77 | 78 | return requestObserver; 79 | } 80 | 81 | @Override 82 | public StreamObserver findMaximum(StreamObserver responseObserver) { 83 | 84 | return new StreamObserver() { 85 | 86 | int currentMaximum = 0; 87 | 88 | @Override 89 | public void onNext(FindMaximumRequest value) { 90 | int currentNumber = value.getNumber(); 91 | 92 | if (currentNumber > currentMaximum) { 93 | currentMaximum = currentNumber; 94 | responseObserver.onNext( 95 | FindMaximumResponse.newBuilder() 96 | .setMaximum(currentMaximum) 97 | .build() 98 | ); 99 | } else { 100 | // nothing 101 | } 102 | } 103 | 104 | @Override 105 | public void onError(Throwable t) { 106 | responseObserver.onCompleted(); 107 | } 108 | 109 | @Override 110 | public void onCompleted() { 111 | // send the current last maximum 112 | responseObserver.onNext( 113 | FindMaximumResponse.newBuilder() 114 | .setMaximum(currentMaximum) 115 | .build()); 116 | // the server is done sending data 117 | responseObserver.onCompleted(); 118 | } 119 | }; 120 | 121 | } 122 | 123 | @Override 124 | public void squareRoot(SquareRootRequest request, StreamObserver responseObserver) { 125 | 126 | Integer number = request.getNumber(); 127 | 128 | if (number >= 0) { 129 | double numberRoot = Math.sqrt(number); 130 | responseObserver.onNext( 131 | SquareRootResponse.newBuilder() 132 | .setNumberRoot(numberRoot) 133 | .build() 134 | ); 135 | responseObserver.onCompleted(); 136 | } else { 137 | // we construct the exception 138 | responseObserver.onError( 139 | Status.INVALID_ARGUMENT 140 | .withDescription("The number being sent is not positive") 141 | .augmentDescription("Number sent: " + number) 142 | .asRuntimeException() 143 | ); 144 | } 145 | 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/greeting/client/GreetingClient.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.greeting.client; 2 | 3 | import com.proto.greet.*; 4 | import io.grpc.*; 5 | import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; 6 | import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder; 7 | import io.grpc.stub.StreamObserver; 8 | 9 | import javax.net.ssl.SSLException; 10 | import java.io.File; 11 | import java.util.Arrays; 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class GreetingClient { 16 | 17 | public static void main(String[] args) throws SSLException { 18 | System.out.println("Hello I'm a gRPC client"); 19 | 20 | GreetingClient main = new GreetingClient(); 21 | main.run(); 22 | } 23 | 24 | private void run() throws SSLException { 25 | ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 50051) 26 | .usePlaintext() 27 | .build(); 28 | 29 | 30 | // With server authentication SSL/TLS; custom CA root certificates; not on Android 31 | // ManagedChannel secureChannel = NettyChannelBuilder.forAddress("localhost", 50051) 32 | // .sslContext(GrpcSslContexts.forClient().trustManager(new File("ssl/ca.crt")).build()) 33 | // .build(); 34 | 35 | doUnaryCall(channel); 36 | 37 | doServerStreamingCall(channel); 38 | 39 | doClientStreamingCall(channel); 40 | 41 | doBiDiStreamingCall(channel); 42 | 43 | doUnaryCallWithDeadline(channel); 44 | 45 | // doUnaryCall(secureChannel); 46 | 47 | System.out.println("Shutting down channel"); 48 | channel.shutdown(); 49 | 50 | } 51 | 52 | private void doUnaryCall(ManagedChannel channel) { 53 | // created a greet service client (blocking - synchronous) 54 | GreetServiceGrpc.GreetServiceBlockingStub greetClient = GreetServiceGrpc.newBlockingStub(channel); 55 | 56 | // Unary 57 | // created a protocol buffer greeting message 58 | Greeting greeting = Greeting.newBuilder() 59 | .setFirstName("Stephane") 60 | .setLastName("Maarek") 61 | .build(); 62 | 63 | // do the same for a GreetRequest 64 | GreetRequest greetRequest = GreetRequest.newBuilder() 65 | .setGreeting(greeting) 66 | .build(); 67 | 68 | // call the RPC and get back a GreetResponse (protocol buffers) 69 | GreetResponse greetResponse = greetClient.greet(greetRequest); 70 | 71 | System.out.println(greetResponse.getResult()); 72 | 73 | } 74 | 75 | private void doServerStreamingCall(ManagedChannel channel) { 76 | GreetServiceGrpc.GreetServiceBlockingStub greetClient = GreetServiceGrpc.newBlockingStub(channel); 77 | 78 | // Server Streaming 79 | // we prepare the request 80 | GreetManyTimesRequest greetManyTimesRequest = 81 | GreetManyTimesRequest.newBuilder() 82 | .setGreeting(Greeting.newBuilder().setFirstName("Stephane")) 83 | .build(); 84 | 85 | // we stream the responses (in a blocking manner) 86 | greetClient.greetManyTimes(greetManyTimesRequest) 87 | .forEachRemaining(greetManyTimesResponse -> { 88 | System.out.println(greetManyTimesResponse.getResult()); 89 | }); 90 | 91 | } 92 | 93 | private void doClientStreamingCall(ManagedChannel channel) { 94 | // create an asynchronous client 95 | GreetServiceGrpc.GreetServiceStub asyncClient = GreetServiceGrpc.newStub(channel); 96 | 97 | CountDownLatch latch = new CountDownLatch(1); 98 | 99 | StreamObserver requestObserver = asyncClient.longGreet(new StreamObserver() { 100 | @Override 101 | public void onNext(LongGreetResponse value) { 102 | // we get a response from the server 103 | System.out.println("Received a response from the server"); 104 | System.out.println(value.getResult()); 105 | // onNext will be called only once 106 | } 107 | 108 | @Override 109 | public void onError(Throwable t) { 110 | // we get an error from the server 111 | } 112 | 113 | @Override 114 | public void onCompleted() { 115 | // the server is done sending us data 116 | // onCompleted will be called right after onNext() 117 | System.out.println("Server has completed sending us something"); 118 | latch.countDown(); 119 | } 120 | }); 121 | 122 | // streaming message #1 123 | System.out.println("sending message 1"); 124 | requestObserver.onNext(LongGreetRequest.newBuilder() 125 | .setGreeting(Greeting.newBuilder() 126 | .setFirstName("Stephane") 127 | .build()) 128 | .build()); 129 | 130 | // streaming message #2 131 | System.out.println("sending message 2"); 132 | requestObserver.onNext(LongGreetRequest.newBuilder() 133 | .setGreeting(Greeting.newBuilder() 134 | .setFirstName("John") 135 | .build()) 136 | .build()); 137 | 138 | // streaming message #3 139 | System.out.println("sending message 3"); 140 | requestObserver.onNext(LongGreetRequest.newBuilder() 141 | .setGreeting(Greeting.newBuilder() 142 | .setFirstName("Marc") 143 | .build()) 144 | .build()); 145 | 146 | // we tell the server that the client is done sending data 147 | requestObserver.onCompleted(); 148 | 149 | try { 150 | latch.await(3L, TimeUnit.SECONDS); 151 | } catch (InterruptedException e) { 152 | e.printStackTrace(); 153 | } 154 | } 155 | 156 | private void doBiDiStreamingCall(ManagedChannel channel) { 157 | GreetServiceGrpc.GreetServiceStub asyncClient = GreetServiceGrpc.newStub(channel); 158 | 159 | CountDownLatch latch = new CountDownLatch(1); 160 | 161 | StreamObserver requestObserver = asyncClient.greetEveryone(new StreamObserver() { 162 | @Override 163 | public void onNext(GreetEveryoneResponse value) { 164 | System.out.println("Response from server: " + value.getResult()); 165 | } 166 | 167 | @Override 168 | public void onError(Throwable t) { 169 | latch.countDown(); 170 | } 171 | 172 | @Override 173 | public void onCompleted() { 174 | System.out.println("Server is done sending data"); 175 | latch.countDown(); 176 | } 177 | }); 178 | 179 | Arrays.asList("Stephane", "John", "Marc", "Patricia").forEach( 180 | name -> { 181 | System.out.println("Sending: " + name); 182 | requestObserver.onNext(GreetEveryoneRequest.newBuilder() 183 | .setGreeting(Greeting.newBuilder() 184 | .setFirstName(name)) 185 | .build()); 186 | try { 187 | Thread.sleep(100); 188 | } catch (InterruptedException e) { 189 | e.printStackTrace(); 190 | } 191 | } 192 | ); 193 | 194 | requestObserver.onCompleted(); 195 | 196 | try { 197 | latch.await(3, TimeUnit.SECONDS); 198 | } catch (InterruptedException e) { 199 | e.printStackTrace(); 200 | } 201 | 202 | } 203 | 204 | private void doUnaryCallWithDeadline(ManagedChannel channel) { 205 | GreetServiceGrpc.GreetServiceBlockingStub blockingStub = GreetServiceGrpc.newBlockingStub(channel); 206 | 207 | // first call (3000 ms deadline) 208 | try { 209 | System.out.println("Sending a request with a deadline of 3000 ms"); 210 | GreetWithDeadlineResponse response = blockingStub.withDeadlineAfter(3000, TimeUnit.MILLISECONDS).greetWithDeadline(GreetWithDeadlineRequest.newBuilder().setGreeting( 211 | Greeting.newBuilder().setFirstName("Stephane") 212 | ).build()); 213 | System.out.println(response.getResult()); 214 | } catch (StatusRuntimeException e) { 215 | if (e.getStatus().getCode() == Status.Code.DEADLINE_EXCEEDED) { 216 | System.out.println("Deadline has been exceeded, we don't want the response"); 217 | } else { 218 | e.printStackTrace(); 219 | } 220 | } 221 | 222 | 223 | // second call (100 ms deadline) 224 | try { 225 | System.out.println("Sending a request with a deadline of 100 ms"); 226 | GreetWithDeadlineResponse response = blockingStub.withDeadline(Deadline.after(100, TimeUnit.MILLISECONDS)).greetWithDeadline(GreetWithDeadlineRequest.newBuilder().setGreeting( 227 | Greeting.newBuilder().setFirstName("Stephane") 228 | ).build()); 229 | System.out.println(response.getResult()); 230 | } catch (StatusRuntimeException e) { 231 | if (e.getStatus().getCode() == Status.Code.DEADLINE_EXCEEDED) { 232 | System.out.println("Deadline has been exceeded, we don't want the response"); 233 | } else { 234 | e.printStackTrace(); 235 | } 236 | } 237 | 238 | 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/greeting/server/GreetServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.greeting.server; 2 | 3 | import com.proto.greet.*; 4 | import io.grpc.Context; 5 | import io.grpc.stub.StreamObserver; 6 | 7 | import java.util.stream.Stream; 8 | 9 | public class GreetServiceImpl extends GreetServiceGrpc.GreetServiceImplBase { 10 | 11 | @Override 12 | public void greet(GreetRequest request, StreamObserver responseObserver) { 13 | // extract the fields we need 14 | Greeting greeting = request.getGreeting(); 15 | String firstName = greeting.getFirstName(); 16 | 17 | // create the response 18 | String result = "Hello " + firstName; 19 | GreetResponse response = GreetResponse.newBuilder() 20 | .setResult(result) 21 | .build(); 22 | 23 | // send the response 24 | responseObserver.onNext(response); 25 | 26 | // complete the RPC call 27 | responseObserver.onCompleted(); 28 | } 29 | 30 | @Override 31 | public void greetManyTimes(GreetManyTimesRequest request, StreamObserver responseObserver) { 32 | String firstName = request.getGreeting().getFirstName(); 33 | 34 | try { 35 | for (int i = 0; i < 10; i++) { 36 | String result = "Hello " + firstName + ", response number: " + i; 37 | GreetManyTimesResponse response = GreetManyTimesResponse.newBuilder() 38 | .setResult(result) 39 | .build(); 40 | 41 | responseObserver.onNext(response); 42 | Thread.sleep(1000L); 43 | } 44 | } catch (InterruptedException e) { 45 | e.printStackTrace(); 46 | } finally { 47 | responseObserver.onCompleted(); 48 | } 49 | } 50 | 51 | @Override 52 | public StreamObserver longGreet(StreamObserver responseObserver) { 53 | // we create the requestObserver that we'll return in this function 54 | StreamObserver requestObserver = new StreamObserver() { 55 | 56 | String result = ""; 57 | 58 | @Override 59 | public void onNext(LongGreetRequest value) { 60 | // client sends a message 61 | result += "Hello " + value.getGreeting().getFirstName() + "! "; 62 | } 63 | 64 | @Override 65 | public void onError(Throwable t) { 66 | // client sends an error 67 | } 68 | 69 | @Override 70 | public void onCompleted() { 71 | // client is done 72 | responseObserver.onNext( 73 | LongGreetResponse.newBuilder() 74 | .setResult(result) 75 | .build() 76 | ); 77 | responseObserver.onCompleted(); 78 | } 79 | }; 80 | 81 | 82 | return requestObserver; 83 | } 84 | 85 | @Override 86 | public StreamObserver greetEveryone(StreamObserver responseObserver) { 87 | StreamObserver requestObserver = new StreamObserver() { 88 | @Override 89 | public void onNext(GreetEveryoneRequest value) { 90 | String result = "Hello " + value.getGreeting().getFirstName(); 91 | GreetEveryoneResponse greetEveryoneResponse = GreetEveryoneResponse.newBuilder() 92 | .setResult(result) 93 | .build(); 94 | 95 | responseObserver.onNext(greetEveryoneResponse); 96 | } 97 | 98 | @Override 99 | public void onError(Throwable t) { 100 | // do nothing 101 | } 102 | 103 | @Override 104 | public void onCompleted() { 105 | responseObserver.onCompleted(); 106 | } 107 | }; 108 | 109 | return requestObserver; 110 | } 111 | 112 | @Override 113 | public void greetWithDeadline(GreetWithDeadlineRequest request, StreamObserver responseObserver) { 114 | 115 | Context current = Context.current(); 116 | 117 | try { 118 | 119 | for (int i = 0; i < 3; i++) { 120 | if (!current.isCancelled()) { 121 | System.out.println("sleep for 100 ms"); 122 | Thread.sleep(100); 123 | } else { 124 | return; 125 | } 126 | } 127 | 128 | System.out.println("send response"); 129 | responseObserver.onNext( 130 | GreetWithDeadlineResponse.newBuilder() 131 | .setResult("hello " + request.getGreeting().getFirstName()) 132 | .build() 133 | ); 134 | 135 | responseObserver.onCompleted(); 136 | } catch (InterruptedException e) { 137 | e.printStackTrace(); 138 | } 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/github/simplesteph/grpc/greeting/server/GreetingServer.java: -------------------------------------------------------------------------------- 1 | package com.github.simplesteph.grpc.greeting.server; 2 | 3 | import io.grpc.Server; 4 | import io.grpc.ServerBuilder; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public class GreetingServer { 10 | 11 | public static void main(String[] args) throws IOException, InterruptedException { 12 | System.out.println("Hello gRPC"); 13 | 14 | // plaintext server 15 | Server server = ServerBuilder.forPort(50051) 16 | .addService(new GreetServiceImpl()) 17 | .build(); 18 | 19 | // secure server 20 | // Server server = ServerBuilder.forPort(50051) 21 | // .addService(new GreetServiceImpl()) 22 | // .useTransportSecurity( 23 | // new File("ssl/server.crt"), 24 | // new File("ssl/server.pem") 25 | // ) 26 | // .build(); 27 | 28 | 29 | server.start(); 30 | 31 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 32 | System.out.println("Received Shutdown Request"); 33 | server.shutdown(); 34 | System.out.println("Successfully stopped the server"); 35 | })); 36 | 37 | server.awaitTermination(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/proto/blog/blog.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package blog; 4 | 5 | option java_package = "com.proto.blog"; 6 | option java_multiple_files = true; 7 | 8 | message Blog { 9 | string id = 1; 10 | string author_id = 2; 11 | string title = 3; 12 | string content = 4; 13 | } 14 | 15 | message CreateBlogRequest { 16 | Blog blog = 1; // the id shouldn't matter 17 | } 18 | 19 | message CreateBlogResponse { 20 | Blog blog = 1; // the id will correspond to the one in MongoDB 21 | } 22 | 23 | message ReadBlogRequest { 24 | string blog_id = 1; 25 | } 26 | 27 | message ReadBlogResponse { 28 | Blog blog = 1; 29 | } 30 | 31 | message UpdateBlogRequest { 32 | Blog blog = 1; 33 | } 34 | 35 | message UpdateBlogResponse { 36 | Blog blog = 1; 37 | } 38 | 39 | message DeleteBlogRequest { 40 | string blog_id = 1; 41 | } 42 | 43 | message DeleteBlogResponse { 44 | string blog_id = 1; 45 | } 46 | 47 | message ListBlogRequest { 48 | 49 | } 50 | 51 | message ListBlogResponse { 52 | Blog blog = 1; 53 | } 54 | 55 | service BlogService { 56 | rpc CreateBlog(CreateBlogRequest) returns (CreateBlogResponse){}; 57 | rpc ReadBlog(ReadBlogRequest) returns (ReadBlogResponse){}; // return NOT_FOUND if not found 58 | rpc UpdateBlog(UpdateBlogRequest) returns (UpdateBlogResponse){}; // return NOT_FOUND if not found 59 | rpc DeleteBlog(DeleteBlogRequest) returns (DeleteBlogResponse){}; // return NOT_FOUND if not found 60 | rpc ListBlog(ListBlogRequest) returns (stream ListBlogResponse){}; 61 | } -------------------------------------------------------------------------------- /src/main/proto/calculator/calculator.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package calculator; 4 | 5 | option java_package = "com.proto.calculator"; 6 | option java_multiple_files = true; 7 | 8 | 9 | message SumRequest { 10 | int32 first_number = 1; 11 | int32 second_number = 2; 12 | } 13 | 14 | message SumResponse { 15 | int32 sum_result = 1; 16 | } 17 | 18 | message PrimeNumberDecompositionRequest { 19 | int64 number = 1; 20 | } 21 | 22 | message PrimeNumberDecompositionResponse { 23 | int64 prime_factor = 1; 24 | } 25 | 26 | message ComputeAverageRequest { 27 | int32 number = 1; 28 | } 29 | 30 | message ComputeAverageResponse { 31 | double average = 1; 32 | } 33 | 34 | message FindMaximumRequest { 35 | int32 number = 1; 36 | } 37 | 38 | message FindMaximumResponse { 39 | int32 maximum = 1; 40 | } 41 | 42 | message SquareRootRequest { 43 | int32 number = 1; 44 | } 45 | 46 | message SquareRootResponse { 47 | double number_root = 1; 48 | } 49 | 50 | service CalculatorService { 51 | rpc Sum(SumRequest) returns (SumResponse) {}; 52 | 53 | rpc PrimeNumberDecomposition(PrimeNumberDecompositionRequest) returns (stream PrimeNumberDecompositionResponse) {}; 54 | 55 | rpc ComputeAverage(stream ComputeAverageRequest) returns (ComputeAverageResponse) {}; 56 | 57 | rpc FindMaximum(stream FindMaximumRequest) returns (stream FindMaximumResponse) {}; 58 | 59 | // error handling 60 | // this RPC will throw an exception if the sent number is negative 61 | // The error being sent is of type INVALID_ARGUMENT 62 | rpc SquareRoot(SquareRootRequest) returns (SquareRootResponse) {}; 63 | } -------------------------------------------------------------------------------- /src/main/proto/dummy/dummy.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package dummy; 4 | 5 | option java_package = "com.proto.dummy"; 6 | option java_multiple_files = true; 7 | 8 | message DummyMessage {} 9 | 10 | service DummyService {} -------------------------------------------------------------------------------- /src/main/proto/greet/greet.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package greet; 4 | 5 | option java_package = "com.proto.greet"; 6 | option java_multiple_files = true; 7 | 8 | message Greeting { 9 | string first_name = 1; 10 | string last_name = 2; 11 | } 12 | 13 | message GreetRequest { 14 | Greeting greeting = 1; 15 | } 16 | 17 | message GreetResponse { 18 | string result = 1; 19 | } 20 | 21 | message GreetManyTimesRequest { 22 | Greeting greeting = 1; 23 | } 24 | 25 | message GreetManyTimesResponse { 26 | string result = 1; 27 | } 28 | 29 | message LongGreetRequest { 30 | Greeting greeting = 1; 31 | } 32 | 33 | message LongGreetResponse { 34 | string result = 1; 35 | } 36 | 37 | message GreetEveryoneRequest { 38 | Greeting greeting = 1; 39 | } 40 | 41 | message GreetEveryoneResponse { 42 | string result = 1; 43 | } 44 | 45 | message GreetWithDeadlineRequest { 46 | Greeting greeting = 1; 47 | } 48 | 49 | message GreetWithDeadlineResponse { 50 | string result = 1; 51 | } 52 | 53 | service GreetService { 54 | // Unary 55 | rpc Greet(GreetRequest) returns (GreetResponse) {}; 56 | 57 | // Server Streaming 58 | rpc GreetManyTimes(GreetManyTimesRequest) returns (stream GreetManyTimesResponse) {}; 59 | 60 | // Client Streaming 61 | rpc LongGreet(stream LongGreetRequest) returns (LongGreetResponse) {}; 62 | 63 | // BiDi Streaming 64 | rpc GreetEveryone(stream GreetEveryoneRequest) returns (stream GreetEveryoneResponse) {}; 65 | 66 | // Rpc with Deadline 67 | // this Rpc will complete in no less than 300ms 68 | rpc GreetWithDeadline(GreetWithDeadlineRequest) returns (GreetWithDeadlineResponse) {}; 69 | } -------------------------------------------------------------------------------- /ssl/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEpDCCAowCCQCiWAvGKbv3DDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls 3 | b2NhbGhvc3QwHhcNMTgwNTEwMTAyNzU5WhcNMTkwNTEwMTAyNzU5WjAUMRIwEAYD 4 | VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDp 5 | STVw1XZY5i1oFxS95jWuEk3A3aGB/A7imufGgLqsGqNKckg7OHMKMWgUHhUesflj 6 | 9XTUfHGXoCegUUNs2YlU0iStyvBO1n6r6sh7WInCz11u9ODEFqMT9d+zgrxwX1ft 7 | A6jxeIdsfYaGP1KywsHeUuogdbxMmDkfHce/OOOuHkq5PpsQLQHQk0m5UB9lyw+o 8 | O0rHgY/tRXMEiurUcJTPRXReOJgQvpJ1m7Xi2VzxFFlQSUvLH3RvJUvNyMFN9tgJ 9 | v2EmgiyHK8NVyPdn+phn49E/3vI7gHhaj7OLQs20WxlAnaCKrdyzN2OdV+SVaMCz 10 | 7/gO421djfQyzoD1ms/i6fTLms8w8Kz00yUcgAkOYhjJ2s0y/F6NeNz8hWcMfjY/ 11 | Pgc3bv61KlHOqTVbkMH19tSeKvPhW9J6SZ2wxxgLFSW1QD2mG6pyrbgV8s05i0HH 12 | +41SBHGFD4C6COAz8/7HiSuaUBFpgbfzmeEibKGnd/WsL7Km7rQAnoJBzpGTPJw6 13 | B1rTrPYpTXH1frv1ThFm5neEptpanXszamOdaht9hg46S743JDCnkv0cls/5nfc2 14 | zvGAay8+R8pRo4QDeAmKt2dY8uzJ/nCNbc+LUCa9vkTI54g9Q0kx0hCdkAwwWrVA 15 | iXSf21kT5+JHmqRGmjRIBt0PPoT3a67ENuGBMMNDUwIDAQABMA0GCSqGSIb3DQEB 16 | CwUAA4ICAQCzkRdsiRhLpu5FF7WttO/09n8uHZyT0SYaCGuO9iBZuhRroM+VIJ1J 17 | dJc4vyuKzzeIRsN0cD8mMDjWSviEPnJe/Wpyi6YKTdWahcOIom5bGvUVV4Oezu9J 18 | LBuVUo9kMfFSRyaTBx8sWuTBpvZdnSf3ZYPbgfogNDcJmku43jDZRDgeUaY+CJm3 19 | ZIkeZMYS3jtr8l4XFeT61RXB02lhHtBIQoYQIMAc3eP+VuNZZAddtTMzMJnHJjfQ 20 | VC6CBuccjytC+IQU/+5OPGlnfhPRtpz+Wvlv6NqJzOnlARKOGvMQahO1HdFNpotq 21 | gj5uHRxBkApN5155R6Q+z7rsxNSd+jq76VCJrUdjGfZ6wlOqh5pISTdhGmE5DssB 22 | BbWxWEcGqka0+5t3SaLZZYXITqSVWWufN6JzUBC4P+77euGWJGCBi5tKjEuN7OuF 23 | JCTPtuGijzD1LoG693If45OkwJPG/d8eIRIH6IQ/KEkVRIcuObC1he9ryX9p3DhA 24 | 2wfdWX0H0DoZWOWxUTY0L0JN2S4Mi3g1Kw3Rean/HwCeuox1mkkahXpmxwZ9xlAF 25 | dk437tkkprJwsK7fqhQaBFZpjQEKpZH1uiHkyZaDrhAic+pfBc82FY8idNybbSkb 26 | COuOr/YoRvePSIa4MFBsWpYkKX2ykXJsmuV2Z+B6HoNM5xNUArILjw== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /ssl/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,38DC52010829E568 4 | 5 | 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 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /ssl/instructions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Inspired from: https://github.com/grpc/grpc-java/tree/master/examples#generating-self-signed-certificates-for-use-with-grpc 3 | 4 | # Output files 5 | # ca.key: Certificate Authority private key file (this shouldn't be shared in real-life) 6 | # ca.crt: Certificate Authority trust certificate (this should be shared with users in real-life) 7 | # server.key: Server private key, password protected (this shouldn't be shared) 8 | # server.csr: Server certificate signing request (this should be shared with the CA owner) 9 | # server.crt: Server certificate signed by the CA (this would be sent back by the CA owner) - keep on server 10 | # server.pem: Conversion of server.key into a format gRPC likes (this shouldn't be shared) 11 | 12 | # Summary 13 | # Private files: ca.key, server.key, server.pem, server.crt 14 | # "Share" files: ca.crt (needed by the client), server.csr (needed by the CA) 15 | 16 | # Changes these CN's to match your hosts in your environment if needed. 17 | SERVER_CN=localhost 18 | 19 | # Step 1: Generate Certificate Authority + Trust Certificate (ca.crt) 20 | openssl genrsa -passout pass:1111 -des3 -out ca.key 4096 21 | openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj "/CN=${SERVER_CN}" 22 | 23 | # Step 2: Generate the Server Private Key (server.key) 24 | openssl genrsa -passout pass:1111 -des3 -out server.key 4096 25 | 26 | # Step 3: Get a certificate signing request from the CA (server.csr) 27 | openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/CN=${SERVER_CN}" 28 | 29 | # Step 4: Sign the certificate with the CA we created (it's called self signing) - server.crt 30 | openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt 31 | 32 | # Step 5: Convert the server certificate to .pem format (server.pem) - usable by gRPC 33 | openssl pkcs8 -topk8 -nocrypt -passin pass:1111 -in server.key -out server.pem -------------------------------------------------------------------------------- /ssl/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEnDCCAoQCAQEwDQYJKoZIhvcNAQEFBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0 3 | MB4XDTE4MDUxMDEwMjkzNVoXDTE5MDUxMDEwMjkzNVowFDESMBAGA1UEAwwJbG9j 4 | YWxob3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt+qNF2AraS3F 5 | JtVqlnsXsg9ze2dzJN/r+v1ls2c83E9P4h+T1VlHm9kxBQpVv7c3SKuZbzT12ANt 6 | d4JwZ+QCVrHFy1K7qvImGGlO2VyBFF2ocwOIJUGWWg/wIamGR0ojCV6su0KHi7e4 7 | kv4JzThfLdTUT0N1dBAoBTSCGW7ef/TNXZ7FneEwhVTNlHt2Cogf47FO7Dvgw6bU 8 | munKP/LErus77g7gYB4sBu0nBu+mdzlUYGNAt1h1pP4L8Nr681mY+ZWkwrbfrNI2 9 | CEtB8m9fkBboPkTOe8Hv9LViOmuXGQt1tml/sxNNiT8hdX+MtpMbiZt2TB9/U5S4 10 | EfZKVZYqiPTpx7YCzZ0A8hMnSEry7olYG++KYZw4DAutDDzwUJtQhknzY8oc9hFt 11 | ns0aM23M9tkURs6e5CcQl3prybevwIvRpNeSlO/QcHZHUVdBSvf9aSKSAVyUzQ68 12 | sL9CCojTLzAI3oNU9yFcQtYEdG+EJg+w39gvT07t+KMNhHIy5dX/iNx4+iZcjRnh 13 | gJcQRlKn631CFHJ8w0L0keexYC0zYskciN8C0wVIIsgn6H8qCM6WMgoTTCUwgVqG 14 | njgsFS0IoseDXNVhrqHlUMN1vUMSmWOZ+NOgTyG5XQS95He0je8LfZfXkk0WYwdo 15 | t1mX6Jnpx/JvNll+qwLgaTyNmjqF5LECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA 16 | WmdQvUcMCxVEzOpvw6x29bYXZgngSJQUz8gNQhjAdnWVCibJR8dRWXZ2GX9d1JaU 17 | QG04mBFhF6R4C3i2JkGj12Ivfl/REpJ4w+ZXkJxoN9nydy0zABvVUlKi081r2I1R 18 | F6db0uQ8OjAI+ErZqNIfH2Dd+z3FRqNbRqpRp5tUCcZVSucTYICWwJ4ui/e2ctpT 19 | 2clsSA20hdhall5oHUuAI8CQ4Kf2rPQ/uWNH5A4SOj+cGMD19UKAZDhGxZS6oC1r 20 | 3eUk451AWwfVEm2Hz8OtmdyRMq8J98vDQfSPnGGXyKAsLHxjlLwwBtNPEDFFCQFS 21 | kK2/n71UZBws7FOvwRh1zL16GGMFVNmI9AWBSmATZhp6phs2H6r1KbgG1Fk2NbCO 22 | BOUYAJ0w0LiXqVUDOkQrMeP6gBkOyameMtunRMyw9YWSre9+5/PGodhEwFcw8syA 23 | tEA/5e10CNmiKeL2m70TzcDszUaOjEWBIyOE09O52KVpTuUJ0sylF3YwM8DeFWvS 24 | 0EjQWipcnyEAHci3ZgROPl7REDxVQKOeAUSr7/BR7p1vvVEPNAQU9vIhkKls2hfK 25 | hvpQQC4IRNh2ohsbmGIg6O0lQBQcZWXUWTAObzuZt0NxiRP6HFIuyGSjEZ0diwAJ 26 | 9OEce8s7PsIEO8pUcXQvVvgWY1vQpPJXWdEYmpn01ac= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /ssl/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIEWTCCAkECAQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0B 3 | AQEFAAOCAg8AMIICCgKCAgEAt+qNF2AraS3FJtVqlnsXsg9ze2dzJN/r+v1ls2c8 4 | 3E9P4h+T1VlHm9kxBQpVv7c3SKuZbzT12ANtd4JwZ+QCVrHFy1K7qvImGGlO2VyB 5 | FF2ocwOIJUGWWg/wIamGR0ojCV6su0KHi7e4kv4JzThfLdTUT0N1dBAoBTSCGW7e 6 | f/TNXZ7FneEwhVTNlHt2Cogf47FO7Dvgw6bUmunKP/LErus77g7gYB4sBu0nBu+m 7 | dzlUYGNAt1h1pP4L8Nr681mY+ZWkwrbfrNI2CEtB8m9fkBboPkTOe8Hv9LViOmuX 8 | GQt1tml/sxNNiT8hdX+MtpMbiZt2TB9/U5S4EfZKVZYqiPTpx7YCzZ0A8hMnSEry 9 | 7olYG++KYZw4DAutDDzwUJtQhknzY8oc9hFtns0aM23M9tkURs6e5CcQl3prybev 10 | wIvRpNeSlO/QcHZHUVdBSvf9aSKSAVyUzQ68sL9CCojTLzAI3oNU9yFcQtYEdG+E 11 | Jg+w39gvT07t+KMNhHIy5dX/iNx4+iZcjRnhgJcQRlKn631CFHJ8w0L0keexYC0z 12 | YskciN8C0wVIIsgn6H8qCM6WMgoTTCUwgVqGnjgsFS0IoseDXNVhrqHlUMN1vUMS 13 | mWOZ+NOgTyG5XQS95He0je8LfZfXkk0WYwdot1mX6Jnpx/JvNll+qwLgaTyNmjqF 14 | 5LECAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4ICAQCrkNLo8nu+r+pHRhOxKAJ+l62C 15 | ECzGMRBt097Xx8MTRTuOSim/ej5QuX0m/Q5v6tcVGkXrmNavinKcSnldLJnKEhNP 16 | 6aZCNZVxYLiJ4gLwT1jZaBDuAU6VCD1bv9BbeSUZPTUSmhv8akPj+odOxBmmCXzN 17 | wfohb+hTCxFWi0NUyKaPx7bsapywY/pJmVvz5GGCi8SIKG2+YKJTQ/rSbI73Y5te 18 | LWozFuIdLJrAEkozoWNzzn3g+o0Oec7jAHri8LWlG6GuYdDZGNuQydEq/VeyhDrg 19 | yPQpzJOHJpO2dAzywRf8LpQZjY9mogaL7bdEdp6+DCMBGj5RPiu+KTmFNN5mC4IJ 20 | ARkbzbKJT3jxBEV7/K2zf6mgfGWOEPxnF/G50c8dYiE/VReAaV8s3HBjDaFVCHes 21 | 22whQbk8Gd1KRrXWB7UdnEIMyxdkqls65nw85pLYneRYL9HCNBRuCLBFjI8ziHuL 22 | 6MARKEB/H6vtUkrGL0uYzB+9MG0AJAoJsg0uIQ72nIukucGU13AiCvccW05OJ305 23 | zHzSJ62DcJXDZg8R/eAhCiPqS27fzQb7FckwCXBV22FCCyTy61FWq685DjJRO4s+ 24 | inxt6oKXbmkH83yaw21+4bbY1qs1kA5J4j+qTNdPXImvNkvSsPxVqUALz769R/Hj 25 | U7+xqnSdb6tw6fG4fQ== 26 | -----END CERTIFICATE REQUEST----- 27 | -------------------------------------------------------------------------------- /ssl/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,6F6D43938B8F261F 4 | 5 | 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 54 | -----END RSA PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /ssl/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | 5il/g6H1/1FKIZYCenDlRXl+/MJngy4= 52 | -----END PRIVATE KEY----- 53 | --------------------------------------------------------------------------------