├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── homo │ └── efficio │ └── springcamp2017 │ └── grpc │ └── hello │ ├── HelloGrpcClient.java │ ├── HelloGrpcClientRunner.java │ └── HelloGrpcClientStubFactory.java └── proto └── hello-springcamp.proto /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | build/ 4 | out/ 5 | 6 | .settings/ 7 | bin/ 8 | gen-src/ 9 | .classpath 10 | .project -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Camp 2017 gRPC 2 | 3 | Spring Camp 2017의 **gㅏ벼운 RPC, gRPC**의 예제 코드와 발표에 다 담지 못한 나머지 예제들 4 | 5 | ## gRPC 예제 코드 - 클라이언트 6 | 7 | ### 브랜치 8 | 9 | - master 브랜치: gRPC의 4가지 스트리밍 모두를 다루는 예제 포함 10 | - comic 브랜치: 발표에 사용된 코믹 버전 - Async Unary Call만 포함 11 | 12 | ### 환경 구성 13 | 14 | - JDK 7+ 15 | - .proto 파일 편집 플러그인(필수는 아님) 16 | - IntelliJ: Protobuf Support 17 | - Eclipse: https://marketplace.eclipse.org/category/free-tagging/grpc/title 18 | 19 | ### 코드 보기 20 | 21 | - 최초 커밋부터 순서대로 체크아웃하면서 보면 gRPC 프로그램 작성 순서와 함께 더 쉽게 이해할 수 있음 22 | 23 | ### 실행 24 | 25 | - HelloGrpcClientRunner.java 파일의 메인 메서드로 gRPC 서버 실행 26 | - master 브랜치에 있는 여러가지 스트리밍 방식은 주석을 통해 필요한 방식만 실행하며 테스트 가능 27 | 28 | ### 슬라이드 29 | 30 | - [Spring Camp 2017 - gㅏ벼운 RPC, gRPC (약 8M)](https://github.com/HomoEfficio/dev-tips/blob/master/SpringCamp2017%20-%20g%E3%85%8F%E1%84%87%E1%85%A7%E1%84%8B%E1%85%AE%E1%86%AB%20RPC%2C%20gRPC.pdf) 31 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'homo.efficio' 2 | version '1.0-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'com.google.protobuf' 6 | apply plugin: 'idea' 7 | 8 | sourceCompatibility = 1.8 9 | 10 | repositories { 11 | mavenCentral() 12 | } 13 | 14 | buildscript { 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6' 22 | } 23 | } 24 | 25 | protobuf { 26 | protoc { 27 | artifact = "com.google.protobuf:protoc:3.6.1" 28 | 29 | // Set the location of generated source code by protoc compiler 30 | generatedFilesBaseDir = "$projectDir/gen-src" 31 | } 32 | plugins { 33 | grpc { 34 | artifact = 'io.grpc:protoc-gen-grpc-java:1.16.1' 35 | } 36 | } 37 | generateProtoTasks { 38 | all()*.plugins { 39 | grpc {} 40 | } 41 | } 42 | } 43 | 44 | // Add source dir for generated source code of gRPC 45 | sourceSets { 46 | main { 47 | java { 48 | srcDirs 'gen-src/main/java' 49 | srcDirs 'gen-src/main/grpc' 50 | } 51 | } 52 | } 53 | 54 | dependencies { 55 | compile 'io.grpc:grpc-netty:1.16.1' 56 | compile 'io.grpc:grpc-protobuf:1.16.1' 57 | compile 'io.grpc:grpc-stub:1.16.1' 58 | 59 | testCompile group: 'junit', name: 'junit', version: '4.12' 60 | } 61 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HomoEfficio/springcamp2017-grpc-java-client/a0b9f3db61ff6e890e8d27568851b6fed3e2b600/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 12 18:34:35 KST 2017 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-4.10.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 = 'springcamp2017-grpc-java-client' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/homo/efficio/springcamp2017/grpc/hello/HelloGrpcClient.java: -------------------------------------------------------------------------------- 1 | package homo.efficio.springcamp2017.grpc.hello; 2 | 3 | import io.grpc.StatusRuntimeException; 4 | import io.grpc.stub.StreamObserver; 5 | 6 | import java.util.Iterator; 7 | import java.util.List; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | /** 12 | * @author homo.efficio@gmail.com 13 | * created on 2017-04-12 14 | */ 15 | public class HelloGrpcClient { 16 | 17 | private final Logger logger = Logger.getLogger(HelloGrpcClient.class.getName()); 18 | 19 | private final HelloSpringCampGrpc.HelloSpringCampBlockingStub blockingStub; 20 | private final HelloSpringCampGrpc.HelloSpringCampStub asyncStub; 21 | private final HelloSpringCampGrpc.HelloSpringCampFutureStub futureStub; 22 | 23 | public HelloGrpcClient(HelloSpringCampGrpc.HelloSpringCampBlockingStub blockingStub, 24 | HelloSpringCampGrpc.HelloSpringCampStub asyncStub, 25 | HelloSpringCampGrpc.HelloSpringCampFutureStub futureStub) { 26 | this.blockingStub = blockingStub; 27 | this.asyncStub = asyncStub; 28 | this.futureStub = futureStub; 29 | } 30 | 31 | public void sendBlockingUnaryMessage(String clientName) { 32 | 33 | // 클라이언트 비즈니스 로직 수행 결과인 clientName으로 request 생성 34 | HelloRequest request = HelloRequest.newBuilder().setClientName(clientName).build(); 35 | HelloResponse response; 36 | 37 | try { 38 | logger.info("Unary Hello 서비스 호출, 메시지 [" + clientName + "]"); 39 | response = blockingStub.unaryHello(request); 40 | } catch (StatusRuntimeException e) { 41 | logger.log(Level.SEVERE, "Unary Hello 서비스 호출 중 실패: " + e.getStatus()); 42 | return; 43 | } 44 | 45 | logger.info("Unary Hello 서비스 응답: " + response.getWelcomeMessage()); 46 | } 47 | 48 | public void sendAsyncUnaryMessage(String clientName) { 49 | 50 | // 클라이언트 비즈니스 로직 수행 결과인 clientName으로 request 생성 51 | HelloRequest request = HelloRequest.newBuilder().setClientName(clientName).build(); 52 | logger.info("Unary Hello 서비스 Async 호출, 메시지 [" + clientName + "]"); 53 | 54 | // 서버에 보낼 데이터를 담은 request와 55 | // 비동기 방식으로 서버에서 호출될 콜백 객체도 함께 파라미터로 전달 56 | asyncStub.unaryHello( 57 | request, 58 | // 서버에 보낼 콜백 객체 59 | new StreamObserver() { 60 | @Override 61 | public void onNext(HelloResponse response) { 62 | logger.info("Async Unary 서버로부터의 응답 " + response.getWelcomeMessage()); 63 | } 64 | 65 | @Override 66 | public void onError(Throwable t) { 67 | logger.log(Level.SEVERE, "Async Unary responseObserver.onError() 호출됨"); 68 | } 69 | 70 | @Override 71 | public void onCompleted() { 72 | logger.info("Async Unary 서버 응답 completed"); 73 | } 74 | } 75 | ); 76 | 77 | // 서버에서 응답이 올 때까지 기다리지 않고, 호출 결과에 상관없이 다른 작업 수행 가능 78 | logger.info("(Nonblocking이면서)Async이니까 원격 메서드 호출 직후 바로 로그가 찍힌다."); 79 | } 80 | 81 | public void sendBlockingServerStreamingMessage(String clientName) { 82 | 83 | // 클라이언트 비즈니스 로직 수행 결과인 clientName으로 request 생성 84 | HelloRequest request = HelloRequest.newBuilder().setClientName(clientName).build(); 85 | Iterator responseIterator; 86 | 87 | try { 88 | logger.info("Server Streaming Hello 서비스 Blocking 호출, 메시지 [" + clientName + "]"); 89 | responseIterator = blockingStub.serverStreamingHello(request); 90 | } catch (StatusRuntimeException e) { 91 | logger.log(Level.SEVERE, "Server Streaming Hello 서비스 Blocking 호출 중 실패: " + e.getStatus()); 92 | return; 93 | } 94 | 95 | responseIterator.forEachRemaining( 96 | (response) -> logger.info("Server Streaming Hello 서비스 응답: " + response.getWelcomeMessage()) 97 | ); 98 | } 99 | 100 | public void sendAsyncServerStreamingMessage(String clientName) { 101 | 102 | // 클라이언트 비즈니스 로직 수행 결과인 clientName으로 request 생성 103 | HelloRequest request = HelloRequest.newBuilder().setClientName(clientName).build(); 104 | Iterator responseIterator; 105 | 106 | logger.info("Server Streaming Hello 서비스 Async 호출, 메시지 [" + clientName + "]"); 107 | asyncStub.serverStreamingHello( 108 | request, 109 | new StreamObserver() { 110 | @Override 111 | public void onNext(HelloResponse response) { 112 | logger.info("Async Server Streaming 서버로부터의 응답 " + response.getWelcomeMessage()); 113 | } 114 | 115 | @Override 116 | public void onError(Throwable t) { 117 | logger.log(Level.SEVERE, "Async Server Streaming responseObserver.onError() 호출됨"); 118 | } 119 | 120 | @Override 121 | public void onCompleted() { 122 | logger.info("Async Server Streaming 서버 응답 completed"); 123 | } 124 | } 125 | ); 126 | 127 | // 서버에서 응답이 올 때까지 기다리지 않고, 호출 결과에 상관없이 다른 작업 수행 가능 128 | logger.info("(Nonblocking이면서)Async이니까 원격 메서드 호출 직후 바로 로그가 찍힌다."); 129 | } 130 | 131 | // Client Streaming은 AsyncStub에서만 가능 132 | public void sendAsyncClientStreamingMessage(List messages) { 133 | 134 | // 서버에 보낼 콜백 객체 135 | StreamObserver responseObserver = new StreamObserver() { 136 | @Override 137 | public void onNext(HelloResponse value) { 138 | logger.info("Async Client Streaming 서버로부터의 응답\n" + value.getWelcomeMessage()); 139 | } 140 | 141 | @Override 142 | public void onError(Throwable t) { 143 | logger.log(Level.SEVERE, "Async Clent Streaming responseObserver.onError() 호출됨"); 144 | } 145 | 146 | @Override 147 | public void onCompleted() { 148 | logger.info("Async Client Streaming 서버 응답 completed"); 149 | } 150 | }; 151 | 152 | StreamObserver requestObserver = asyncStub.clientStreamingHello(responseObserver); 153 | try { 154 | for (String msg: messages) { 155 | requestObserver.onNext(HelloRequest.newBuilder().setClientName(msg).build()); 156 | } 157 | } catch (Exception e) { 158 | requestObserver.onError(e); 159 | throw e; 160 | } 161 | 162 | // 서버에서 응답이 올 때까지 기다리지 않고, 호출 결과에 상관없이 다른 작업 수행 가능 163 | logger.info("(Nonblocking이면서)Async이니까 원격 메서드 호출 직후 바로 로그가 찍힌다."); 164 | 165 | requestObserver.onCompleted(); 166 | } 167 | 168 | // Bidirectional Streaming은 AsyncStub에서만 가능 169 | public void sendBidirectionalStreamingMessage(List messages) { 170 | 171 | // 서버에 보낼 콜백 객체 172 | StreamObserver responseObserver = new StreamObserver() { 173 | @Override 174 | public void onNext(HelloResponse response) { 175 | logger.info("Bidirectional Streaming 서버로부터의 응답\n" + response.getWelcomeMessage()); 176 | } 177 | 178 | @Override 179 | public void onError(Throwable t) { 180 | logger.log(Level.SEVERE, "Bidirectional Streaming responseObserver.onError() 호출됨"); 181 | } 182 | 183 | @Override 184 | public void onCompleted() { 185 | logger.info("Bidirectional Streaming 서버 응답 completed"); 186 | } 187 | }; 188 | 189 | StreamObserver requestObserver = asyncStub.biStreamingHello(responseObserver); 190 | try { 191 | for (String msg: messages) { 192 | requestObserver.onNext(HelloRequest.newBuilder().setClientName(msg).build()); 193 | } 194 | } catch (Exception e) { 195 | requestObserver.onError(e); 196 | throw e; 197 | } 198 | 199 | // 서버에서 응답이 올 때까지 기다리지 않고, 호출 결과에 상관없이 다른 작업 수행 가능 200 | logger.info("(Nonblocking이면서)Async이니까 원격 메서드 호출 직후 바로 로그가 찍힌다."); 201 | 202 | requestObserver.onCompleted(); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/main/java/homo/efficio/springcamp2017/grpc/hello/HelloGrpcClientRunner.java: -------------------------------------------------------------------------------- 1 | package homo.efficio.springcamp2017.grpc.hello; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * @author homo.efficio@gmail.com 7 | * created on 2017-04-12 8 | */ 9 | public class HelloGrpcClientRunner { 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | 13 | String host = "localhost"; 14 | int port = 54321; 15 | 16 | HelloGrpcClientStubFactory clientStubFactory = 17 | new HelloGrpcClientStubFactory(host, port); 18 | 19 | HelloGrpcClient grpcClient = 20 | new HelloGrpcClient(clientStubFactory.getBlockingStub(), 21 | clientStubFactory.getAsyncStub(), 22 | clientStubFactory.getFutureStub()); 23 | 24 | // Blocking Unary 25 | // grpcClient.sendBlockingUnaryMessage("Blocking Unary, gㅏ벼운 RPC, gPRC"); 26 | // clientStubFactory.shutdownChannel(); 27 | 28 | // Async Unary 29 | // grpcClient.sendAsyncUnaryMessage("Async Unary, gㅏ벼운 RPC, gRPC"); 30 | // Thread.sleep(3000); 31 | // clientStubFactory.shutdownChannel(); 32 | 33 | // Blocking Server Streaming 34 | // grpcClient.sendBlockingServerStreamingMessage("Blocking Server Streaming, gㅏ벼운 RPC, gPRC"); 35 | // clientStubFactory.shutdownChannel(); 36 | 37 | // Async Server Streaming 38 | // grpcClient.sendAsyncServerStreamingMessage("Async Server Streaming, gㅏ벼운 RPC, gRPC"); 39 | // Thread.sleep(3000); 40 | // clientStubFactory.shutdownChannel(); 41 | 42 | // Async Client Streaming 43 | // grpcClient.sendAsyncClientStreamingMessage(Arrays.asList("Async Client Streaming,", "gㅏ벼운 RPC,", "gRPC")); 44 | // Thread.sleep(3000); 45 | // clientStubFactory.shutdownChannel(); 46 | // 아래와 같이 1초만 대기해서 서버 응답 전에 channel을 닫으면 47 | // 클라이언트는 응답을 못 받으며, 서버 쪽에서도 에러가 발생하지 않는다. 48 | // Thread.sleep(1000); 49 | // clientStubFactory.shutdownChannel(); 50 | 51 | // Bidirectional Client Streaming 52 | grpcClient.sendBidirectionalStreamingMessage(Arrays.asList("Async Bidirectional Streaming,", "gㅏ벼운 RPC,", "gRPC")); 53 | Thread.sleep(3000); 54 | clientStubFactory.shutdownChannel(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/homo/efficio/springcamp2017/grpc/hello/HelloGrpcClientStubFactory.java: -------------------------------------------------------------------------------- 1 | package homo.efficio.springcamp2017.grpc.hello; 2 | 3 | import io.grpc.ManagedChannel; 4 | import io.grpc.ManagedChannelBuilder; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.logging.Logger; 8 | 9 | /** 10 | * @author homo.efficio@gmail.com 11 | * created on 2017-04-12 12 | */ 13 | public class HelloGrpcClientStubFactory { 14 | 15 | private final Logger logger = Logger.getLogger(HelloGrpcClientStubFactory.class.getName()); 16 | 17 | private final ManagedChannel channel; 18 | private final HelloSpringCampGrpc.HelloSpringCampBlockingStub blockingStub; 19 | private final HelloSpringCampGrpc.HelloSpringCampStub asyncStub; 20 | private final HelloSpringCampGrpc.HelloSpringCampFutureStub futureStub; 21 | 22 | public HelloGrpcClientStubFactory(String host, int port) { 23 | this.channel = ManagedChannelBuilder.forAddress(host, port) 24 | .usePlaintext(true) 25 | .build(); 26 | this.blockingStub = HelloSpringCampGrpc.newBlockingStub(channel); 27 | this.asyncStub = HelloSpringCampGrpc.newStub(channel); 28 | this.futureStub = HelloSpringCampGrpc.newFutureStub(channel); 29 | } 30 | 31 | public void shutdownChannel() throws InterruptedException { 32 | logger.info("gRPC Channel shutdown..."); 33 | this.channel.shutdown().awaitTermination(2, TimeUnit.SECONDS); 34 | } 35 | 36 | public HelloSpringCampGrpc.HelloSpringCampBlockingStub getBlockingStub() { 37 | return blockingStub; 38 | } 39 | 40 | public HelloSpringCampGrpc.HelloSpringCampStub getAsyncStub() { 41 | return asyncStub; 42 | } 43 | 44 | public HelloSpringCampGrpc.HelloSpringCampFutureStub getFutureStub() { 45 | return futureStub; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/proto/hello-springcamp.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_outer_classname = "HelloGrpcProto"; 5 | 6 | package homo.efficio.springcamp2017.grpc.hello; 7 | 8 | message HelloRequest { 9 | string clientName = 1; 10 | } 11 | 12 | message HelloResponse { 13 | string welcomeMessage = 1; 14 | } 15 | 16 | service HelloSpringCamp { 17 | 18 | rpc unaryHello(HelloRequest) returns (HelloResponse) {} 19 | 20 | rpc serverStreamingHello(HelloRequest) returns (stream HelloResponse) {} 21 | 22 | rpc clientStreamingHello(stream HelloRequest) returns (HelloResponse) {} 23 | 24 | rpc biStreamingHello(stream HelloRequest) returns (stream HelloResponse) {} 25 | } 26 | --------------------------------------------------------------------------------