gRPC

Crate hopf-grpc: unary gRPC over hopf-http Streams. Uses a runtime .proto model (rprotobuf) — no generated stubs or codegen. Works wherever Hopf Streams run (HTTP/2 preferred; H1/H3 inherit Stream framing, with H1 trailer caveats).

#![forbid(unsafe_code)].

Standards

Topic Detail
Transport gRPC over HTTP (application/grpc), typically HTTP/2
Path POST /{service}/{method}
Message 5-byte prefix: 1-byte compression flag + 4-byte big-endian length
Status grpc-status / grpc-message in trailers (or headers)
TE te: trailers
Protobuf proto2 / proto3 via rprotobuf
Status codes e.g. 12 UNIMPLEMENTED, 13 INTERNAL

Content-types accepted: application/grpc, application/grpc+proto, and application/grpc+json (optional ; parameters). Other subtypes (+thrift, grpc-web*, …) return HTTP 415. JSON payloads use the proto3 JSON mapping via rjsonparser; framing stays 5-byte length-prefixed.

Architecture

HTTP Stream
    │ POST /pkg.Service/Method  content-type: application/grpc
    ▼
GrpcHandlerFactory  (ServerHandlerFactory)
    │ ProtoFile::get_rpc_by_path
    ▼
GrpcService::start_unary_call → ProtoMessageHandler
    │
    └── GrpcResponseChannel → framed response + grpc-status trailers

Client path: GrpcClient::unary_call → GrpcUnaryCall as ClientHandlerFactory for an HTTP client endpoint.

Framing

Type / const Purpose
GrpcFrameParser<H> Incremental length-prefixed parser
GrpcEventHandler Message callbacks
frame(&[u8]) -> Vec<u8> Encode one message
framed_size Size helper
DEFAULT_MAX_MESSAGE_SIZE 4 MiB
GrpcFramingError Oversized / compressed / partial

Compressed frames (flag ≠ 0) are rejected. Split-feed safe. 0 max size uses the default 4 MiB (not unlimited); pass u64::MAX only for an explicit no-cap.

use hopf_grpc::{frame, GrpcFrameParser};

let bytes = frame(b"protobuf-payload");
// parser.receive(&chunk) pushes complete messages to handler

Proto model

Build schemas at runtime — no tonic/prost codegen:

Type Purpose
ProtoFile / ProtoFileBuilder Package, syntax, messages, enums, services
ProtoFileParser Parse .proto text
MessageDescriptor / EnumDescriptor / FieldDescriptor / FieldType Schema
ServiceDescriptor / RpcDescriptor RPC map
ProtoMessageHandler / ProtoDefaultHandler Decode push events
ProtoLocator / ScalarValue Field access
ProtoModelAdapter / ProtoModelSerializer Protobuf wire encode/decode
JsonModelAdapter / JsonModelSerializer Proto3 JSON encode/decode (application/grpc+json)
ProtoParseError Parse failures
use hopf_grpc::proto::ProtoFile;

let file = ProtoFile::builder()
    // .package(...).syntax(...).add_message(...).add_service(...)
    .build();
// or: ProtoFileParser::parse(include_str!("hello.proto"))?
let rpc = file.get_rpc_by_path("/helloworld.Greeter/SayHello");

Server SPI

pub trait GrpcService: Send + Sync {
    fn start_unary_call(
        &self,
        path: &str,
        channel: GrpcResponseChannel,
    ) -> Option<Box<dyn ProtoMessageHandler>>;
}

// Factory implements ServerHandlerFactory:
GrpcHandlerFactory::new(proto_file, Arc::new(service))
factory.set_max_message_size(4 * 1024 * 1024);

Responding

// Inside handler after decoding request fields:
let mut msg = channel.open_message(None)?; // content-type optional
msg.field("message", ScalarValue::String("hello".into()));
// or msg.writer() / msg.serializer()
msg.complete()?;

// Errors:
channel.send_error(12, "UNIMPLEMENTED");
channel.send_error_cause(&err);

Server maps protocol problems to HTTP 415 / 404 / 400, or gRPC status in trailers. One response message per unary call (response already sent if repeated).

Client

use hopf_grpc::GrpcClient;

let client = GrpcClient::new(proto_file);
let call = client.unary_call(
    "/helloworld.Greeter/SayHello",
    req_bytes,
    Box::new(MyResponseHandler),
);
// or client.unary_call_json(...) for application/grpc+json
// `call` is a ClientHandlerFactory — attach to H2/H1/H3 client endpoint
Type Role
GrpcClient Holds ProtoFile
GrpcUnaryCall ClientHandlerFactory for one call
GrpcResponseHandler App callback for response / status

Surfaces grpc-status from headers or trailers.

Configuration

Knob Type Default Notes
Max message size u64 4 MiB (DEFAULT_MAX_MESSAGE_SIZE) GrpcHandlerFactory::set_max_message_size; parser set_max_message_size (0 → default 4 MiB, not unlimited)
Schema ProtoFile caller Builder or parser
Service Arc<dyn GrpcService> caller Path routing

Cargo features

Feature Effect
(default) Unary gRPC
integration Integration tests

Examples

See Cookbook: gRPC.

cargo run -p grpc -- 127.0.0.1:8080
Package Demonstrates Knobs
examples/grpc Unary server demo arg1 = listen addr

No environment variables.

Limitations