I am using the new Springboot starter from org.springframework.boot
(more details below) and I am able to use it to make a simple GRPC API to return Hello world. Now I was trying to upgrade the same program to support streaming HTTP 2 request but when I try to build my project which eventually autogenerates the models is not building the Request models.
Code exerpts (shortened code wherever I can, code is bootstrapped from Spring intializer.)
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.4'
id 'io.spring.dependency-management' version '1.1.7'
id 'com.google.protobuf' version '0.9.4'
}
repositories {
mavenCentral()
}
ext {
set('springGrpcVersion', "0.4.0")
}
dependencies {
implementation 'io.grpc:grpc-services'
implementation 'org.springframework.grpc:spring-grpc-spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.grpc:spring-grpc-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'io.grpc:grpc-netty-shaded'
modules {
module("io.grpc:grpc-netty") {
replacedBy("io.grpc:grpc-netty-shaded", "Use Netty shaded instead of regular Netty")
}
}
}
dependencyManagement {
imports {
mavenBom "org.springframework.grpc:spring-grpc-dependencies:${springGrpcVersion}"
}
}
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc'
}
plugins {
grpc {
artifact = 'io.grpc:protoc-gen-grpc-java'
}
}
generateProtoTasks {
all()*.plugins {
grpc {
option 'jakarta_omit'
option '@generated=omit'
}
}
}
}
// ...
Hello World.proto
syntax = "proto3";
option java_package = "com.asr.example.grpc.demo.model";
option java_multiple_files = true;
message HelloWorldRequest {
optional string name = 1;
}
message HelloWorldResponse {
string greeting = 1;
}
service HelloWorldService {
rpc sayHello(stream HelloWorldRequest) returns (stream HelloWorldResponse);
}
HelloWorldController.java
import com.asr.example.grpc.demo.model.HelloWorldRequest;
import com.asr.example.grpc.demo.model.HelloWorldResponse;
import com.asr.example.grpc.demo.model.HelloWorldServiceGrpc;
import io.grpc.stub.StreamObserver;
import org.springframework.grpc.server.service.GrpcService;
import org.springframework.util.StringUtils;
import java.text.MessageFormat;
@GrpcService
public class HelloWorldController extends HelloWorldServiceGrpc.HelloWorldServiceImplBase {
@Override
public StreamObserver<HelloWorldRequest> sayHello(StreamObserver<HelloWorldResponse> responseObserver) {
return super.sayHello(responseObserver);
}
/* Expected:
public StreamObserver<HelloWorldResponse> sayHello(HelloWorldRequest request, StreamObserver<HelloWorldResponse> responseObserver) {
// Further Implementation
}
*/
// Original without streaming
// @Override
public void sayHello(
HelloWorldRequest request,
StreamObserver<HelloWorldResponse> responseObserver) {
responseObserver
.onNext(HelloWorldResponse.newBuilder()
.setGreeting(MessageFormat.format(
"Hello {0}!!!",
StringUtils.hasText(request.getName()) ? request.getName() : "Arvind")
)
.build()
);
responseObserver
.onCompleted();
}
}
Code explanation:
stream
keyword.stream
keyword as prefix in HelloWorld request in Protobuf spec.Questions:
io.github.lognet
and net.devh
, are any of these 2 viable alternatives for LTS ?Hey @Kannan J and @Vy Do
Thanks for your answers, but for clarifications for newbies like me. Getting into more details.
Yes, as @Kannan J mentioned, the created stub for HelloWorldService.sayHello
is expected as it is mentioned in question. The parameter for HelloWorldResponse can be considered as a Consumer of the response (Drawing analogy from Java 8's Consumer methods) which needs to be consuming the response which needs to be sent back to the client. Whereas, Request is also a consumer (again, the same Java 8's Consumer method's analogy) here will be called once a HelloWorldRequest
is received, so we need to define what needs to be done by implementing and returning that.
Here's the sample implementation of the same:
@Override
public StreamObserver<HelloWorldRequest> sayHello(StreamObserver<HelloWorldResponse> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(HelloWorldRequest helloWorldRequest) {
String name = helloWorldRequest.getName();
String greeting = null;
if (StringUtils.hasText(name)) {
if (!name.startsWith("Hello"))
greeting = "Hello " + name;
} else {
greeting = "Hello World";
}
if (StringUtils.hasText(name)) {
HelloWorldResponse response = HelloWorldResponse.newBuilder()
.setGreeting(greeting)
.build();
responseObserver.onNext(response);
}
}
@Override
public void onError(Throwable throwable) {
// Handle error
log.error("Error occurred: {}", throwable.getMessage(), throwable);
responseObserver.onError(throwable);
}
@Override
public void onCompleted() {
// Complete the response
log.info("Request completed");
HelloWorldResponse response = HelloWorldResponse.newBuilder()
.setGreeting("Quitting chat , Thank you")
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
};
}
Here, I am creating a new StreamObserver for the request , which tells what to do with the incoming messages (since, it is a stream there could be more than one, like a P2P chat). onNext
tells what to do when a message is received, which can be used using the parameter provided for the same. onError
when something breaks, and finally onCompleted
when a streaming connection is closed. Within these methods responseObserver
for sending messages (or emitting messages, analogy from Reactor streams) back to the client.