Skip to content

Commit

Permalink
fixes #342, closes #3 and #163
Browse files Browse the repository at this point in the history
  • Loading branch information
jvmlet committed Apr 11, 2023
1 parent 509d60a commit 0ef79fe
Show file tree
Hide file tree
Showing 13 changed files with 378 additions and 42 deletions.
18 changes: 18 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,24 @@ This is the current limitation.
== Events
`GRpcServerInitializedEvent` is published upon server startup, you can consume it using regular spring API.

== Reactive API support

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]
integrates SalesForce's https://github.com/salesforce/reactive-grpc[reactive-grpc] protoc plugin :

[source,groovy]
----
import org.lognet.springboot.grpc.gradle.ReactiveFeature
plugins {
id "io.github.lognet.grpc-spring-boot"
}
grpcSpringBoot {
reactiveFeature.set(ReactiveFeature.REACTOR) // or ReactiveFeature.RX
}
----

Here are the tests and reactive grpc sample service.

== Error handling

The starter registers the `GRpcExceptionHandlerInterceptor` which is responsible to propagate the service-thrown exception to the error handlers. +
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ gradleErrorPronePluginVersion=3.0.1
errorProneVersion=2.16
lombokVersion=1.18.24

version=5.0.1-SNAPSHOT
version=5.1.0-SNAPSHOT
group=io.github.lognet
description=Spring Boot starter for Google RPC.
gitHubUrl=https\://github.com/LogNet/grpc-spring-boot-starter
Expand Down
3 changes: 3 additions & 0 deletions grpc-spring-boot-starter-demo/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.lognet.springboot.grpc.gradle.ReactiveFeature

buildscript {
repositories {
mavenCentral()
Expand Down Expand Up @@ -27,6 +29,7 @@ facets {

grpcSpringBoot {
grpcSpringBootStarterVersion.set((String) null)
reactiveFeature.set(ReactiveFeature.REACTOR)
}
dependencyManagement {
imports {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.lognet.springboot.grpc.demo;

import io.grpc.Status;
import io.grpc.examples.reactor.ReactiveHelloRequest;
import io.grpc.examples.reactor.ReactiveHelloResponse;
import io.grpc.examples.reactor.ReactorReactiveGreeterGrpc;
import lombok.extern.slf4j.Slf4j;
import org.lognet.springboot.grpc.GRpcService;
import org.lognet.springboot.grpc.recovery.GRpcExceptionHandler;
import org.lognet.springboot.grpc.recovery.GRpcExceptionScope;
import org.springframework.beans.factory.annotation.Autowired;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.stream.IntStream;


@GRpcService
@Slf4j
public class ReactiveGreeterGrpcService extends ReactorReactiveGreeterGrpc.ReactiveGreeterImplBase {

private ReactiveGreeterService reactiveGreeterService;

public ReactiveGreeterGrpcService(ReactiveGreeterService reactiveGreeterService) {
this.reactiveGreeterService = reactiveGreeterService;
}

@Override
public Mono<ReactiveHelloResponse> greet(Mono<ReactiveHelloRequest> request) {
return reactiveGreeterService.greet(request);

}

@Override
public Flux<ReactiveHelloResponse> multiGreet(Mono<ReactiveHelloRequest> request) {
return request.flatMapIterable(r ->
IntStream.range(0, r.getName().length())
.mapToObj(i -> ReactiveHelloResponse.newBuilder()
.setMessage(String.format("Hello %d,%s ", i, r.getName()))
.build())
.toList()
);
}

@Override
public Flux<ReactiveHelloResponse> streamGreet(Flux<ReactiveHelloRequest> request) {
return request.flatMap(r -> Mono.just(
ReactiveHelloResponse.newBuilder()
.setMessage(String.format("Hello ,%s ", r.getName()))
.build()
)
);
}

@GRpcExceptionHandler
public Status handle(Exception ex, GRpcExceptionScope scope) {
var status = Status.INVALID_ARGUMENT.withDescription(ex.getLocalizedMessage()).withCause(ex);
log.error("(GrpcExceptionAdvice) : ", ex);
return status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.lognet.springboot.grpc.demo;

import io.grpc.examples.reactor.ReactiveHelloRequest;
import io.grpc.examples.reactor.ReactiveHelloResponse;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

@Service
public class ReactiveGreeterService {
public Mono<ReactiveHelloResponse> greet(Mono<ReactiveHelloRequest> request) {
return Mono
.from(request)
.flatMap(r -> {
if ("wolf".equalsIgnoreCase(r.getName())) {
return Mono.error(new Exception("Wolf is not welcome!"));
}
return Mono.just(ReactiveHelloResponse.newBuilder()
.setMessage("Hello " + r.getName())
.build());
});
}
}
4 changes: 2 additions & 2 deletions grpc-spring-boot-starter-demo/src/main/proto/greeter.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ service SecuredGreeter {
rpc SayAuthHello ( google.protobuf.Empty) returns ( HelloReply) {}
rpc SayAuthHello2 ( google.protobuf.Empty) returns ( HelloReply) {}

rpc secured_methods_with_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
rpc AnotherSecured_methods_with_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
rpc secured_Methods_With_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}
rpc AnotherSecured_Methods_With_UnderScoRes(google.protobuf.Empty) returns (google.protobuf.Empty){}


}
Expand Down
26 changes: 26 additions & 0 deletions grpc-spring-boot-starter-demo/src/main/proto/reactiveHello.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
syntax = "proto3";


option java_multiple_files = true;
option java_package = "io.grpc.examples.reactor";
option java_outer_classname = "ReactiveHelloWorldProto";

/*
* Define the service's operations
*/
service ReactiveGreeter {
rpc Greet (ReactiveHelloRequest) returns (ReactiveHelloResponse) {}
rpc MultiGreet (ReactiveHelloRequest) returns (stream ReactiveHelloResponse) {}
rpc StreamGreet (stream ReactiveHelloRequest) returns (stream ReactiveHelloResponse) {}
}

/*
* Define the service's data structures
*/
message ReactiveHelloRequest {
string name = 1;
}

message ReactiveHelloResponse {
string message = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package org.lognet.springboot.grpc.reactive;

import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.grpc.examples.reactor.ReactiveGreeterGrpc;
import io.grpc.examples.reactor.ReactiveHelloRequest;
import io.grpc.examples.reactor.ReactiveHelloResponse;
import io.grpc.examples.reactor.ReactorReactiveGreeterGrpc;
import lombok.extern.slf4j.Slf4j;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.collection.IsIn;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.lognet.springboot.grpc.GrpcServerTestBase;
import org.lognet.springboot.grpc.demo.DemoApp;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.hamcrest.Matchers;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThrows;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;


@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApp.class, webEnvironment = NONE)
@ActiveProfiles("disable-security")
public class ReactiveDemoTest extends GrpcServerTestBase {
@Test
public void grpcGreetTest() {
String shrek = "Shrek";
String message = ReactiveGreeterGrpc.newBlockingStub(channel)
.greet(ReactiveHelloRequest.newBuilder().setName(shrek).build())
.getMessage();
assertThat(message, containsString(shrek));

}

@Test
public void reactorGreetTest() {
String shrek = "Shrek";
ReactiveHelloResponse helloResponse = ReactorReactiveGreeterGrpc.newReactorStub(channel)
.greet(simpleRequest(shrek))
.block(Duration.ofSeconds(10));
assertThat(helloResponse, notNullValue());

assertThat(helloResponse.getMessage(), containsString(shrek));

}

@Test
public void reactorGreetFailureTest() {
String shrek = "Wolf";
StatusRuntimeException e = assertThrows(StatusRuntimeException.class, () -> {

ReactorReactiveGreeterGrpc.newReactorStub(channel)
.greet(simpleRequest(shrek))
.block(Duration.ofSeconds(10));
});
assertThat(e.getMessage(), containsStringIgnoringCase("not welcome"));
assertThat(e.getStatus().getCode(), is(Status.INVALID_ARGUMENT.getCode()));


}

@Test
public void reactorMultiGreerTest() {
String shrek = "Shrek";
List<ReactiveHelloResponse> greets = ReactorReactiveGreeterGrpc.newReactorStub(channel)
.multiGreet(simpleRequest(shrek))
.collectList()
.block(Duration.ofSeconds(10));

assertThat(greets, notNullValue());
assertThat(greets, hasSize(shrek.length()));

assertThat(greets.stream().map(ReactiveHelloResponse::getMessage).toList(), everyItem(containsString(shrek)));

}

@Test
public void reactorBidiGreerTest() {
String[] names = new String[]{
"Shrek",
"Fiona",
"Robin",
"Christopher"
};
List<ReactiveHelloResponse> greets = ReactorReactiveGreeterGrpc.newReactorStub(channel)
.streamGreet(
Flux.fromStream(Arrays.stream(names).map(this::simpleRequest))
)
.collectList()
.block(Duration.ofSeconds(10));

assertThat(greets, notNullValue());
assertThat(greets, hasSize(name.length()));

assertThat(greets.stream().map(ReactiveHelloResponse::getMessage).toList(),
Matchers.everyItem(new IsIn<>(names) {
@Override
public boolean matches(Object actual) {
return Arrays.stream(names)
.anyMatch(a -> actual.toString().contains(a));
}
})
);

}

private ReactiveHelloRequest simpleRequest(String name) {
return ReactiveHelloRequest.newBuilder().setName(name).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class GrpcSpringBootExtension {
private final Property<String> grpcVersion;
private final Property<String> grpcSpringBootStarterVersion;
private final Property<String> protocVersion;
private final Property<String> reactiveProtocVersion;
private final Property<ReactiveFeature> reactiveFeature;

public GrpcSpringBootExtension(Project project) {
this.project = project;
Expand All @@ -21,6 +23,12 @@ public GrpcSpringBootExtension(Project project) {
protocVersion = this.project.getObjects().property(String.class);
protocVersion.set("3.21.7");

reactiveProtocVersion = this.project.getObjects().property(String.class);
reactiveProtocVersion.set("1.2.3");

reactiveFeature = this.project.getObjects().property(ReactiveFeature.class);
reactiveFeature.set(ReactiveFeature.OFF);


}

Expand All @@ -35,4 +43,12 @@ public Property<String> getGrpcSpringBootStarterVersion() {
public Property<String> getProtocVersion() {
return protocVersion;
}

public Property<String> getReactiveProtocVersion() {
return reactiveProtocVersion;
}

public Property<ReactiveFeature> getReactiveFeature() {
return reactiveFeature;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.lognet.springboot.grpc.gradle;

public enum ReactiveFeature {


OFF,
REACTOR,
RX

}
Loading

0 comments on commit 0ef79fe

Please sign in to comment.