add grpc server kotlin tests
Signed-off-by: Thomas McKernan <tmeaglei@gmail.com>
This commit is contained in:
committed by
Dave Syer
parent
a59a0961cc
commit
943c72abf5
71
samples/grpc-server-kotlin/HELP-gradle.md
Normal file
71
samples/grpc-server-kotlin/HELP-gradle.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Getting Started
|
||||
|
||||
### Reference Documentation
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
* [Official Gradle documentation](https://docs.gradle.org)
|
||||
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.5/gradle-plugin)
|
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/3.4.5/gradle-plugin/packaging-oci-image.html)
|
||||
* [GraalVM Native Image Support](https://docs.spring.io/spring-boot/3.4.5/reference/packaging/native-image/introducing-graalvm-native-images.html)
|
||||
* [Spring gRPC [Experimental]](https://docs.spring.io/spring-grpc/reference/index.html)
|
||||
|
||||
### Additional Links
|
||||
These additional references should also help you:
|
||||
|
||||
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle)
|
||||
* [Configure AOT settings in Build Plugin](https://docs.spring.io/spring-boot/3.4.4/how-to/aot.html)
|
||||
* [Various sample apps using Spring gRPC](https://github.com/spring-projects/spring-grpc/tree/main/samples)
|
||||
|
||||
## GraalVM Native Support
|
||||
|
||||
This project has been configured to let you generate either a lightweight container or a native executable.
|
||||
It is also possible to run your tests in a native image.
|
||||
|
||||
### Lightweight Container with Cloud Native Buildpacks
|
||||
If you're already familiar with Spring Boot container images support, this is the easiest way to get started.
|
||||
Docker should be installed and configured on your machine prior to creating the image.
|
||||
|
||||
To create the image, run the following goal:
|
||||
|
||||
```
|
||||
$ ./gradlew bootBuildImage
|
||||
```
|
||||
|
||||
Then, you can run the app like any other container:
|
||||
|
||||
```
|
||||
$ docker run --rm grpc-server:0.5.0-SNAPSHOT
|
||||
```
|
||||
|
||||
### Executable with Native Build Tools
|
||||
Use this option if you want to explore more options such as running your tests in a native image.
|
||||
The GraalVM `native-image` compiler should be installed and configured on your machine.
|
||||
|
||||
NOTE: GraalVM 22.3+ is required.
|
||||
|
||||
To create the executable, run the following goal:
|
||||
|
||||
```
|
||||
$ ./gradlew nativeCompile
|
||||
```
|
||||
|
||||
Then, you can run the app as follows:
|
||||
```
|
||||
$ build/native/nativeCompile/grpc-server
|
||||
```
|
||||
|
||||
You can also run your existing tests suite in a native image.
|
||||
This is an efficient way to validate the compatibility of your application.
|
||||
|
||||
To run your existing tests in a native image, run the following goal:
|
||||
|
||||
```
|
||||
$ ./gradlew nativeTest
|
||||
```
|
||||
|
||||
### Gradle Toolchain support
|
||||
|
||||
There are some limitations regarding Native Build Tools and Gradle toolchains.
|
||||
Native Build Tools disable toolchain support by default.
|
||||
Effectively, native image compilation is done with the JDK used to execute Gradle.
|
||||
You can read more about [toolchain support in the Native Build Tools here](https://graalvm.github.io/native-build-tools/latest/gradle-plugin.html#configuration-toolchains).
|
||||
71
samples/grpc-server-kotlin/HELP-maven.md
Normal file
71
samples/grpc-server-kotlin/HELP-maven.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Getting Started
|
||||
|
||||
### Reference Documentation
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
|
||||
* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/3.4.5/maven-plugin)
|
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/3.4.5/maven-plugin/build-image.html)
|
||||
* [GraalVM Native Image Support](https://docs.spring.io/spring-boot/3.4.5/reference/packaging/native-image/introducing-graalvm-native-images.html)
|
||||
* [Spring gRPC [Experimental]](https://docs.spring.io/spring-grpc/reference/index.html)
|
||||
|
||||
### Additional Links
|
||||
These additional references should also help you:
|
||||
|
||||
* [Configure AOT settings in Build Plugin](https://docs.spring.io/spring-boot/3.4.4/how-to/aot.html)
|
||||
* [Various sample apps using Spring gRPC](https://github.com/spring-projects/spring-grpc/tree/main/samples)
|
||||
|
||||
## GraalVM Native Support
|
||||
|
||||
This project has been configured to let you generate either a lightweight container or a native executable.
|
||||
It is also possible to run your tests in a native image.
|
||||
|
||||
### Lightweight Container with Cloud Native Buildpacks
|
||||
If you're already familiar with Spring Boot container images support, this is the easiest way to get started.
|
||||
Docker should be installed and configured on your machine prior to creating the image.
|
||||
|
||||
To create the image, run the following goal:
|
||||
|
||||
```
|
||||
$ ./mvnw spring-boot:build-image -Pnative
|
||||
```
|
||||
|
||||
Then, you can run the app like any other container:
|
||||
|
||||
```
|
||||
$ docker run --rm grpc-server-sample:0.5.0-SNAPSHOT
|
||||
```
|
||||
|
||||
### Executable with Native Build Tools
|
||||
Use this option if you want to explore more options such as running your tests in a native image.
|
||||
The GraalVM `native-image` compiler should be installed and configured on your machine.
|
||||
|
||||
NOTE: GraalVM 22.3+ is required.
|
||||
|
||||
To create the executable, run the following goal:
|
||||
|
||||
```
|
||||
$ ./mvnw native:compile -Pnative
|
||||
```
|
||||
|
||||
Then, you can run the app as follows:
|
||||
```
|
||||
$ target/grpc-server-sample
|
||||
```
|
||||
|
||||
You can also run your existing tests suite in a native image.
|
||||
This is an efficient way to validate the compatibility of your application.
|
||||
|
||||
To run your existing tests in a native image, run the following goal:
|
||||
|
||||
```
|
||||
$ ./mvnw test -PnativeTest
|
||||
```
|
||||
|
||||
|
||||
### Maven Parent overrides
|
||||
|
||||
Due to Maven's design, elements are inherited from the parent POM to the project POM.
|
||||
While most of the inheritance is fine, it also inherits unwanted elements like `<license>` and `<developers>` from the parent.
|
||||
To prevent this, the project POM contains empty overrides for these elements.
|
||||
If you manually switch to a different parent and actually want the inheritance, you need to remove those overrides.
|
||||
40
samples/grpc-server-kotlin/README.md
Normal file
40
samples/grpc-server-kotlin/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Spring Boot gRPC Sample
|
||||
|
||||
This project is a copy one of the samples from the [gRPC Spring Boot Starter](https://github.com/yidongnan/grpc-spring-boot-starter/blob/master/examples/local-grpc-server/build.gradle). Build and run any way you like to run Spring Boot. E.g:
|
||||
|
||||
```
|
||||
$ ./mvnw spring-boot:run
|
||||
...
|
||||
. ____ _ __ _ _
|
||||
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
|
||||
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
|
||||
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
|
||||
' |____| .__|_| |_|_| |_\__, | / / / /
|
||||
=========|_|==============|___/=/_/_/_/
|
||||
:: Spring Boot :: (v3.4.5)
|
||||
2022-12-08T05:32:24.934-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 17.0.5 with PID 551632 (/home/dsyer/dev/scratch/demo/target/classes started by dsyer in /home/dsyer/dev/scratch/demo)
|
||||
2022-12-08T05:32:24.938-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
|
||||
2022-12-08T05:32:25.377-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcHealthServiceAutoConfiguration
|
||||
2022-12-08T05:32:25.416-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration
|
||||
2022-12-08T05:32:25.425-08:00 WARN 551632 --- [ main] ocalVariableTableParameterNameDiscoverer : Using deprecated '-debug' fallback for parameter name resolution. Compile the affected code with '-parameters' instead or avoid its introspection: net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration
|
||||
2022-12-08T05:32:25.427-08:00 INFO 551632 --- [ main] g.s.a.GrpcServerFactoryAutoConfiguration : Detected grpc-netty: Creating NettyGrpcServerFactory
|
||||
2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: Simple, bean: grpcServerService, class: com.example.demo.GrpcServerService
|
||||
2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.health.v1.Health, bean: grpcHealthService, class: io.grpc.protobuf.services.HealthServiceImpl
|
||||
2022-12-08T05:32:25.712-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.AbstractGrpcServerFactory : Registered gRPC service: grpc.reflection.v1alpha.ServerReflection, bean: protoReflectionService, class: io.grpc.protobuf.services.ProtoReflectionService
|
||||
2022-12-08T05:32:25.820-08:00 INFO 551632 --- [ main] n.d.b.g.s.s.GrpcServerLifecycle : gRPC Server started, listening on address: *, port: 9090
|
||||
2022-12-08T05:32:25.831-08:00 INFO 551632 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 1.264 seconds (process running for 1.623)
|
||||
```
|
||||
|
||||
The server starts by default on port 9090. Test with [gRPCurl](https://github.com/fullstorydev/grpcurl):
|
||||
|
||||
```
|
||||
$ grpcurl -d '{"name":"Hi"}' -plaintext localhost:9090 Simple.SayHello
|
||||
{
|
||||
"message": "Hello ==\u003e Hi"
|
||||
}
|
||||
```
|
||||
|
||||
## Native Image
|
||||
[Native Image with Gradle](./HELP-gradle.md)
|
||||
|
||||
[Native Image with Maven](./HELP-maven.md)
|
||||
83
samples/grpc-server-kotlin/build.gradle
Normal file
83
samples/grpc-server-kotlin/build.gradle
Normal file
@@ -0,0 +1,83 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.4.5'
|
||||
id 'io.spring.dependency-management' version '1.1.6'
|
||||
// id 'org.graalvm.buildtools.native' version '0.10.3'
|
||||
id 'com.google.protobuf' version '0.9.4'
|
||||
id 'org.jetbrains.kotlin.jvm' version '2.1.20'
|
||||
id 'org.jetbrains.kotlin.plugin.spring' version '2.1.20'
|
||||
}
|
||||
|
||||
group = 'com.example'
|
||||
version = '0.9.0-SNAPSHOT'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
maven { url 'https://repo.spring.io/snapshot' }
|
||||
}
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom 'org.springframework.grpc:spring-grpc-dependencies:0.9.0-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
def kotlinStubVersion = "1.4.3"
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'org.springframework.grpc:spring-grpc-spring-boot-starter'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
implementation 'io.grpc:grpc-services'
|
||||
implementation "io.grpc:grpc-kotlin-stub:${kotlinStubVersion}"
|
||||
implementation 'com.google.protobuf:protobuf-kotlin'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testImplementation 'org.springframework.grpc:spring-grpc-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
testRuntimeOnly "io.netty:netty-transport-native-epoll::linux-x86_64"
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging.showStandardStreams = true
|
||||
outputs.upToDateWhen { false }
|
||||
}
|
||||
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:${dependencyManagement.importedProperties['protobuf-java.version']}"
|
||||
}
|
||||
plugins {
|
||||
grpc {
|
||||
artifact = "io.grpc:protoc-gen-grpc-java:${dependencyManagement.importedProperties['grpc.version']}"
|
||||
}
|
||||
grpckt {
|
||||
artifact = "io.grpc:protoc-gen-grpc-kotlin:${kotlinStubVersion}:jdk8@jar"
|
||||
}
|
||||
}
|
||||
generateProtoTasks {
|
||||
all()*.plugins {
|
||||
grpc {
|
||||
option 'jakarta_omit'
|
||||
option '@generated=omit'
|
||||
}
|
||||
grpckt {
|
||||
outputSubDir = "kotlin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
264
samples/grpc-server-kotlin/pom.xml
Normal file
264
samples/grpc-server-kotlin/pom.xml
Normal file
@@ -0,0 +1,264 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.4.5</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<groupId>org.springframework.grpc</groupId>
|
||||
<artifactId>grpc-server-kotlin-sample</artifactId>
|
||||
<version>0.9.0-SNAPSHOT</version>
|
||||
<name>Spring gRPC Server Sample</name>
|
||||
<description>Demo project for Spring gRPC</description>
|
||||
<url/>
|
||||
<licenses>
|
||||
<license/>
|
||||
</licenses>
|
||||
<developers>
|
||||
<developer/>
|
||||
</developers>
|
||||
<scm>
|
||||
<connection/>
|
||||
<developerConnection/>
|
||||
<tag/>
|
||||
<url/>
|
||||
</scm>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-javaformat-maven-plugin.version>0.0.43</spring-javaformat-maven-plugin.version>
|
||||
<protobuf-java.version>4.30.2</protobuf-java.version>
|
||||
<grpc.version>1.72.0</grpc.version>
|
||||
<grpc.kotlin.version>1.4.3</grpc.kotlin.version>
|
||||
<kotlin.version>1.9.22</kotlin.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.grpc</groupId>
|
||||
<artifactId>spring-grpc-dependencies</artifactId>
|
||||
<version>0.9.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.grpc</groupId>
|
||||
<artifactId>spring-grpc-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-coroutines-core</artifactId>
|
||||
<version>1.10.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-kotlin-stub</artifactId>
|
||||
<version>${grpc.kotlin.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.protobuf</groupId>
|
||||
<artifactId>protobuf-kotlin</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>grpc-services</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- for testing unix domain sockets -->
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-transport-native-epoll</artifactId>
|
||||
<classifier>linux-x86_64</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.grpc</groupId>
|
||||
<artifactId>spring-grpc-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>24.0.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||
<extensions>
|
||||
<extension>
|
||||
<groupId>kr.motd.maven</groupId>
|
||||
<artifactId>os-maven-plugin</artifactId>
|
||||
<version>1.7.1</version>
|
||||
</extension>
|
||||
</extensions>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>${kotlin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.graalvm.buildtools</groupId>
|
||||
<artifactId>native-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<buildArgs>
|
||||
<buildArg>--verbose</buildArg>
|
||||
</buildArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.spring.javaformat</groupId>
|
||||
<artifactId>spring-javaformat-maven-plugin</artifactId>
|
||||
<version>${spring-javaformat-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<?m2e ignore?>
|
||||
<phase>validate</phase>
|
||||
<inherited>true</inherited>
|
||||
<goals>
|
||||
<goal>validate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.xolstice.maven.plugins</groupId>
|
||||
<artifactId>protobuf-maven-plugin</artifactId>
|
||||
<version>0.6.1</version>
|
||||
<configuration>
|
||||
<protocArtifact>
|
||||
com.google.protobuf:protoc:${protobuf-java.version}:exe:${os.detected.classifier}
|
||||
</protocArtifact>
|
||||
<pluginId>grpc-java</pluginId>
|
||||
<pluginArtifact>
|
||||
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
|
||||
</pluginArtifact>
|
||||
<protocPlugins>
|
||||
<protocPlugin>
|
||||
<id>grpc-kotlin</id>
|
||||
<groupId>io.grpc</groupId>
|
||||
<artifactId>protoc-gen-grpc-kotlin</artifactId>
|
||||
<version>${grpc.kotlin.version}</version>
|
||||
<classifier>jdk8</classifier>
|
||||
<mainClass>org.springframework.grpc.sample.GrpcServerApplicationKt</mainClass>
|
||||
</protocPlugin>
|
||||
</protocPlugins>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<configuration>
|
||||
<pluginParameter>
|
||||
jakarta_omit,@generated=omit
|
||||
</pluginParameter>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>grpc-kotlin</id>
|
||||
<goals>
|
||||
<goal>compile-custom</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<pluginId>grpc-kotlin</pluginId>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import io.grpc.Status
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.runApplication
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.grpc.server.exception.GrpcExceptionHandler
|
||||
|
||||
@SpringBootApplication
|
||||
open class GrpcServerApplication {
|
||||
|
||||
@Bean
|
||||
open fun globalInterceptor(): GrpcExceptionHandler = GrpcExceptionHandler { exception ->
|
||||
when (exception) {
|
||||
is IllegalArgumentException -> Status.INVALID_ARGUMENT.withDescription(exception.message).asException();
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
runApplication<GrpcServerApplication>(*args)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import org.springframework.grpc.sample.proto.HelloReply
|
||||
import org.springframework.grpc.sample.proto.HelloRequest
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpcKt
|
||||
import org.springframework.stereotype.Service
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
||||
@Service
|
||||
class GrpcServerService : SimpleGrpcKt.SimpleCoroutineImplBase() {
|
||||
|
||||
override suspend fun sayHello(request: HelloRequest): HelloReply {
|
||||
|
||||
if (request.name.startsWith("error")) {
|
||||
throw IllegalArgumentException("Bad name: ${request.name}")
|
||||
}
|
||||
|
||||
if (request.name.startsWith("internal")) {
|
||||
throw RuntimeException()
|
||||
}
|
||||
|
||||
return HelloReply.newBuilder()
|
||||
.setMessage("Hello ==> ${request.name}")
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun streamHello(request: HelloRequest): Flow<HelloReply> {
|
||||
return (1..10)
|
||||
.map { HelloReply.newBuilder().setMessage("Hello ($it) ==> ${request.name}").build() }
|
||||
.asFlow()
|
||||
}
|
||||
}
|
||||
23
samples/grpc-server-kotlin/src/main/proto/hello.proto
Normal file
23
samples/grpc-server-kotlin/src/main/proto/hello.proto
Normal file
@@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_multiple_files = true;
|
||||
option java_package = "org.springframework.grpc.sample.proto";
|
||||
option java_outer_classname = "HelloWorldProto";
|
||||
|
||||
// The greeting service definition.
|
||||
service Simple {
|
||||
// Sends a greeting
|
||||
rpc SayHello (HelloRequest) returns (HelloReply) {
|
||||
}
|
||||
rpc StreamHello(HelloRequest) returns (stream HelloReply) {}
|
||||
}
|
||||
|
||||
// The request message containing the user's name.
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
// The response message containing the greetings
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
spring.application.name=grpc-server-kotlin
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import io.grpc.stub.AbstractStub
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.grpc.client.FutureStubFactory
|
||||
import org.springframework.grpc.client.ImportGrpcClients
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleBlockingStub
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleFutureStub
|
||||
import org.springframework.grpc.test.AutoConfigureInProcessTransport
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class NoAutowiredClients {
|
||||
|
||||
@Autowired
|
||||
private lateinit var context: ApplicationContext
|
||||
|
||||
@Test
|
||||
fun noStubIsCreated() {
|
||||
assertThat(context.containsBeanDefinition("simpleBlockingStub")).isFalse()
|
||||
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
|
||||
assertThat(context.containsBeanDefinition("simpleFutureStub")).isFalse()
|
||||
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
|
||||
.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@SpringBootTest(properties = ["spring.grpc.client.default-channel.address=0.0.0.0:9090"])
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class DefaultAutowiredClients {
|
||||
@Autowired
|
||||
private lateinit var context: ApplicationContext
|
||||
|
||||
@Test
|
||||
fun onlyDefaultStubIsCreated() {
|
||||
Assertions.assertThat(context.containsBeanDefinition("simpleBlockingStub")).isTrue()
|
||||
assertThat(
|
||||
context.getBean(
|
||||
SimpleBlockingStub::class.java
|
||||
)
|
||||
).isNotNull()
|
||||
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
|
||||
assertThat(context.containsBeanDefinition("simpleFutureStub")).isFalse()
|
||||
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
|
||||
.hasSize(1)
|
||||
}
|
||||
}
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.client.default-channel.address=0.0.0.0:9090"]
|
||||
)
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class SpecificAutowiredClients {
|
||||
@Autowired
|
||||
private lateinit var context: ApplicationContext
|
||||
|
||||
@Test
|
||||
fun stubOfCorrectTypeIsCreated() {
|
||||
assertThat(context.containsBeanDefinition("simpleFutureStub")).isTrue()
|
||||
assertThat(
|
||||
context.getBean(
|
||||
SimpleFutureStub::class.java
|
||||
)
|
||||
).isNotNull()
|
||||
assertThat(context.containsBeanDefinition("simpleStub")).isFalse()
|
||||
assertThat(context.containsBeanDefinition("simpleBlockingStub")).isFalse()
|
||||
assertThat(context.getBeanNamesForType(AbstractStub::class.java))
|
||||
.hasSize(1)
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
@ImportGrpcClients(basePackageClasses = [SimpleGrpc::class], factory = FutureStubFactory::class)
|
||||
internal open class TestConfig
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import org.apache.commons.logging.Log
|
||||
import org.apache.commons.logging.LogFactory
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod
|
||||
import org.springframework.grpc.sample.proto.HelloRequest
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleBlockingStub
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
|
||||
@SpringBootTest(
|
||||
properties = [
|
||||
"spring.grpc.server.port=0",
|
||||
"spring.grpc.client.default-channel.address=0.0.0.0:\${local.grpc.port}"
|
||||
],
|
||||
useMainMethod = UseMainMethod.ALWAYS
|
||||
)
|
||||
@DirtiesContext
|
||||
class GrpcServerApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private lateinit var stub: SimpleBlockingStub
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serverResponds() {
|
||||
log.info("Testing")
|
||||
val response = stub.sayHello(
|
||||
HelloRequest.newBuilder()
|
||||
.setName("Alien")
|
||||
.build()
|
||||
)
|
||||
Assertions.assertEquals("Hello ==> Alien", response.getMessage())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val log: Log = LogFactory.getLog(GrpcServerApplicationTests::class.java)
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
SpringApplicationBuilder(GrpcServerApplication::class.java).run()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.grpc.health.v1.HealthCheckRequest
|
||||
import io.grpc.health.v1.HealthCheckResponse.ServingStatus
|
||||
import io.grpc.health.v1.HealthGrpc
|
||||
import io.grpc.health.v1.HealthGrpc.HealthBlockingStub
|
||||
import io.grpc.protobuf.services.HealthStatusManager
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.assertj.core.api.ThrowableAssert
|
||||
import org.awaitility.Awaitility
|
||||
import org.awaitility.core.ThrowingRunnable
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator
|
||||
import org.springframework.boot.actuate.health.Health
|
||||
import org.springframework.boot.actuate.health.HealthIndicator
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.grpc.client.GrpcChannelFactory
|
||||
import org.springframework.grpc.sample.proto.HelloRequest
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc.SimpleBlockingStub
|
||||
import org.springframework.grpc.test.AutoConfigureInProcessTransport
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import java.time.Duration
|
||||
|
||||
/**
|
||||
* Integration tests for gRPC server health feature.
|
||||
*/
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.health-test.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.health-test.health.enabled=true", "spring.grpc.client.channels.health-test.health.service-name=my-service"]
|
||||
)
|
||||
@DirtiesContext
|
||||
internal class WithClientHealthEnabled {
|
||||
@Test
|
||||
fun loadBalancerRespectsServerHealth(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@Autowired healthStatusManager: HealthStatusManager
|
||||
) {
|
||||
val channel = channels.createChannel("health-test")
|
||||
val client = SimpleGrpc.newBlockingStub(channel)
|
||||
|
||||
// put the service up (SERVING) and give load balancer time to update
|
||||
updateHealthStatusAndWait("my-service", ServingStatus.SERVING, healthStatusManager)
|
||||
|
||||
// initially the status should be SERVING
|
||||
assertThatResponseIsServedToChannel(client)
|
||||
|
||||
// put the service down (NOT_SERVING) and give load balancer time to update
|
||||
updateHealthStatusAndWait("my-service", ServingStatus.NOT_SERVING, healthStatusManager)
|
||||
|
||||
// now the request should fail
|
||||
assertThatResponseIsNotServedToChannel(client)
|
||||
|
||||
// put the service up (SERVING) and give load balancer time to update
|
||||
updateHealthStatusAndWait("my-service", ServingStatus.SERVING, healthStatusManager)
|
||||
|
||||
// now the request should pass
|
||||
assertThatResponseIsServedToChannel(client)
|
||||
}
|
||||
|
||||
private fun updateHealthStatusAndWait(
|
||||
serviceName: String?, healthStatus: ServingStatus,
|
||||
healthStatusManager: HealthStatusManager
|
||||
) {
|
||||
healthStatusManager.setStatus(serviceName, healthStatus)
|
||||
try {
|
||||
Thread.sleep(2000L)
|
||||
} catch (e: InterruptedException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertThatResponseIsServedToChannel(client: SimpleBlockingStub) {
|
||||
val response = client.sayHello(HelloRequest.newBuilder().setName("Alien").build())
|
||||
Assertions.assertThat(response.getMessage()).isEqualTo("Hello ==> Alien")
|
||||
}
|
||||
|
||||
private fun assertThatResponseIsNotServedToChannel(client: SimpleBlockingStub) {
|
||||
Assertions.assertThatExceptionOfType<StatusRuntimeException?>(StatusRuntimeException::class.java)
|
||||
.isThrownBy(ThrowableAssert.ThrowingCallable {
|
||||
client.sayHello(
|
||||
HelloRequest.newBuilder().setName("Alien").build()
|
||||
)
|
||||
})
|
||||
.withMessageContaining("UNAVAILABLE: Health-check service responded NOT_SERVING for 'my-service'")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.server.health.actuator.health-indicator-paths=custom", "spring.grpc.server.health.actuator.update-initial-delay=3s", "spring.grpc.server.health.actuator.update-rate=3s", "management.health.defaults.enabled=true"]
|
||||
)
|
||||
@AutoConfigureInProcessTransport
|
||||
@DirtiesContext
|
||||
internal class WithActuatorHealthAdapter {
|
||||
@Test
|
||||
fun healthIndicatorsAdaptedToGrpcHealthStatus(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@Autowired customHealthIndicator: CustomHealthIndicator
|
||||
) {
|
||||
val channel = channels.createChannel("0.0.0.0:0")
|
||||
val healthStub = HealthGrpc.newBlockingStub(channel)
|
||||
val serviceName = "custom"
|
||||
|
||||
// initially the status should be SERVING
|
||||
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.SERVING, Duration.ofSeconds(4))
|
||||
|
||||
// put the service down and the status should then be NOT_SERVING
|
||||
customHealthIndicator.SERVICE_IS_UP = false
|
||||
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.NOT_SERVING, Duration.ofSeconds(4))
|
||||
|
||||
// put the service up and the status should be SERVING
|
||||
customHealthIndicator.SERVICE_IS_UP = true
|
||||
assertThatGrpcHealthStatusIs(healthStub, serviceName, ServingStatus.SERVING, Duration.ofSeconds(4))
|
||||
}
|
||||
|
||||
private fun assertThatGrpcHealthStatusIs(
|
||||
healthBlockingStub: HealthBlockingStub, service: String,
|
||||
expectedStatus: ServingStatus?, maxWaitTime: Duration?
|
||||
) {
|
||||
Awaitility.await().atMost(maxWaitTime).ignoreException(StatusRuntimeException::class.java).untilAsserted {
|
||||
val healthRequest = HealthCheckRequest.newBuilder().setService(service).build()
|
||||
val healthResponse = healthBlockingStub.check(healthRequest)
|
||||
Assertions.assertThat(healthResponse.getStatus()).isEqualTo(expectedStatus)
|
||||
// verify the overall status as well
|
||||
val overallHealthRequest = HealthCheckRequest.newBuilder().setService("").build()
|
||||
val overallHealthResponse = healthBlockingStub.check(overallHealthRequest)
|
||||
Assertions.assertThat(overallHealthResponse.getStatus()).isEqualTo(expectedStatus)
|
||||
}
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
internal open class MyHealthIndicatorsConfig {
|
||||
@ConditionalOnEnabledHealthIndicator("custom")
|
||||
@Bean
|
||||
open fun customHealthIndicator(): CustomHealthIndicator {
|
||||
return CustomHealthIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomHealthIndicator : HealthIndicator {
|
||||
override fun health(): Health? {
|
||||
return if (SERVICE_IS_UP) Health.up().build() else Health.down().build()
|
||||
}
|
||||
|
||||
var SERVICE_IS_UP: Boolean = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.grpc.sample
|
||||
|
||||
import io.grpc.ForwardingServerCallListener
|
||||
import io.grpc.ManagedChannel
|
||||
import io.grpc.Metadata
|
||||
import io.grpc.ServerCall
|
||||
import io.grpc.ServerCallHandler
|
||||
import io.grpc.ServerInterceptor
|
||||
import io.grpc.Status
|
||||
import io.grpc.StatusRuntimeException
|
||||
import io.grpc.netty.NettyChannelBuilder
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Assert
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.condition.EnabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.boot.test.context.TestConfiguration
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.grpc.client.ChannelBuilderOptions
|
||||
import org.springframework.grpc.client.GrpcChannelBuilderCustomizer
|
||||
import org.springframework.grpc.client.GrpcChannelFactory
|
||||
import org.springframework.grpc.sample.proto.HelloRequest
|
||||
import org.springframework.grpc.sample.proto.SimpleGrpc
|
||||
import org.springframework.grpc.server.GlobalServerInterceptor
|
||||
import org.springframework.grpc.test.AutoConfigureInProcessTransport
|
||||
import org.springframework.grpc.test.LocalGrpcPort
|
||||
import org.springframework.test.annotation.DirtiesContext
|
||||
import org.springframework.test.context.ActiveProfiles
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/**
|
||||
* More detailed integration tests for [gRPC server factories][GrpcServerFactory] and
|
||||
* various [GrpcServerProperties].
|
||||
*/
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class ServerWithInProcessChannel {
|
||||
@Test
|
||||
fun servesResponseToClient(@Autowired channels: GrpcChannelFactory) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:0"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class ServerWithException {
|
||||
|
||||
@Test
|
||||
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat(
|
||||
Assert.assertThrows(StatusRuntimeException::class.java) {
|
||||
client.sayHello(
|
||||
HelloRequest.newBuilder().setName("error").build()
|
||||
)
|
||||
}
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.INVALID_ARGUMENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun defaultErrorResponseIsUnknown(@Autowired channels: GrpcChannelFactory) {
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat<Status.Code?>(
|
||||
Assert.assertThrows(
|
||||
StatusRuntimeException::class.java
|
||||
) { client.sayHello(HelloRequest.newBuilder().setName("internal").build()) }
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class ServerWithExceptionInInterceptorCall {
|
||||
@Test
|
||||
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat(
|
||||
Assert.assertThrows(
|
||||
StatusRuntimeException::class.java
|
||||
) { client.sayHello(HelloRequest.newBuilder().setName("foo").build()) }
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.INVALID_ARGUMENT)
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
internal open class TestConfig {
|
||||
@Bean
|
||||
@GlobalServerInterceptor
|
||||
open fun exceptionInterceptor(): ServerInterceptor {
|
||||
return CustomInterceptor()
|
||||
}
|
||||
|
||||
internal class CustomInterceptor : ServerInterceptor {
|
||||
override fun <ReqT, RespT> interceptCall(
|
||||
call: ServerCall<ReqT?, RespT?>?, headers: Metadata?,
|
||||
next: ServerCallHandler<ReqT?, RespT?>?
|
||||
): ServerCall.Listener<ReqT?>? {
|
||||
throw IllegalArgumentException("test")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class ServerWithExceptionInInterceptorListener {
|
||||
@Test
|
||||
fun specificErrorResponse(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@Autowired testConfig: TestConfig
|
||||
) {
|
||||
testConfig.reset()
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat(
|
||||
Assert.assertThrows(
|
||||
StatusRuntimeException::class.java
|
||||
) { client.sayHello(HelloRequest.newBuilder().setName("foo").build()) }
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.INVALID_ARGUMENT)
|
||||
Assertions.assertThat(TestConfig.readyCount.get()).isEqualTo(1)
|
||||
Assertions.assertThat(TestConfig.callCount.get()).isEqualTo(0)
|
||||
Assertions.assertThat(TestConfig.messageCount.get()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
internal open class TestConfig {
|
||||
companion object {
|
||||
var callCount: AtomicInteger = AtomicInteger()
|
||||
var messageCount: AtomicInteger = AtomicInteger()
|
||||
var readyCount: AtomicInteger = AtomicInteger()
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
callCount.set(0)
|
||||
messageCount.set(0)
|
||||
readyCount.set(0)
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
@GlobalServerInterceptor
|
||||
open fun exceptionInterceptor(): ServerInterceptor {
|
||||
return CustomInterceptor()
|
||||
}
|
||||
|
||||
internal class CustomInterceptor : ServerInterceptor {
|
||||
override fun <ReqT, RespT> interceptCall(
|
||||
call: ServerCall<ReqT?, RespT?>?, headers: Metadata?,
|
||||
next: ServerCallHandler<ReqT?, RespT?>
|
||||
): ServerCall.Listener<ReqT?> {
|
||||
return CustomListener(next.startCall(call, headers))
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomListener<ReqT>(private val delegate: ServerCall.Listener<ReqT?>?) :
|
||||
ForwardingServerCallListener<ReqT?>() {
|
||||
override fun onReady() {
|
||||
readyCount.incrementAndGet()
|
||||
throw IllegalArgumentException("test")
|
||||
}
|
||||
|
||||
override fun onHalfClose() {
|
||||
callCount.incrementAndGet()
|
||||
super.onHalfClose()
|
||||
}
|
||||
|
||||
override fun onMessage(message: ReqT?) {
|
||||
messageCount.incrementAndGet()
|
||||
super.onMessage(message)
|
||||
}
|
||||
|
||||
override fun delegate(): ServerCall.Listener<ReqT?>? {
|
||||
return this.delegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest("spring.grpc.server.exception-handler.enabled=false")
|
||||
@AutoConfigureInProcessTransport
|
||||
internal class ServerWithUnhandledException {
|
||||
@Test
|
||||
fun specificErrorResponse(@Autowired channels: GrpcChannelFactory) {
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat<Status.Code?>(
|
||||
Assert.assertThrows(StatusRuntimeException::class.java) {
|
||||
client.sayHello(
|
||||
HelloRequest.newBuilder().setName("error").build()
|
||||
)
|
||||
}
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.UNKNOWN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun defaultErrorResponseIsUnknown(@Autowired channels: GrpcChannelFactory) {
|
||||
val client = SimpleGrpc.newBlockingStub(channels.createChannel("0.0.0.0:0"))
|
||||
Assertions.assertThat(
|
||||
Assert.assertThrows(
|
||||
StatusRuntimeException::class.java
|
||||
) { client.sayHello(HelloRequest.newBuilder().setName("internal").build()) }
|
||||
.status
|
||||
.code
|
||||
).isEqualTo(Status.Code.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(properties = ["spring.grpc.server.host=0.0.0.0", "spring.grpc.server.port=0"])
|
||||
internal class ServerWithAnyIPv4AddressAndRandomPort {
|
||||
@Test
|
||||
fun servesResponseToClientWithAnyIPv4AddressAndRandomPort(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@LocalGrpcPort port: Int
|
||||
) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:" + port))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(properties = ["spring.grpc.server.host=::", "spring.grpc.server.port=0"])
|
||||
internal class ServerWithAnyIPv6AddressAndRandomPort {
|
||||
@Test
|
||||
fun servesResponseToClientWithAnyIPv4AddressAndRandomPort(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@LocalGrpcPort port: Int
|
||||
) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("0.0.0.0:" + port))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(properties = ["spring.grpc.server.host=127.0.0.1", "spring.grpc.server.port=0"])
|
||||
internal class ServerWithLocalhostAndRandomPort {
|
||||
@Test
|
||||
fun servesResponseToClientWithLocalhostAndRandomPort(
|
||||
@Autowired channels: GrpcChannelFactory,
|
||||
@LocalGrpcPort port: Int
|
||||
) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("127.0.0.1:" + port))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}"]
|
||||
)
|
||||
@DirtiesContext
|
||||
internal class ServerConfiguredWithStaticClientChannel {
|
||||
@Test
|
||||
fun servesResponseToClientWithConfiguredChannel(@Autowired channels: GrpcChannelFactory) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(properties = ["spring.grpc.server.address=unix:unix-test-channel"])
|
||||
@EnabledOnOs(OS.LINUX)
|
||||
internal class ServerWithUnixDomain {
|
||||
@Test
|
||||
fun clientChannelWithUnixDomain(@Autowired channels: GrpcChannelFactory) {
|
||||
assertThatResponseIsServedToChannel(
|
||||
channels.createChannel(
|
||||
"unix:unix-test-channel",
|
||||
ChannelBuilderOptions.defaults()
|
||||
.withCustomizer<NettyChannelBuilder?>(GrpcChannelBuilderCustomizer { `__`: String?, b: NettyChannelBuilder? -> b!!.usePlaintext() })
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.server.port=0", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.test-channel.negotiation-type=TLS", "spring.grpc.client.channels.test-channel.secure=false"]
|
||||
)
|
||||
@ActiveProfiles("ssl")
|
||||
@DirtiesContext
|
||||
internal class ServerWithSsl {
|
||||
@Test
|
||||
fun clientChannelWithSsl(@Autowired channels: GrpcChannelFactory) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SpringBootTest(
|
||||
properties = ["spring.grpc.server.port=0", "spring.grpc.server.ssl.client-auth=REQUIRE", "spring.grpc.server.ssl.secure=false", "spring.grpc.client.channels.test-channel.address=static://0.0.0.0:\${local.grpc.port}", "spring.grpc.client.channels.test-channel.ssl.bundle=ssltest", "spring.grpc.client.channels.test-channel.negotiation-type=TLS", "spring.grpc.client.channels.test-channel.secure=false"]
|
||||
)
|
||||
@ActiveProfiles("ssl")
|
||||
@DirtiesContext
|
||||
internal class ServerWithClientAuth {
|
||||
@Test
|
||||
fun clientChannelWithSsl(@Autowired channels: GrpcChannelFactory) {
|
||||
assertThatResponseIsServedToChannel(channels.createChannel("test-channel"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertThatResponseIsServedToChannel(clientChannel: ManagedChannel?) {
|
||||
val client = SimpleGrpc.newBlockingStub(clientChannel)
|
||||
val response = client.sayHello(HelloRequest.newBuilder().setName("Alien").build())
|
||||
Assertions.assertThat(response.getMessage()).isEqualTo("Hello ==> Alien")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
spring.grpc.server.ssl.bundle=ssltest
|
||||
spring.ssl.bundle.jks.ssltest.keystore.location=classpath:test.jks
|
||||
spring.ssl.bundle.jks.ssltest.keystore.password=secret
|
||||
spring.ssl.bundle.jks.ssltest.keystore.type=JKS
|
||||
spring.ssl.bundle.jks.ssltest.key.password=password
|
||||
BIN
samples/grpc-server-kotlin/src/test/resources/test.jks
Normal file
BIN
samples/grpc-server-kotlin/src/test/resources/test.jks
Normal file
Binary file not shown.
@@ -21,6 +21,7 @@
|
||||
<module>grpc-secure</module>
|
||||
<module>grpc-oauth2</module>
|
||||
<module>grpc-reactive</module>
|
||||
<module>grpc-server-kotlin</module>
|
||||
<module>grpc-server-netty-shaded</module>
|
||||
<module>grpc-tomcat</module>
|
||||
<module>grpc-tomcat-secure</module>
|
||||
|
||||
@@ -10,6 +10,7 @@ include 'grpc-reactive'
|
||||
include 'grpc-oauth2'
|
||||
include 'grpc-secure'
|
||||
include 'grpc-server'
|
||||
include 'grpc-server-kotlin'
|
||||
include 'grpc-server-netty-shaded'
|
||||
include 'grpc-tomcat'
|
||||
include 'grpc-tomcat-secure'
|
||||
|
||||
Reference in New Issue
Block a user