diff --git a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java index 39ee2273c9d..9b1a6e59c4c 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/antora/AntoraAsciidocAttributes.java @@ -161,6 +161,9 @@ public class AntoraAsciidocAttributes { }); attributes.put("version-native-build-tools", (String) this.projectProperties.get("nativeBuildToolsVersion")); attributes.put("version-graal", (String) this.projectProperties.get("graalVersion")); + attributes.put("version-protobuf-gradle-plugin", + (String) this.projectProperties.get("protobufGradlePluginVersion")); + addDependencyVersion(attributes, "grpc-api", "io.grpc:grpc-api"); addDependencyVersion(attributes, "jackson-annotations", "com.fasterxml.jackson.core:jackson-annotations"); addDependencyVersion(attributes, "jackson-core", "tools.jackson.core:jackson-core"); addDependencyVersion(attributes, "jackson-databind", "tools.jackson.core:jackson-databind"); diff --git a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties index f64a46c277c..1797a4028fd 100644 --- a/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties +++ b/buildSrc/src/main/resources/org/springframework/boot/build/antora/antora-asciidoc-attributes.properties @@ -26,6 +26,7 @@ url-gradle-docs-java-plugin={url-gradle-docs}/java_plugin.html url-gradle-docs-war-plugin={url-gradle-docs}/war_plugin.html url-gradle-dsl=https://docs.gradle.org/current/dsl url-gradle-javadoc=https://docs.gradle.org/current/javadoc +url-grpc-api-javadoc=https://javadoc.io/doc/io.grpc/grpc-api/{version-grpc-api} url-kotlin-docs-kotlin-plugin={url-kotlin-docs}/using-gradle.html url-micrometer-docs-concepts={url-micrometer-docs}/concepts url-micrometer-docs-implementations={url-micrometer-docs}/implementations @@ -82,6 +83,8 @@ url-jackson-databind-javadoc=https://javadoc.io/doc/tools.jackson.core/jackson-d url-jackson-dataformat-xml-javadoc=https://javadoc.io/doc/tools.jackson.dataformat/jackson-dataformat-xml/{version-jackson-dataformat-xml} url-jackson2-databind-javadoc=https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/{version-jackson2-databind} +https://javadoc.io/doc/io.grpc/grpc-api/latest/io/grpc/package-summary.html + # === Javadoc Locations === javadoc-location-com-fasterxml-jackson-annotation={url-jackson-annotations-javadoc} @@ -104,6 +107,7 @@ javadoc-location-org-springframework-data-rest={url-spring-data-rest-javadoc} javadoc-location-tools-jackson-core={url-jackson-core-javadoc} javadoc-location-tools-jackson-databind={url-jackson-databind-javadoc} javadoc-location-tools-jackson-dataformat-xml={url-jackson-dataformat-xml-javadoc} +javadoc-location-io-grpc={url-grpc-api-javadoc} # === API References === diff --git a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java index b8506232ba7..ed228c2bf72 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/antora/AntoraAsciidocAttributesTests.java @@ -286,6 +286,7 @@ class AntoraAsciidocAttributesTests { addMockJacksonCoreVersion(versions, "jackson-core", version); addMockJacksonCoreVersion(versions, "jackson-databind", version); addMockJacksonCoreVersion(versions, "jackson-databind", version); + versions.put("io.grpc:grpc-api", version); versions.put("org.apache.pulsar:pulsar-client-api", version); versions.put("tools.jackson.dataformat:jackson-dataformat-xml", version); return versions; diff --git a/documentation/spring-boot-docs/build.gradle b/documentation/spring-boot-docs/build.gradle index 2d739b45907..cf6ab55c302 100644 --- a/documentation/spring-boot-docs/build.gradle +++ b/documentation/spring-boot-docs/build.gradle @@ -149,6 +149,8 @@ dependencies { implementation("ch.qos.logback:logback-classic") implementation("com.redis:testcontainers-redis") implementation("com.zaxxer:HikariCP") + implementation("io.grpc:grpc-stub") + implementation("io.grpc:grpc-netty") implementation("io.micrometer:micrometer-jakarta9") implementation("io.micrometer:micrometer-tracing") implementation("io.micrometer:micrometer-registry-graphite") diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc new file mode 100644 index 00000000000..1289ead9474 --- /dev/null +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/io/grpc.adoc @@ -0,0 +1,743 @@ +[[io.grpc]] += gRPC + +Google Remote Procedure Call (gRPC) is a high-performance RPC framework that enables client-server communication using binary messages. +Spring Boot include support for developing and testing both client and server gRPC applications. + +The underling message format used by gRPC is Protocol Buffers which allow messages to be created and consumed by a wide variety of programming languages. + + + +[[io.grpc.servicedefinitions]] +== Service Definitions + +To develop a gRPC application you first need a Protocol Buffers service definition file. +A `.proto` file defines the services and messages that your application can consume or provide. + +Here's an example of a typical `.proto` file that uses https://protobuf.dev/programming-guides/proto3/[the `proto3` revision] of the protocol buffers language: + +[,protobuf] +---- +syntax = "proto3"; + +option java_package = "com.example.grpc.proto"; +option java_multiple_files = true; + +service HelloWorld { + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +message HelloRequest { + string name = 1; +} + +message HelloReply { + string message = 1; +} +---- + +This file defines a `HelloWorld` service with a single method that accepts a `HelloReqest` message and return a `HelloReply` message. +The `HelloReqest` message contains a `name` string field. +The `HelloReply` message contains a `message` string field. + +With the exception of a the `java_package` and `java_multiple_files` options, there is nothing in the `.proto` file that is specific to the Java programming langage. + + + +[[io.grpc.servicedefinitions.generatingjavacode]] +=== Generating Java Code + +Since `.proto` files are language agnostic, we need a process to convert them into usable Java code. +We can then use the generated code to either make a remote procedure call to running service, or implement the service ourselves so that others may call it. + +The exact process you use to generate code will depend on your build system. +Spring Boot supports for both Maven and Gradle protobuf plugins, but you are free to use whatever solution works best for you. + + + +[[io.grpc.servicedefinitions.generatingjavacode.maven]] +==== Using the Maven Plugin + +Spring Boot include dependency management for the `io.github.ascopes:protobuf-maven-plugin` Maven plugin. +If you are using the the `spring-boot-starter-parent` POM, you'll also get sensible out-of-the-box configuration. + +The following shows a typical Maven POM file that uses the plugin: + +[source,xml,subs="verbatim,attributes"] +---- + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + {version-spring-boot} + + com.example + myproject + 0.0.1-SNAPSHOT + + + + + io.github.ascopes + protobuf-maven-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + +ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] + + + + spring-snapshots + https://repo.spring.io/snapshot + + true + + + + + + spring-snapshots + https://repo.spring.io/snapshot + + true + + + +endif::[] + +---- + +Since POM above extends `spring-boot-starter-parent`, you'll get the following: + +* Configuration of the `protoc` version. +* Configuration of the `binary-maven` plugin. +* Execution configuration for the `generate` goal. + +The `.proto` files should be added to `src/main/proto`. + +TIP: If you don't use `spring-boot-starter-parent`, or you want to configure the plugin directly, refer to the https://ascopes.github.io/protobuf-maven-plugin[protobuf-maven-plugin documentation]. +If you use Spring Boot's dependency management the `${protobuf-java.version}` and `${grpc-java.version}` properties will be useful. + + + +[[io.grpc.servicedefinitions.generatingjavacode.gradle]] +==== Using the Gradle Plugin + +Spring Boot include dependency management for the `com.google.protobuf:protobuf-gradle-plugin` Gradle plugin. +In addition, the `spring-boot-gradle-plugin` will react to the presence of the protobuf plugin and configure it appropriately. + +The following shows a typical Gradle file that uses the plugin: + +[source,gradle,subs="verbatim,attributes"] +---- +plugins { + id 'java' + id 'org.springframework.boot' version '{version-spring-boot}' + id 'io.spring.dependency-management' version '{version-dependency-management-plugin}' + id 'com.google.protobuf' version '{version-protobuf-gradle-plugin}' +} + +group = 'com.example' +version = '0.0.1-SNAPSHOT' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] + // you don't need this if you are using a release version + maven { url 'https://repo.spring.io/snapshot' } +endif::[] +} +---- + +Since this gradle file uses both the `org.springframework.boot` and `com.google.protobuf` plugins, you'll get the following: + +* Configuration of the `protoc` version. +* Configuration of the `protoc-gen-grpc-java` version. + +The `.proto` files should be added to `src/main/proto`. + +TIP: If you don't use `org.springframework.boot` plugin, or you want to configure the plugin directly, refer to the https://github.com/google/protobuf-gradle-plugin[protobuf-gradle-plugin documentation]. + + + +[[io.grpc.server]] +== Writing a gRPC Server Application + +Spring Boot provides a `spring-boot-grpc-server` module and a `spring-boot-starter-grpc-server` starter POM that you can use for server applications. + +In order to write the actual server code, you'll need to extended one or more of base classes generated from your `.proto` file and expose them as Spring beans. +Spring gRPC will automatically expose any bean that implements javadoc:io.grpc.BindableService[] as a gRPC server. +Since all `.proto` generated classes implement javadoc:io.grpc.BindableService[], adding them as beans is enough to expose them over gRPC. + +TIP: For more details see {url-spring-grpc-docs}/server.html#_create_a_grpc_service[the Spring gRPC documentation]. + +The following example shows how the `HelloWorld` service from the `.proto` file above could be implemented. +In this example, we're using the javadoc:org.springframework.grpc.server.service.GrpcService[format=annotation] annotation and assuming that the code is in a package that will be picked up by component scanning: + +include-code::MyHelloWorldService[] + +If the application makes used of `spring-boot-starter-grpc-server`, then Netty will be used as the server implementation listening on port `9090`. + +You can test your application using https://github.com/fullstorydev/grpcurl[grpcurl]: + +[source,shell] +---- +$ grpcurl -d '{"name":"Spring"}' -plaintext localhost:9090 HelloWorld.SayHello +---- + +[source,json] +---- +{ + "message": "Hello 'Spring'" +} +---- + + + +[[io.grpc.server.netty-shaded]] +=== Switching to a Netty Shaded Server + +If you find that the version of Netty provided by the `spring-boot-starter-grpc-server` starter POM isn't compatible with other libraries you use, you can switch to a "`shaded`" version. + +To switch, you can excluded `io.grpc:grpc-netty` and include `io.grpc:grpc-netty-shaded`. +For example: + + +[tabs] +====== + +Maven:: ++ +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-grpc-server + + + + io.grpc + grpc-netty + + + + + + io.grpc + grpc-netty-shaded + +---- ++ + +Gradle:: ++ +[source,gradle] +---- +dependencies { + implementation('org.springframework.boot:spring-boot-starter-grpc-server') { + // Exclude the gRPC Netty dependency + exclude group: 'io.grpc', module: 'grpc-netty' + } + // Use gRPC Netty Shaded instead + implementation "io.grpc:grpc-netty-shaded" +} +---- ++ +====== + + + +[[io.grpc.server.servlet]] +=== Switching to a Servlet Container + +It's possible to expose gRPC services using a regular Servlet Container such as Tomcat rather than using Netty. +To do so, your Servlet Container must be configured to support HTTP/2. + +To switch to the Servlet gRPC implementation, you can exclude `io.grpc:grpc-netty` and include `io.grpc:grpc-servlet-jakarta`. +For example: + +[tabs] +====== + +Maven:: ++ +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-webmvc + + + org.springframework.boot + spring-boot-starter-grpc-server + + + + io.grpc + grpc-netty + + + + + + io.grpc + grpc-servlet-jakarta + +---- ++ + +Gradle:: ++ +[source,gradle] +---- +dependencies { + implementation('org.springframework.boot:spring-boot-starter-webmvc') { + implementation('org.springframework.boot:spring-boot-starter-grpc-server') { + // Exclude the gRPC Netty dependency + exclude group: 'io.grpc', module: 'grpc-netty' + } + // Use gRPC Servlet Jakarta instead + implementation "io.grpc:grpc-servlet-jakarta" +} +---- ++ +====== + +TIP: Remember to include a Servlet Container dependency, for example using `spring-boot-starter-tomcat`, and to set `server.http2.enabled` to `true`. + +NOTE: When using a servlet container, certain gRPC server configuration properties are not relevant and will be ignored. +For example, configprop:spring.grpc.server.port[] is ignored since configprop:server.port[] used used to set a web server port. + + + +[[io.grpc.server.ssl]] +=== SSL Support + +SSL can be configured for both `grpc-netty` and `grpc-netty-shaded` servers using SSL bundles. +See the xref:features/ssl.adoc[SSL core documentation] for details on how to declare an SSL bundle. + +Once your bundle has been defined, you can use the following properties in your gRPC server application to use it: + +[configprops,yaml] +---- +spring: + grpc: + server: + ssl: + bundle: mysslbundle +---- + +Client authentication can also be configured by setting configprop:spring.grpc.server.ssl.client-auth[] to `optional` or `require`. + +TIP: To temporarily disable server SSL support, for example to aid with testing, you can set configprop:spring.grpc.server.ssl.enabled[] to `false`. + + + +[[io.grpc.server.in-process]] +=== Using an In-Process Server + +You can run an in-process server by including the `io.grpc:grpc-inprocess` dependency on your classpath and defining a configprop:spring.grpc.server.inprocess.name[] property. +In this mode, the in-process server factory is auto-configured in addition to the regular server factory. + +The name you provide can be used as a client channel target using the form `in-process:`. + + + +[[io.grpc.server.reflection]] +=== Reflection + +When it's available, Spring Boot will auto-configure the https://grpc.io/docs/guides/reflection/[gRPC Reflection service]. +This allows clients to browse the metadata of your services and download their `.proto` files. + +The reflection service resides in the `io.grpc:grpc-services` library, which is an optional dependency. +You will need to add the dependency to your project in order for auto-configuration to apply. + +TIP: If you have the `io.grpc:grpc-services` library but prefer that reflection isn't auto-configured, you can set configprop:spring.grpc.server.reflection.enabled[] to `false`. + + + +[[io.grpc.server.health]] +=== Server Health + +A gRPC server can provide health information using a standard service API (https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto[health/v1]). +This allows clients to check on the health of your server services a route traffic appropriately. + +Spring Boot provides a bridge between its own `spring-boot-health` module and the standard gRPC health service. +Health information is provided whenever the `io.grpc:grpc-services` and `org.springframework.boot:spring-boot-health` modules are on your classpath. + +TIP: If you don't want health indicators to be exposed, you can set configprop:spring.grpc.server.health.enabled[] to `false`. + + + +[[io.grpc.server.health.service-mappings]] +==== Service Specific Health Mappings + +By default, health information is provided for the overall server status (`""`) using all available health indicators. + +It is also possible to provide fine-grained health information for specific services by including only a sub-set of health indicators. +Custom mapping and ordering rules can also be defined on a per-service basis. + +For example, the following configuration will provide health for "`myservice`" using only the `db` and `redis` indicators. + +[configprops,yaml] +---- +spring: + grpc: + server: + health: + service: + myservice: + include: + - db + - redis +---- + +TIP: You can set configprop:spring.grpc.server.health.include-overall-health[] to `false` to disable the overall server status health if you only want to provide service-specific health. + + + +[[io.grpc.server.health.push]] +==== Push Configuration + +Unlike web-based health checks, gRPC health information is periodically pushed rather than pulled. +By default, the first health push happens 5 seconds after the application starts and then every subsequent 5 seconds. + +To fine-tune this, you can use the following properties: + +[configprops,yaml] +---- +spring: + grpc: + server: + health: + schedule: + period: 5m + delay: 2s +---- + +TIP: You can also set configprop:spring.grpc.server.health.schedule.enabled[] to `false` if want to send health updates in some other way. + + + +[[io.grpc.server.security]] +=== Securing gRPC Server Applications + + + +[[io.grpc.server.security.netty]] +==== Netty Based Servers + +Spring gRPC includes features that allow you to secure your Netty based server applications declaratively using Spring Security. +This follows similar patterns to those you would use to secure a regular web application. + +Spring Boot provides auto-configuration for both javadoc:org.springframework.grpc.server.security.GrpcSecurity[] and javadoc:org.springframework.grpc.server.security.SecurityGrpcExceptionHandler[] beans. +Typically gRPC application are then secured using javadoc:org.springframework.security.access.prepost.PreAuthorize[format=annotation] annotations on your gRPC service beans, or a javadoc:org.springframework.grpc.server.security.AuthenticationProcessInterceptor[] bean. + +For more details, please see {url-spring-grpc-docs}/server.html#_declarative_security_with_spring_security[the Spring gRPC documentation]. + + + +[[io.grpc.server.security.servlet]] +==== Servlet Container Based Servers + +If your gRPC server is running xref:web/servlet.adoc[within a standard Servlet Container], you can use typical xref:web/spring-security.adoc#web.security.spring-mvc[web security configuration] to secure your application. +Spring Boot will auto-configre javadoc:org.springframework.boot.grpc.server.autoconfigure.GrpcServerExecutorProvider[] and javadoc:org.springframework.grpc.server.security.SecurityContextServerInterceptor[] beans to ensure that Spring Security works correctly. + +Cross-Site Request Forgery (CSRF) protection is incompatible with the gRPC protocol and will be disabled by default for all gRPC requests. +If you prefer to configure your own CSRF protection, you can switch this off by setting configprop:spring.grpc.server.security.csrf.enabled[] to `false`. + +To help with manual Security configuration, Spring Boot provides request matchers for gRPC services. +Matches are available for both servlet and reactive stacks. +For example, the following will include all gRPC services with the exception of "`special`". + +include-code::MySecurityConfiguration[] + +NOTE: For reactive matches use javadoc:org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest[format=full]. + + + +[[io.grpc.server.security.oauth-resource-server]] +==== OAuth2 Resource Server + +https://oauth.net/2/[OAuth2] is a widely used authorization framework. +Spring Boot's OAuth2 Resource Server support in compatible with gRPC and may be configured in the usual way. + +For details of how to configure an OAuth2 Resource Server to use with your gRPC server application, see the xref:security/oauth2.adoc#security.oauth2.server["`OAuth2`" section] of under "`Security`". + + + +[[io.grpc.client]] +== Writing a gRPC Client Application + +Spring Boot provides a `spring-boot-grpc-client` module and a `spring-boot-starter-grpc-client` starter POM that you can use for client applications. + +Clients can call remote gRPC services by importing one or more of the "`stub`" classes generated from their `.proto` file. +You can use the javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] annotation to import the stub classes you want to use. + +Each import includes a `target` which can either be a logical channel name, or the base URL of the remote server. +We typically recommend using channel names rather than hard-coding targets. + +Here's a typical example: + +include-code::MyApplication[] + +NOTE: If you don't specify a `target` then "`default`" is used. + +TIP: You can use the `basePackageClasses` or `basePackages` attribute of javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] to import all stubs in given package. + + + +[[io.grpc.client.channel-properties]] +=== Channel Properties + +When the `target` attribute of javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] uses a logical channel name, you'll need to provide some properties so that Spring Boot can find the actual gRPC server to call. + +To do that, you can add an entry using `spring.grpc.channel..*` properties. +Typically, you'll configure the actual real `target`, along with any other settings that are unique to the channel. + +For example, the following will configure `myservice` to use the real target of `static://grpc.example.com:9090`. +It also changes the keep-alive timeout and the maximum message size permitted: + +[configprops,yaml] +---- +spring: + grpc: + client: + channel: + myservice: + target: static://grpc.example.com:9090 + inbound: + keepalive: + timeout: 40s + message: + max-size: 8MB +---- + + +[[io.grpc.client.stub-beans]] +=== Using Stub Beans + +With your javadoc:org.springframework.grpc.client.ImportGrpcClients[format=annotation] annotations in place, and your properties written, you can use stubs as you would any other bean. + +For example, here's the `HelloWorldStub` being injected into a `ApplicationRunner` bean: + +include-code::MyApplicationRunner[] + + + +[[io.grpc.client.netty-shaded]] +=== Switching to Netty Shaded Client Transport + +Under the hood, remote gRPC network calls are made using Netty. +If you find that the version of Netty provided by the `spring-boot-starter-grpc-client` starter POM isn't compatible with other libraries you use, you can switch to a "`shaded`" version. + +To switch, you can excluded `io.grpc:grpc-netty` and include `io.grpc:grpc-netty-shaded`. +For example: + + +[tabs] +====== + +Maven:: ++ +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-grpc-client + + + + io.grpc + grpc-netty + + + + + + io.grpc + grpc-netty-shaded + +---- ++ + +Gradle:: ++ +[source,gradle] +---- +dependencies { + implementation('org.springframework.boot:spring-boot-starter-grpc-client') { + // Exclude the gRPC Netty dependency + exclude group: 'io.grpc', module: 'grpc-netty' + } + // Use gRPC Netty Shaded instead + implementation "io.grpc:grpc-netty-shaded" +} +---- ++ +====== + + +[[io.grpc.client.ssl]] +=== SSL Support + +Client gRPC applications can connect to gRPC services using SSL/TSL encrypted connections. +You can configure gRPC connections to use standard one-way-TLS, or mutual TLS + + + +[[io.grpc.client.ssl.one-way]] +==== Standard one-way TLS + +To use standard one-way TLS, you can set the `ssl.enabled` property to `true` in your channel properties. +For example, the following will enabled an SSL/TLS connection for the `myservice` channel: + +[configprops,yaml] +---- +spring: + grpc: + client: + channel: + myservice: + target: static://grpc.example.com:9090 + ssl: + enabled: true +---- + + + +[[io.grpc.client.ssl.mutual]] +==== Mutual TLS + +Mutual TLS (mTLS) is a security protocol that requires both the client and the server to present certificates to each other. +To use mutual TLS, you can set the `ssl.bundle` property in your channel properties. +See the xref:features/ssl.adoc[SSL core features documentation] for details on how to declare an SSL bundle. + +Here is an example the configures the `myservice` channel to use the `mybundle` bundle for mutual TLS: + +[configprops,yaml] +---- +spring: + grpc: + client: + channel: + myservice: + target: static://grpc.example.com:9090 + ssl: + bundle: mybundle +---- + +[TIP] +==== +To temporarily disable client SSL support, for example to aid with testing, you can set `bypass-certificate-validation` to `true` on your channel config: + +[configprops,yaml] +---- +spring: + grpc: + client: + channel: + myservice: + bypass-certificate-validation: true +---- +==== + + + +[[io.grpc.client.in-process]] +=== Using In-Process Channels + +You can communicate with an xref:io/grpc.adoc#io.grpc.server.in-process[in-process server] (i.e. not listening on a network port) by including the `io.grpc.grpc-inprocess` dependency on your classpath. + +In this mode, the in-process channel factory is auto-configured in addition to the regular channel factories (e.g. Netty). +To prevent users from having to deal with multiple channel factories, a composite channel factory is configured as the primary channel factory bean. +The composite consults its composed factories to find the first one that supports the channel target. + +To use the in-process server the channel target must be set to `in-process:` + +TIP: To disable the in-process channel factory, you can set the configprop:spring.grpc.client.inprocess.enabled[] property to `false`. + + + +[[io.grpc.client.observability]] +=== Observability + +Spring Boot provides auto-configuration of the javadoc:io.micrometer.core.instrument.binder.grpc.ObservationGrpcClientInterceptor[] whenever Micrometer is available. +This interceptor provides observability into your gRPC client applications. + +TIP: If you use Micrometer, but prefer to not to use it for gRPC, you can set configprop:spring.grpc.client.observation.enabled[] to `false`. + + + +[[io.grpc.client.channel-customization]] +=== Channel Customization + +If you need to customize your gRPC channel beyond the basic properties, you can use a javadoc:org.springframework.grpc.client.GrpcChannelBuilderCustomizer[]. +Each customizer is called with the logical target name and the javadoc:io.grpc.ManagedChannelBuilder[] that will build the channel. +There's also a convenient `matching(String pattern)` factory method that will limit customizations to targets that match the given regex pattern. + +A common customizer use-case is to add {url-spring-grpc-docs}/client.html#_http_headers[security interceptors] to the builder. +For example, here we're adding the javadoc:org.springframework.grpc.client.interceptor.security.BearerTokenAuthenticationInterceptor[] to the target matching "`hello`": + +include-code::MyGrpcConfiguration[] + + + +[[io.grpc.testing]] +== Testing gRPC Applications + +To help test your gRPC client and server applications you can use the `spring-boot-grpc-test` module or the `spring-boot-starter-grpc-client-test` / `spring-boot-starter-grpc-server-test` starter POMs. + + +[[io.grpc.testing.test-transport]] +=== Using In-Process Test Transport + +The javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] annotation allows you to quickly replace gRPC communication channels with in-process channels specifically designed for testing. +Unlike regular in-process channels, these test channels to not require any configuration. + +Using test gRPC transport means that you don't need to actually listen on a network port to start your application. +This allows your tests to run quickly, whilst still ensuring that your application works as expected. + +By default, using javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] will: + +* Configure test javadoc:org.springframework.grpc.server.GrpcServerFactory[] / javadoc:org.springframework.grpc.client.GrpcChannelFactory[] beans +* Disable any gRPC servelt registration. +* Disable javadoc:org.springframework.grpc.server.GrpcServerFactory[] bean auto-configuration. +* Disable javadoc:org.springframework.grpc.client.GrpcChannelFactory[] bean auto-configuration. + +The following example shows how you can use javadoc:org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport[format=annotation] to test a gRPC server application: + +include-code::MyGrpcTests[] + + + +[[io.grpc.testing.local-server-port]] +=== Testing With a Running Server + +If you prefer to test your gRPC application by starting the real server and using the actual network connection, we recommend that you use random ports. +This will ensure that you can run your tests in any environment, and that you won't accidentally call real services. + +To start a gRPC server using a random port, set configprop:spring.grpc.server.port[] to `0`. +You can use the javadoc:org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort[format=annotation] annotation to obtain the actual port that the server started on. + +Here's an example test: + +include-code::MyGrpcIntegrationTests[] + + + +// FIXME https://github.com/spring-projects/spring-grpc/issues/397 \ No newline at end of file diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc index 3f1235532fd..8b7e069f3cd 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/reference/partials/nav-reference.adoc @@ -40,6 +40,7 @@ ** xref:reference:io/index.adoc[] *** xref:reference:io/caching.adoc[] *** xref:reference:io/spring-batch.adoc[] +*** xref:reference:io/grpc.adoc[] *** xref:reference:io/hazelcast.adoc[] *** xref:reference:io/quartz.adoc[] *** xref:reference:io/email.adoc[] diff --git a/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc b/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc index 97b9bfe20c7..f21ceb0be5f 100644 --- a/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc +++ b/documentation/spring-boot-docs/src/docs/antora/modules/tutorial/pages/first-application/index.adoc @@ -155,7 +155,7 @@ Open your favorite text editor and add the following: plugins { id 'java' id 'org.springframework.boot' version '{version-spring-boot}' - id 'io.spring.dependency-management' version '{version-dependency-management-plugin}' + id 'io.spring.dependency-management' version '{version-dependency-management-plugin}' } group = 'com.example' @@ -170,7 +170,7 @@ java { repositories { mavenCentral() ifeval::["{build-and-artifact-release-type}" == "opensource-snapshot"] - // you don't need this if you are using a release version + // you don't need this if you are using a release version maven { url 'https://repo.spring.io/snapshot' } endif::[] } diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java new file mode 100644 index 00000000000..a9d29503cdc --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client; + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java new file mode 100644 index 00000000000..ed692948df8 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/MyApplication.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.grpc.client.ImportGrpcClients; + +@SpringBootApplication(proxyBeanMethods = false) +@ImportGrpcClients(target = "hello", types = HelloWorldGrpc.HelloWorldBlockingStub.class) +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java new file mode 100644 index 00000000000..5d64753c609 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client.channelcustomization; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.grpc.client.GrpcChannelBuilderCustomizer; +import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor; + +@Configuration(proxyBeanMethods = false) +public class MyGrpcConfiguration { + + @Bean + GrpcChannelBuilderCustomizer helloChannelCustomizer() { + return GrpcChannelBuilderCustomizer.matching("hello", + (builder) -> builder.intercept(new BasicAuthenticationInterceptor("user", "password"))); + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java new file mode 100644 index 00000000000..5fd2437dda2 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client.stubbeans; + +interface HelloReply { + + String getMessage(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setMessage(String message); + + HelloReply build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java new file mode 100644 index 00000000000..baf2f88e52d --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client.stubbeans; + +interface HelloRequest { + + String getName(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setName(String name); + + HelloRequest build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java new file mode 100644 index 00000000000..f86094051b8 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client.stubbeans; + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + HelloReply sayHello(HelloRequest request); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java new file mode 100644 index 00000000000..772749a648e --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.client.stubbeans; + +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.boot.docs.io.grpc.client.stubbeans.HelloWorldGrpc.HelloWorldBlockingStub; +import org.springframework.stereotype.Component; + +@Component +class MyApplicationRunner implements ApplicationRunner { + + private final HelloWorldBlockingStub helloStub; + + MyApplicationRunner(HelloWorldGrpc.HelloWorldBlockingStub helloStub) { + this.helloStub = helloStub; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + HelloRequest request = HelloRequest.newBuilder().setName("Spring").build(); + HelloReply reply = this.helloStub.sayHello(request); + System.out.println(reply.getMessage()); + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java new file mode 100644 index 00000000000..d951f2b874f --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloReply.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.server; + +interface HelloReply { + + String getMessage(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setMessage(String message); + + HelloReply build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java new file mode 100644 index 00000000000..f0bb4dcf6df --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.server; + +interface HelloRequest { + + String getName(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setName(String name); + + HelloRequest build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java new file mode 100644 index 00000000000..de1595401c8 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.java @@ -0,0 +1,29 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.server; + +import io.grpc.stub.StreamObserver; + +class HelloWorldGrpc { + + abstract static class HelloWorldImplBase { + + abstract void sayHello(HelloRequest request, StreamObserver responseObserver); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java new file mode 100644 index 00000000000..70de5ff2d98 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.server; + +import io.grpc.stub.StreamObserver; + +import org.springframework.grpc.server.service.GrpcService; + +@GrpcService +public class MyHelloWorldService extends HelloWorldGrpc.HelloWorldImplBase { + + @Override + public void sayHello(HelloRequest request, StreamObserver responseObserver) { + String message = "Hello '%s'".formatted(request.getName()); + HelloReply reply = HelloReply.newBuilder().setMessage(message).build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java new file mode 100644 index 00000000000..d2d9a64929f --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.server.security.servlet; + +import org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.Customizer.withDefaults; + +@Configuration(proxyBeanMethods = false) +public class MySecurityConfiguration { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) { + http.securityMatcher(GrpcRequest.toAnyService().excluding("special")); + http.authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("GRPC_ADMIN")); + http.httpBasic(withDefaults()); + return http.build(); + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java new file mode 100644 index 00000000000..46ff563e859 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.localserverport; + +interface HelloReply { + + String getMessage(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setMessage(String message); + + HelloReply build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java new file mode 100644 index 00000000000..b2ed9386480 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.localserverport; + +interface HelloRequest { + + String getName(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setName(String name); + + HelloRequest build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java new file mode 100644 index 00000000000..db8994d4aad --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.java @@ -0,0 +1,33 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.localserverport; + +import io.grpc.ManagedChannel; + +interface HelloWorldGrpc { + + static HelloWorldBlockingStub newBlockingStub(ManagedChannel channel) { + throw new IllegalStateException(); + } + + interface HelloWorldBlockingStub { + + HelloReply sayHello(HelloRequest request); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java new file mode 100644 index 00000000000..5fa4304daf3 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.localserverport; + +import io.grpc.ManagedChannel; +import io.grpc.netty.NettyChannelBuilder; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.docs.io.grpc.testing.localserverport.HelloWorldGrpc.HelloWorldBlockingStub; +import org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(properties = "spring.grpc.server.port=0") +class MyGrpcIntegrationTests { + + @LocalGrpcServerPort + private int port; + + @Test + void sayHello() { + String target = "localhost:%s".formatted(this.port); + ManagedChannel channel = NettyChannelBuilder.forTarget(target).usePlaintext().build(); + try { + HelloWorldBlockingStub hello = HelloWorldGrpc.newBlockingStub(channel); + HelloRequest request = HelloRequest.newBuilder().setName("Spring").build(); + assertThat(hello.sayHello(request).getMessage()).isEqualTo("Hello 'Spring'"); + } + finally { + channel.shutdown(); + } + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java new file mode 100644 index 00000000000..4a15ad45cea --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.testtransport; + +interface HelloReply { + + String getMessage(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setMessage(String message); + + HelloReply build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java new file mode 100644 index 00000000000..8d520fc6605 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.testtransport; + +interface HelloRequest { + + String getName(); + + static Builder newBuilder() { + throw new IllegalStateException(); + } + + interface Builder { + + Builder setName(String name); + + HelloRequest build(); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java new file mode 100644 index 00000000000..b5d680b5fc7 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.java @@ -0,0 +1,27 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.testtransport; + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + HelloReply sayHello(HelloRequest request); + + } + +} diff --git a/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java new file mode 100644 index 00000000000..178192a4716 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/java/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-present 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.boot.docs.io.grpc.testing.testtransport; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.grpc.client.ImportGrpcClients; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@AutoConfigureTestGrpcTransport +@ImportGrpcClients(types = HelloWorldGrpc.HelloWorldBlockingStub.class) +class MyGrpcTests { + + @Autowired + private HelloWorldGrpc.HelloWorldBlockingStub helloStub; + + @Test + void sayHello() { + HelloRequest request = HelloRequest.newBuilder().setName("Spring").build(); + HelloReply reply = this.helloStub.sayHello(request); + assertThat(reply.getMessage()).isEqualTo("Hello 'Spring'"); + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt new file mode 100644 index 00000000000..f4da5896b39 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/HelloWorldGrpc.kt @@ -0,0 +1,9 @@ +package org.springframework.boot.docs.io.grpc.client + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt new file mode 100644 index 00000000000..7b5c0a369f3 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/MyApplication.kt @@ -0,0 +1,14 @@ +package org.springframework.boot.docs.io.grpc.client + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.docs.features.springapplication.MyApplication +import org.springframework.boot.runApplication +import org.springframework.grpc.client.ImportGrpcClients + +@SpringBootApplication(proxyBeanMethods = false) +@ImportGrpcClients(target = "hello", types = [HelloWorldGrpc.HelloWorldBlockingStub::class]) +class MyApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt new file mode 100644 index 00000000000..36c019233b4 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/channelcustomization/MyGrpcConfiguration.kt @@ -0,0 +1,20 @@ +package org.springframework.boot.docs.io.grpc.client.channelcustomization + +import io.grpc.ManagedChannelBuilder +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.grpc.client.GrpcChannelBuilderCustomizer +import org.springframework.grpc.client.interceptor.security.BasicAuthenticationInterceptor +import java.util.function.Consumer + +@Configuration(proxyBeanMethods = false) +class MyGrpcConfiguration { + + @Bean + fun helloChannelCustomizer(): GrpcChannelBuilderCustomizer<*> { + return GrpcChannelBuilderCustomizer.matching("hello", { builder -> + builder.intercept(BasicAuthenticationInterceptor("user", "password")) + }) + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt new file mode 100644 index 00000000000..b9e136cb17c --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloReply.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.client.stubbeans + +interface HelloReply { + + fun getMessage(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setMessage(message: String): Builder + + fun build(): HelloReply + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt new file mode 100644 index 00000000000..70c921637bc --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloRequest.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.client.stubbeans + +interface HelloRequest { + + fun getName(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setName(name: String): Builder + + fun build(): HelloRequest + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt new file mode 100644 index 00000000000..2bb2103ab8d --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/HelloWorldGrpc.kt @@ -0,0 +1,11 @@ +package org.springframework.boot.docs.io.grpc.client.stubbeans + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + fun sayHello(request: HelloRequest): HelloReply + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt new file mode 100644 index 00000000000..f441809cdc4 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/client/stubbeans/MyApplicationRunner.kt @@ -0,0 +1,15 @@ +package org.springframework.boot.docs.io.grpc.client.stubbeans + +import org.springframework.boot.ApplicationArguments +import org.springframework.boot.ApplicationRunner + + +class MyApplicationRunner(val helloStub: HelloWorldGrpc.HelloWorldBlockingStub) : ApplicationRunner { + + override fun run(args: ApplicationArguments) { + val request = HelloRequest.newBuilder().setName("Spring").build() + val reply: HelloReply = helloStub.sayHello(request) + println(reply.getMessage()) + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt new file mode 100644 index 00000000000..6ea3efc39c0 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloReply.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.server + +interface HelloReply { + + fun getMessage(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setMessage(message: String): Builder + + fun build(): HelloReply + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt new file mode 100644 index 00000000000..2fedd32fa64 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloRequest.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.server + +interface HelloRequest { + + fun getName(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setName(name: String): Builder + + fun build(): HelloRequest + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt new file mode 100644 index 00000000000..858880344fe --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/HelloWorldGrpc.kt @@ -0,0 +1,14 @@ +package org.springframework.boot.docs.io.grpc.server + +import io.grpc.stub.StreamObserver + +class HelloWorldGrpc { + + abstract class HelloWorldImplBase { + abstract fun sayHello( + request: HelloRequest, + responseObserver: StreamObserver + ) + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt new file mode 100644 index 00000000000..5a6d57d41a4 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/MyHelloWorldService.kt @@ -0,0 +1,16 @@ +package org.springframework.boot.docs.io.grpc.server + +import io.grpc.stub.StreamObserver +import org.springframework.grpc.server.service.GrpcService + +@GrpcService +class MyHelloWorldService : HelloWorldGrpc.HelloWorldImplBase() { + + override fun sayHello(request: HelloRequest, responseObserver: StreamObserver) { + val message = "Hello '${request.getName()}'" + val reply = HelloReply.newBuilder().setMessage(message).build() + responseObserver.onNext(reply) + responseObserver.onCompleted() + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt new file mode 100644 index 00000000000..d8af99d1b9b --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/server/security/servlet/MySecurityConfiguration.kt @@ -0,0 +1,21 @@ +package org.springframework.boot.docs.io.grpc.server.security.servlet + +import org.springframework.boot.grpc.server.autoconfigure.security.web.servlet.GrpcRequest +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.Customizer.withDefaults +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.SecurityFilterChain + +@Configuration(proxyBeanMethods = false) +class MySecurityConfiguration { + + @Bean + fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { + http.securityMatcher(GrpcRequest.toAnyService().excluding("special")) + http.authorizeHttpRequests { requests -> requests.anyRequest().hasRole("GRPC_ADMIN") } + http.httpBasic(withDefaults()) + return http.build() + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt new file mode 100644 index 00000000000..fc4df9a7d98 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloReply.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.testing.localserverport + +interface HelloReply { + + fun getMessage(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setMessage(message: String): Builder + + fun build(): HelloReply + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt new file mode 100644 index 00000000000..0dec210254a --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloRequest.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.testing.localserverport + +interface HelloRequest { + + fun getName(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setName(name: String): Builder + + fun build(): HelloRequest + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt new file mode 100644 index 00000000000..d19c4cadf93 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/HelloWorldGrpc.kt @@ -0,0 +1,19 @@ +package org.springframework.boot.docs.io.grpc.testing.localserverport + +import io.grpc.ManagedChannel + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + fun sayHello(request: HelloRequest): HelloReply + + } + + companion object { + fun newBlockingStub(channel: ManagedChannel): HelloWorldBlockingStub { + throw IllegalStateException() + } + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt new file mode 100644 index 00000000000..f53cbaccdb2 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/localserverport/MyGrpcIntegrationTests.kt @@ -0,0 +1,28 @@ +package org.springframework.boot.docs.io.grpc.testing.localserverport + +import io.grpc.ManagedChannel +import io.grpc.netty.NettyChannelBuilder +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.springframework.boot.grpc.test.autoconfigure.LocalGrpcServerPort +import org.springframework.boot.test.context.SpringBootTest + +@SpringBootTest(properties = ["spring.grpc.server.port=0"]) +class MyGrpcIntegrationTests { + + @LocalGrpcServerPort + var port = 0 + + @Test + fun sayHello() { + val target = "localhost:${port}" + val channel: ManagedChannel = NettyChannelBuilder.forTarget(target).usePlaintext().build() + try { + val hello: HelloWorldGrpc.HelloWorldBlockingStub = HelloWorldGrpc.newBlockingStub(channel) + val request = HelloRequest.newBuilder().setName("Spring").build() + assertThat(hello.sayHello(request).getMessage()).isEqualTo("Hello 'Spring'") + } finally { + channel.shutdown() + } + } +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt new file mode 100644 index 00000000000..44a56925ac5 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloReply.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.testing.testtransport + +interface HelloReply { + + fun getMessage(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setMessage(message: String): Builder + + fun build(): HelloReply + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt new file mode 100644 index 00000000000..718318c7c95 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloRequest.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.testing.testtransport + +interface HelloRequest { + + fun getName(): String + + companion object { + + fun newBuilder(): Builder { + throw IllegalStateException() + } + + } + + interface Builder { + + fun setName(name: String): Builder + + fun build(): HelloRequest + + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt new file mode 100644 index 00000000000..24fe86509b1 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/HelloWorldGrpc.kt @@ -0,0 +1,19 @@ +package org.springframework.boot.docs.io.grpc.testing.testtransport + +import io.grpc.ManagedChannel + +class HelloWorldGrpc { + + interface HelloWorldBlockingStub { + + fun sayHello(request: HelloRequest): HelloReply + + } + + companion object { + fun newBlockingStub(channel: ManagedChannel): HelloWorldBlockingStub { + throw IllegalStateException() + } + } + +} diff --git a/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt new file mode 100644 index 00000000000..3a89499cc61 --- /dev/null +++ b/documentation/spring-boot-docs/src/main/kotlin/org/springframework/boot/docs/io/grpc/testing/testtransport/MyGrpcTests.kt @@ -0,0 +1,23 @@ +package org.springframework.boot.docs.io.grpc.testing.testtransport + +import org.assertj.core.api.Assertions.assertThat +import org.jooq.DSLContext +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.grpc.test.autoconfigure.AutoConfigureTestGrpcTransport +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.grpc.client.ImportGrpcClients + +@SpringBootTest +@AutoConfigureTestGrpcTransport +@ImportGrpcClients(types = [HelloWorldGrpc.HelloWorldBlockingStub::class]) +class MyGrpcTests(@Autowired val helloStub: HelloWorldGrpc.HelloWorldBlockingStub) { + + @Test + fun sayHello() { + val request = HelloRequest.newBuilder().setName("Spring").build() + val reply = helloStub.sayHello(request) + assertThat(reply.getMessage()).isEqualTo("Hello 'Spring'") + } + +} diff --git a/platform/spring-boot-dependencies/build.gradle b/platform/spring-boot-dependencies/build.gradle index 73cdab167d7..0849893be56 100644 --- a/platform/spring-boot-dependencies/build.gradle +++ b/platform/spring-boot-dependencies/build.gradle @@ -2597,7 +2597,7 @@ bom { github("https://github.com/spring-projects/spring-grpc") javadoc(version -> "https://docs.spring.io/spring-grpc/docs/%s/api" .formatted(version.forMajorMinorGeneration()), "org.springframework.grpc") - docs(version -> "https://docs.spring.io/spring-grpc/docs/%s/reference" + docs(version -> "https://docs.spring.io/spring-grpc/reference/%s" .formatted(version.forMajorMinorGeneration())) releaseNotes("https://github.com/spring-projects/spring-grpc/releases/tag/v{version}") } diff --git a/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle b/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle index b647f93a872..0a79058693f 100644 --- a/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle +++ b/smoke-test/spring-boot-smoke-test-grpc-server-netty-shaded/build.gradle @@ -24,7 +24,7 @@ description = "Spring Boot gRPC server netty shaded smoke test" dependencies { implementation(project(":starter:spring-boot-starter-grpc-server")) { - exclude(group: "io.grpc", module: "grpc-netty-shaded") + exclude(group: "io.grpc", module: "grpc-netty") } implementation("io.grpc:grpc-netty-shaded") diff --git a/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle b/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle index 4d7901ea632..6b71b0d1890 100644 --- a/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle +++ b/smoke-test/spring-boot-smoke-test-grpc-server-servlet/build.gradle @@ -25,7 +25,7 @@ description = "Spring Boot gRPC server servlet smoke test" dependencies { implementation(project(":starter:spring-boot-starter-grpc-server")) { - exclude(group: "io.grpc", module: "grpc-netty-shaded") + exclude(group: "io.grpc", module: "grpc-netty") } implementation(project(":starter:spring-boot-starter-tomcat")) implementation("io.grpc:grpc-servlet-jakarta") diff --git a/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java b/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java index e8d5de4c01e..eabf6ba5ef8 100644 --- a/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java +++ b/smoke-test/spring-boot-smoke-test-grpc-server/src/main/java/smoketest/grpcserver/HelloWorldService.java @@ -23,10 +23,10 @@ import smoketest.grpcserver.proto.HelloReply; import smoketest.grpcserver.proto.HelloRequest; import smoketest.grpcserver.proto.HelloWorldGrpc; -import org.springframework.stereotype.Service; +import org.springframework.grpc.server.service.GrpcService; import org.springframework.util.Assert; -@Service +@GrpcService public class HelloWorldService extends HelloWorldGrpc.HelloWorldImplBase { private static Log logger = LogFactory.getLog(HelloWorldService.class);