Gumdrop provides gRPC support built on top of its HTTP
client and server. gRPC runs over HTTP/2 (or HTTP/3) with
Content-Type: application/grpc. The implementation uses an
event-oriented, schema-driven approach with no generated code and no
com.google.protobuf dependency.
Gumdrop’s gRPC support lives in org.bluezoo.gumdrop.grpc:
GrpcClient composes the HTTP client,
adds gRPC framing around request/response bodiesGrpcHandlerFactory implements
HTTPRequestHandlerFactory, routes gRPC paths to GrpcHandlerProtoMessageHandler,
ProtoModelAdapter, ProtoModelSerializer
The client uses the HTTP client as-is (composition, not extension). The server
integrates via the existing HTTPRequestHandlerFactory pattern.
Both HTTP/2 and HTTP/3 are supported.
Before using gRPC, you need a Proto model from your .proto file. Use
ProtoFileParser.parse(CharSequence) to parse a .proto definition:
import org.bluezoo.gumdrop.grpc.proto.*;
String protoSource = ""
+ "syntax = \"proto3\";\n"
+ "package example.v1;\n"
+ "message GetUserRequest {\n"
+ " int32 user_id = 1;\n"
+ "}\n"
+ "message GetUserResponse {\n"
+ " string name = 1;\n"
+ " string email = 2;\n"
+ "}\n"
+ "service UserService {\n"
+ " rpc GetUser(GetUserRequest) returns (GetUserResponse);\n"
+ "}\n";
ProtoFile protoFile = ProtoFileParser.parse(protoSource);
For loading from a file, use the push-style API:
ProtoFileParser parser = new ProtoFileParser();
try (FileChannel channel = FileChannel.open(Paths.get("service.proto"))) {
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (channel.read(buffer) > 0) {
buffer.flip();
parser.receive(buffer);
buffer.compact();
}
}
ProtoFile protoFile = parser.close();
The Proto model provides:
getMessage(String fullName) – lookup message by fully qualified namegetService(String fullName) – lookup servicegetRpcPath(String rpcName) – get gRPC path like /example.v1.UserService/GetUser
Gumdrop does not use generated protobuf classes. Instead, it uses an
event-oriented API similar to JSONContentHandler and
MIMEHandler. Implement ProtoMessageHandler to receive
semantic events:
public interface ProtoMessageHandler {
void setLocator(ProtoLocator locator);
void startMessage(String typeName) throws ProtoParseException;
void endMessage() throws ProtoParseException;
void field(String name, Object value) throws ProtoParseException;
void startField(String name, String typeName) throws ProtoParseException;
void endField() throws ProtoParseException;
}
For a message like GetUserResponse { name = "Alice"; email = "alice@example.com" },
the event sequence is:
startMessage("example.v1.GetUserResponse")
field("name", "Alice")
field("email", "alice@example.com")
endMessage()
Use ProtoDefaultHandler as a base if you only need to override
some methods.
Implement GrpcService to handle RPC calls:
import org.bluezoo.gumdrop.grpc.server.*;
import java.nio.ByteBuffer;
public class UserGrpcService implements GrpcService {
@Override
public void unaryCall(String path, ByteBuffer request, GrpcResponseSender response) {
if ("/example.v1.UserService/GetUser".equals(path)) {
// Parse request, build response, send
ByteBuffer responseMessage = handleGetUser(request);
response.send(responseMessage);
} else {
response.sendError(12, "Unimplemented"); // UNIMPLEMENTED
}
}
}
GrpcResponseSender provides:
send(ByteBuffer message) – send success response (message is raw protobuf, not framed)sendError(int status, String message) – send gRPC error (status codes: 0=OK, 1=CANCELLED, 12=UNIMPLEMENTED, 13=INTERNAL, etc.)sendError(Throwable cause) – send INTERNAL error
Create a GrpcHandlerFactory and wire it to your HTTP service.
The factory routes requests where :path matches
/package.Service/Method and content-type is
application/grpc:
ProtoFile protoFile = ProtoFileParser.parse(protoSource); GrpcService service = new UserGrpcService(); GrpcHandlerFactory grpcFactory = new GrpcHandlerFactory(protoFile, service); // Set on your HTTP service (see Integration below)
The gRPC client composes the HTTP client. Create
an HTTPClient, connect, then use GrpcClient for
unary calls:
import org.bluezoo.gumdrop.grpc.client.*;
import org.bluezoo.gumdrop.http.client.*;
HTTPClient httpClient = new HTTPClient("localhost", 50051);
httpClient.setSecure(true); // TLS for production
ProtoFile protoFile = ProtoFileParser.parse(protoSource);
GrpcClient grpcClient = new GrpcClient(protoFile);
httpClient.connect(new HTTPClientHandler() {
public void onConnected(Endpoint endpoint) {
// Serialize request (see Serialization below)
ByteBuffer requestMessage = ...;
grpcClient.unaryCall(httpClient, "/example.v1.UserService/GetUser",
requestMessage, new GrpcResponseHandler() {
public void onMessage(ByteBuffer message) {
// Parse response message
}
public void onError(Exception e) {
e.printStackTrace();
}
});
}
public void onSecurityEstablished(SecurityInfo info) { }
public void onError(Exception cause) { cause.printStackTrace(); }
public void onDisconnected() { }
});
For high-level parsing, use the overload that takes a
ProtoMessageHandler:
grpcClient.unaryCall(httpClient, "/example.v1.UserService/GetUser",
requestMessage, "example.v1.GetUserResponse", new ProtoMessageHandler() {
public void setLocator(ProtoLocator locator) { }
public void startMessage(String typeName) { }
public void endMessage() { }
public void field(String name, Object value) {
if ("name".equals(name)) {
System.out.println("User: " + value);
}
}
public void startField(String name, String typeName) { }
public void endField() { }
});
The HTTP client negotiates HTTP/2 via ALPN when setSecure(true)
is used (HTTP/2 is the default for TLS). For HTTP/3, call
httpClient.setH3Enabled(true) before connecting.
To serialize a request or parse a response, use the Proto model with
ProtobufWriter (wire format) and ProtoModelAdapter /
ProtoModelSerializer (schema-driven).
ProtoModelSerializer writes protobuf from events. Drive it with
startMessage, field, endMessage:
import org.bluezoo.gumdrop.grpc.proto.*; import org.bluezoo.gumdrop.telemetry.protobuf.ProtobufWriter; ByteArrayOutputStream out = new ByteArrayOutputStream(); ProtobufWriter writer = new ProtobufWriter(out); ProtoModelSerializer serializer = new ProtoModelSerializer(protoFile); serializer.startMessage(writer, "example.v1.GetUserRequest"); serializer.field(writer, "user_id", 42); serializer.endMessage(); ByteBuffer requestMessage = ByteBuffer.wrap(out.toByteArray());
ProtoModelAdapter bridges the low-level ProtobufParser
to your ProtoMessageHandler. The gRPC client uses this internally
when you pass a ProtoMessageHandler. For raw ByteBuffer
responses, parse manually:
import org.bluezoo.gumdrop.grpc.proto.*;
import org.bluezoo.gumdrop.telemetry.protobuf.ProtobufParser;
ProtoModelAdapter adapter = new ProtoModelAdapter(protoFile, myHandler);
ProtobufParser parser = new ProtobufParser(adapter);
adapter.startRootMessage("example.v1.GetUserResponse");
parser.receive(messageBuffer);
parser.close();
adapter.endRootMessage();
An HTTPService has a single HTTPRequestHandlerFactory.
To serve both gRPC and other content (e.g. servlets, static files), use a
composite factory that delegates to GrpcHandlerFactory first:
public class CompositeHandlerFactory implements HTTPRequestHandlerFactory {
private final GrpcHandlerFactory grpcFactory;
private final HTTPRequestHandlerFactory fallback;
public CompositeHandlerFactory(ProtoFile protoFile, GrpcService grpcService,
HTTPRequestHandlerFactory fallback) {
this.grpcFactory = new GrpcHandlerFactory(protoFile, grpcService);
this.fallback = fallback;
}
@Override
public HTTPRequestHandler createHandler(HTTPResponseState state, Headers headers) {
HTTPRequestHandler handler = grpcFactory.createHandler(state, headers);
if (handler != null) {
return handler;
}
return fallback != null ? fallback.createHandler(state, headers) : null;
}
@Override
public Set<String> getSupportedMethods() {
return null; // use defaults (includes POST for gRPC)
}
}
GrpcHandlerFactory.createHandler returns null when
the path or content-type does not match gRPC, so the fallback factory handles
other requests.
For a gRPC-only service, set the factory directly:
HTTPService service = new HTTPService() {
protected HTTPRequestHandlerFactory getHandlerFactory() {
return new GrpcHandlerFactory(protoFile, grpcService);
}
};
HTTPListener listener = new HTTPListener();
listener.setPort(50051);
listener.setSecure(true);
listener.setKeystoreFile("keystore.p12");
listener.setKeystorePass("changeit");
service.addListener(listener);
service.start();
gRPC requires HTTP/2. The HTTP listener negotiates HTTP/2 via ALPN when TLS is enabled. Ensure your keystore and TLS configuration are correct.
← Back to Main Page | HTTP Server & Client | Building Microservices | Telemetry
Gumdrop gRPC