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)].
Contents
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 handlerProto 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
- Unary RPC only — no client / server / bidirectional streaming.
- No message compression — compressed frames rejected.
- No generated stubs — runtime
.protomodel only. - H1 trailer support is limited (inherited from
hopf-http); prefer H2/H3. - One message per unary response enforced.
- Not a full Google gRPC stack (no interceptors, load balancing, etc.).