Skip to content

Commit 0ef79fe

Browse files
committed
fixes #342, closes #3 and #163
1 parent 509d60a commit 0ef79fe

File tree

13 files changed

+378
-42
lines changed

13 files changed

+378
-42
lines changed

README.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,24 @@ This is the current limitation.
456456
== Events
457457
`GRpcServerInitializedEvent` is published upon server startup, you can consume it using regular spring API.
458458

459+
== Reactive API support
460+
461+
Starting from version `5.1.0`, https://github.com/LogNet/grpc-spring-boot-starter/tree/master/grpc-spring-boot-starter-gradle-plugin[spring-boot-starter-gradle-plugin]
462+
integrates SalesForce's https://github.com/salesforce/reactive-grpc[reactive-grpc] protoc plugin :
463+
464+
[source,groovy]
465+
----
466+
import org.lognet.springboot.grpc.gradle.ReactiveFeature
467+
plugins {
468+
id "io.github.lognet.grpc-spring-boot"
469+
}
470+
grpcSpringBoot {
471+
reactiveFeature.set(ReactiveFeature.REACTOR) // or ReactiveFeature.RX
472+
}
473+
----
474+
475+
Here are the tests and reactive grpc sample service.
476+
459477
== Error handling
460478

461479
The starter registers the `GRpcExceptionHandlerInterceptor` which is responsible to propagate the service-thrown exception to the error handlers. +

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ gradleErrorPronePluginVersion=3.0.1
88
errorProneVersion=2.16
99
lombokVersion=1.18.24
1010

11-
version=5.0.1-SNAPSHOT
11+
version=5.1.0-SNAPSHOT
1212
group=io.github.lognet
1313
description=Spring Boot starter for Google RPC.
1414
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter

grpc-spring-boot-starter-demo/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.lognet.springboot.grpc.gradle.ReactiveFeature
2+
13
buildscript {
24
repositories {
35
mavenCentral()
@@ -27,6 +29,7 @@ facets {
2729

2830
grpcSpringBoot {
2931
grpcSpringBootStarterVersion.set((String) null)
32+
reactiveFeature.set(ReactiveFeature.REACTOR)
3033
}
3134
dependencyManagement {
3235
imports {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.lognet.springboot.grpc.demo;
2+
3+
import io.grpc.Status;
4+
import io.grpc.examples.reactor.ReactiveHelloRequest;
5+
import io.grpc.examples.reactor.ReactiveHelloResponse;
6+
import io.grpc.examples.reactor.ReactorReactiveGreeterGrpc;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.lognet.springboot.grpc.GRpcService;
9+
import org.lognet.springboot.grpc.recovery.GRpcExceptionHandler;
10+
import org.lognet.springboot.grpc.recovery.GRpcExceptionScope;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import reactor.core.publisher.Flux;
13+
import reactor.core.publisher.Mono;
14+
15+
import java.util.stream.IntStream;
16+
17+
18+
@GRpcService
19+
@Slf4j
20+
public class ReactiveGreeterGrpcService extends ReactorReactiveGreeterGrpc.ReactiveGreeterImplBase {
21+
22+
private ReactiveGreeterService reactiveGreeterService;
23+
24+
public ReactiveGreeterGrpcService(ReactiveGreeterService reactiveGreeterService) {
25+
this.reactiveGreeterService = reactiveGreeterService;
26+
}
27+
28+
@Override
29+
public Mono<ReactiveHelloResponse> greet(Mono<ReactiveHelloRequest> request) {
30+
return reactiveGreeterService.greet(request);
31+
32+
}
33+
34+
@Override
35+
public Flux<ReactiveHelloResponse> multiGreet(Mono<ReactiveHelloRequest> request) {
36+
return request.flatMapIterable(r ->
37+
IntStream.range(0, r.getName().length())
38+
.mapToObj(i -> ReactiveHelloResponse.newBuilder()
39+
.setMessage(String.format("Hello %d,%s ", i, r.getName()))
40+
.build())
41+
.toList()
42+
);
43+
}
44+
45+
@Override
46+
public Flux<ReactiveHelloResponse> streamGreet(Flux<ReactiveHelloRequest> request) {
47+
return request.flatMap(r -> Mono.just(
48+
ReactiveHelloResponse.newBuilder()
49+
.setMessage(String.format("Hello ,%s ", r.getName()))
50+
.build()
51+
)
52+
);
53+
}
54+
55+
@GRpcExceptionHandler
56+
public Status handle(Exception ex, GRpcExceptionScope scope) {
57+
var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
58+
log.error("(GrpcExceptionAdvice) : ", ex);
59+
return status;
60+
}
61+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.lognet.springboot.grpc.demo;
2+
3+
import io.grpc.examples.reactor.ReactiveHelloRequest;
4+
import io.grpc.examples.reactor.ReactiveHelloResponse;
5+
import org.springframework.stereotype.Service;
6+
import reactor.core.publisher.Mono;
7+
8+
@Service
9+
public class ReactiveGreeterService {
10+
public Mono<ReactiveHelloResponse> greet(Mono<ReactiveHelloRequest> request) {
11+
return Mono
12+
.from(request)
13+
.flatMap(r -> {
14+
if ("wolf".equalsIgnoreCase(r.getName())) {
15+
return Mono.error(new Exception("Wolf is not welcome!"));
16+
}
17+
return Mono.just(ReactiveHelloResponse.newBuilder()
18+
.setMessage("Hello " + r.getName())
19+
.build());
20+
});
21+
}
22+
}

grpc-spring-boot-starter-demo/src/main/proto/greeter.proto

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ service SecuredGreeter {
1919
rpc SayAuthHello ( google.protobuf.Empty) returns ( HelloReply) {}
2020
rpc SayAuthHello2 ( google.protobuf.Empty) returns ( HelloReply) {}
2121

22-
rpc secured_methods_with_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
23-
rpc AnotherSecured_methods_with_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
22+
rpc secured_Methods_With_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
23+
rpc AnotherSecured_Methods_With_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
2424

2525

2626
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
syntax = "proto3";
2+
3+
4+
option java_multiple_files = true;
5+
option java_package = "io.grpc.examples.reactor";
6+
option java_outer_classname = "ReactiveHelloWorldProto";
7+
8+
/*
9+
* Define the service's operations
10+
*/
11+
service ReactiveGreeter {
12+
rpc Greet (ReactiveHelloRequest) returns (ReactiveHelloResponse) {}
13+
rpc MultiGreet (ReactiveHelloRequest) returns (stream ReactiveHelloResponse) {}
14+
rpc StreamGreet (stream ReactiveHelloRequest) returns (stream ReactiveHelloResponse) {}
15+
}
16+
17+
/*
18+
* Define the service's data structures
19+
*/
20+
message ReactiveHelloRequest {
21+
string name = 1;
22+
}
23+
24+
message ReactiveHelloResponse {
25+
string message = 1;
26+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package org.lognet.springboot.grpc.reactive;
2+
3+
import io.grpc.Status;
4+
import io.grpc.StatusRuntimeException;
5+
import io.grpc.examples.reactor.ReactiveGreeterGrpc;
6+
import io.grpc.examples.reactor.ReactiveHelloRequest;
7+
import io.grpc.examples.reactor.ReactiveHelloResponse;
8+
import io.grpc.examples.reactor.ReactorReactiveGreeterGrpc;
9+
import lombok.extern.slf4j.Slf4j;
10+
import org.hamcrest.BaseMatcher;
11+
import org.hamcrest.Description;
12+
import org.hamcrest.collection.IsIn;
13+
import org.junit.Test;
14+
import org.junit.runner.RunWith;
15+
import org.lognet.springboot.grpc.GrpcServerTestBase;
16+
import org.lognet.springboot.grpc.demo.DemoApp;
17+
import org.springframework.boot.test.context.SpringBootTest;
18+
import org.springframework.test.context.ActiveProfiles;
19+
import org.springframework.test.context.junit4.SpringRunner;
20+
import org.hamcrest.Matchers;
21+
import reactor.core.publisher.Flux;
22+
import reactor.core.publisher.Mono;
23+
24+
import java.time.Duration;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import java.util.concurrent.ExecutionException;
28+
29+
import static org.hamcrest.MatcherAssert.assertThat;
30+
import static org.hamcrest.Matchers.*;
31+
import static org.junit.Assert.assertThrows;
32+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
33+
34+
35+
@Slf4j
36+
@RunWith(SpringRunner.class)
37+
@SpringBootTest(classes = DemoApp.class, webEnvironment = NONE)
38+
@ActiveProfiles("disable-security")
39+
public class ReactiveDemoTest extends GrpcServerTestBase {
40+
@Test
41+
public void grpcGreetTest() {
42+
String shrek = "Shrek";
43+
String message = ReactiveGreeterGrpc.newBlockingStub(channel)
44+
.greet(ReactiveHelloRequest.newBuilder().setName(shrek).build())
45+
.getMessage();
46+
assertThat(message, containsString(shrek));
47+
48+
}
49+
50+
@Test
51+
public void reactorGreetTest() {
52+
String shrek = "Shrek";
53+
ReactiveHelloResponse helloResponse = ReactorReactiveGreeterGrpc.newReactorStub(channel)
54+
.greet(simpleRequest(shrek))
55+
.block(Duration.ofSeconds(10));
56+
assertThat(helloResponse, notNullValue());
57+
58+
assertThat(helloResponse.getMessage(), containsString(shrek));
59+
60+
}
61+
62+
@Test
63+
public void reactorGreetFailureTest() {
64+
String shrek = "Wolf";
65+
StatusRuntimeException e = assertThrows(StatusRuntimeException.class, () -> {
66+
67+
ReactorReactiveGreeterGrpc.newReactorStub(channel)
68+
.greet(simpleRequest(shrek))
69+
.block(Duration.ofSeconds(10));
70+
});
71+
assertThat(e.getMessage(), containsStringIgnoringCase("not welcome"));
72+
assertThat(e.getStatus().getCode(), is(Status.INVALID_ARGUMENT.getCode()));
73+
74+
75+
}
76+
77+
@Test
78+
public void reactorMultiGreerTest() {
79+
String shrek = "Shrek";
80+
List<ReactiveHelloResponse> greets = ReactorReactiveGreeterGrpc.newReactorStub(channel)
81+
.multiGreet(simpleRequest(shrek))
82+
.collectList()
83+
.block(Duration.ofSeconds(10));
84+
85+
assertThat(greets, notNullValue());
86+
assertThat(greets, hasSize(shrek.length()));
87+
88+
assertThat(greets.stream().map(ReactiveHelloResponse::getMessage).toList(), everyItem(containsString(shrek)));
89+
90+
}
91+
92+
@Test
93+
public void reactorBidiGreerTest() {
94+
String[] names = new String[]{
95+
"Shrek",
96+
"Fiona",
97+
"Robin",
98+
"Christopher"
99+
};
100+
List<ReactiveHelloResponse> greets = ReactorReactiveGreeterGrpc.newReactorStub(channel)
101+
.streamGreet(
102+
Flux.fromStream(Arrays.stream(names).map(this::simpleRequest))
103+
)
104+
.collectList()
105+
.block(Duration.ofSeconds(10));
106+
107+
assertThat(greets, notNullValue());
108+
assertThat(greets, hasSize(name.length()));
109+
110+
assertThat(greets.stream().map(ReactiveHelloResponse::getMessage).toList(),
111+
Matchers.everyItem(new IsIn<>(names) {
112+
@Override
113+
public boolean matches(Object actual) {
114+
return Arrays.stream(names)
115+
.anyMatch(a -> actual.toString().contains(a));
116+
}
117+
})
118+
);
119+
120+
}
121+
122+
private ReactiveHelloRequest simpleRequest(String name) {
123+
return ReactiveHelloRequest.newBuilder().setName(name).build();
124+
}
125+
}

grpc-spring-boot-starter-gradle-plugin/src/main/java/org/lognet/springboot/grpc/gradle/GrpcSpringBootExtension.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ public class GrpcSpringBootExtension {
88
private final Property<String> grpcVersion;
99
private final Property<String> grpcSpringBootStarterVersion;
1010
private final Property<String> protocVersion;
11+
private final Property<String> reactiveProtocVersion;
12+
private final Property<ReactiveFeature> reactiveFeature;
1113

1214
public GrpcSpringBootExtension(Project project) {
1315
this.project = project;
@@ -21,6 +23,12 @@ public GrpcSpringBootExtension(Project project) {
2123
protocVersion = this.project.getObjects().property(String.class);
2224
protocVersion.set("3.21.7");
2325

26+
reactiveProtocVersion = this.project.getObjects().property(String.class);
27+
reactiveProtocVersion.set("1.2.3");
28+
29+
reactiveFeature = this.project.getObjects().property(ReactiveFeature.class);
30+
reactiveFeature.set(ReactiveFeature.OFF);
31+
2432

2533
}
2634

@@ -35,4 +43,12 @@ public Property<String> getGrpcSpringBootStarterVersion() {
3543
public Property<String> getProtocVersion() {
3644
return protocVersion;
3745
}
46+
47+
public Property<String> getReactiveProtocVersion() {
48+
return reactiveProtocVersion;
49+
}
50+
51+
public Property<ReactiveFeature> getReactiveFeature() {
52+
return reactiveFeature;
53+
}
3854
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.lognet.springboot.grpc.gradle;
2+
3+
public enum ReactiveFeature {
4+
5+
6+
OFF,
7+
REACTOR,
8+
RX
9+
10+
}

0 commit comments

Comments
 (0)