Gumdrop

Gumdrop gRPC Client & Server

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.

Contents

Overview

Gumdrop’s gRPC support lives in org.bluezoo.gumdrop.grpc:

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.

Proto Model and .proto Parser

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:

Event-Oriented Message Handling

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.

gRPC Server

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:

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)

gRPC Client

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.

Serialization and Parsing

To serialize a request or parse a response, use the Proto model with ProtobufWriter (wire format) and ProtoModelAdapter / ProtoModelSerializer (schema-driven).

Serializing (request)

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());

Parsing (response)

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();

Integration with HTTP Services

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.

Standalone gRPC Service

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