https://github.com/qoretechnologies/module-grpc
Qore gRPC/protobuf module
https://github.com/qoretechnologies/module-grpc
Last synced: about 1 month ago
JSON representation
Qore gRPC/protobuf module
- Host: GitHub
- URL: https://github.com/qoretechnologies/module-grpc
- Owner: qoretechnologies
- License: mit
- Created: 2026-02-20T04:52:09.000Z (5 months ago)
- Default Branch: develop
- Last Pushed: 2026-06-05T18:14:36.000Z (about 2 months ago)
- Last Synced: 2026-06-05T20:08:52.390Z (about 2 months ago)
- Language: QuakeC
- Size: 692 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: COPYING.MIT
Awesome Lists containing this project
README
# Qore grpc Module
## Introduction
The `grpc` module provides gRPC client/server and protobuf support for Qore, including:
- Dynamic protobuf schema loading from `.proto` files (no code generation needed)
- Binary protobuf encoding/decoding via `ProtobufSchema`
- gRPC client with all four call patterns (unary, server streaming, client streaming, bidirectional)
- gRPC server with handler registration and async I/O
- TLS/SSL support on client and server
- Custom metadata passing (request, initial response, trailing)
- Timeout/deadline enforcement
- Connection pooling via `HttpClientConnectionManager`
- Built-in gRPC server reflection (`GrpcReflectionService`) for client service discovery
## Architecture
The module has two layers:
- **Binary module (`grpc.so`)**: C++ QPP wrapping `libprotobuf` for schema loading and message encoding/decoding. Only dependency is `libprotobuf` (no libgrpc++).
- **Qore module (`GrpcUtil`)**: Pure Qore implementing the gRPC protocol natively on Qore's HTTP/2 stack.
## Requirements
- Qore 2.0+ (with HTTP/2 trailer support)
- CMake 3.5+
- C++17 compiler
- `libprotobuf` (protobuf development libraries)
- Ubuntu/Debian: `libprotobuf-dev`
- Alpine: `protobuf-dev`
- Fedora: `protobuf-devel`
## Building
```bash
mkdir build && cd build
cmake ..
make
make install
```
## Quick Start
### Unary Call
```qore
#!/usr/bin/env qore
%modern
%requires grpc
%requires GrpcUtil
ProtobufSchema schema("./proto/", "service.proto");
GrpcChannel channel("http://localhost:50051");
GrpcClient client(channel, schema, "MyService");
hash result = client.call("SayHello", {"name": "World"});
printf("Response: %y\n", result.body);
printf("Status: %d %s\n", result.status_code, result.status_message);
channel.shutdown();
```
### Server
```qore
ProtobufSchema schema("./proto/", "service.proto");
GrpcServer server(schema);
server.registerHandler("MyService", "SayHello",
hash sub(hash request, hash metadata) {
return {"message": "Hello, " + request.name, "status": 0};
});
int port = server.addInsecurePort("localhost:0");
server.start();
server.wait();
```
### Server Streaming
```qore
# Client
GrpcClientStream stream = client.serverStream("ListItems", {"category": "books"});
while (*hash item = stream.read(5s)) {
printf("Item: %y\n", item);
}
hash result = stream.finish();
# Server handler
server.registerHandler("MyService", "ListItems",
sub(hash request, GrpcServerStream stream, hash metadata) {
for (int i = 0; i < 10; ++i) {
stream.write({"name": sprintf("item-%d", i)});
}
});
```
### Client Streaming
```qore
# Client
GrpcClientStream stream = client.clientStream("Upload");
stream.write({"chunk": "data1"});
stream.write({"chunk": "data2"});
hash result = stream.finish();
# Server handler
server.registerHandler("MyService", "Upload",
hash sub(GrpcServerStream stream, hash metadata) {
int count = 0;
while (*hash msg = stream.read()) {
++count;
}
return {"total": count};
});
```
### Bidirectional Streaming
The server handler processes messages incrementally -- no pre-buffering of the
client stream. Both sides can interleave reads and writes for interactive
communication:
```qore
# Client - interactive request/response
GrpcClientStream stream = client.bidiStream("Chat");
stream.write({"text": "Hello"});
*hash reply = stream.read(5s); # response arrives before next send
printf("Reply: %s\n", reply.text);
stream.write({"text": "World"});
reply = stream.read(5s);
printf("Reply: %s\n", reply.text);
stream.writesDone();
hash result = stream.finish();
# Server handler - echo each message immediately
server.registerHandler("MyService", "Chat",
sub(GrpcServerStream stream, hash metadata) {
while (*hash msg = stream.read()) {
stream.write({"text": "Echo: " + msg.text});
}
});
```
### TLS
```qore
# Server with TLS
string cert = ReadOnlyFile::readTextFile("server.crt");
string key = ReadOnlyFile::readTextFile("server.key");
int port = server.addSecurePort("localhost:0", {
"server_cert": cert,
"server_key": key,
});
# Client with HTTPS
GrpcChannel channel("https://localhost:50051");
```
### Metadata and Timeouts
```qore
# Send metadata and set timeout
hash opts = {
"timeout_ms": 5000,
"metadata": {"x-request-id": "abc-123"},
};
hash result = client.call("Method", request, opts);
# Server receives metadata in handler
server.registerHandler("Service", "Method",
hash sub(hash request, hash metadata) {
string req_id = metadata."x-request-id";
return {"id": req_id};
});
# Streaming handlers can set trailing metadata
server.registerHandler("Service", "Stream",
sub(hash request, GrpcServerStream stream, hash metadata) {
stream.setTrailingMetadata({"x-count": "5"});
# ... write messages ...
});
```
### Server Reflection
Enable clients to discover services without local `.proto` files using the standard
gRPC server reflection protocol (`grpc.reflection.v1`). This works with tools like
`grpcurl` and `grpc_cli` as well as programmatic clients.
#### Server Setup
Just pass `enable_reflection: True` in the server options:
```qore
%modern
%requires grpc
%requires GrpcUtil
ProtobufSchema schema("./proto/", "service.proto");
GrpcServer server(schema, {"enable_reflection": True});
server.registerHandler("MyService", "MyMethod",
hash sub(hash request, hash metadata) {
return {"result": "ok"};
});
server.addInsecurePort("localhost:50051");
server.start();
server.wait();
```
#### Client Discovery with grpcurl
```bash
# List all services exposed by the server
grpcurl -plaintext localhost:50051 list
# Describe a specific service
grpcurl -plaintext localhost:50051 describe mypackage.MyService
# Make a call without needing the .proto file
grpcurl -plaintext -d '{"name": "World"}' localhost:50051 mypackage.MyService/MyMethod
```
#### Programmatic Discovery
```qore
%modern
%requires grpc
%requires GrpcUtil
GrpcChannel channel("http://localhost:50051");
# Discover services and schema via reflection
GrpcReflectionClient rc(channel);
list services = rc.listServices();
printf("Available services: %y\n", services);
# Build a schema from the server's descriptors and make calls
ProtobufSchema discovered = rc.discoverSchema();
GrpcClient client(channel, discovered, "MyService");
hash result = client.call("MyMethod", {"name": "World"});
printf("Response: %y\n", result.body);
channel.shutdown();
```
#### Data Provider with Reflection
The `GrpcDataProvider` module can use reflection for zero-configuration service access:
```qore
%modern
%requires GrpcDataProvider
GrpcDataProvider provider({
"url": "http://localhost:50051",
"use_reflection": True,
});
# Services are discovered automatically
auto result = provider.getChildProvider("MyService")
.getChildProvider("MyMethod")
.doRequest({"name": "World"});
```
Or from the command line:
```bash
qdp 'grpc{url=http://localhost:50051,use_reflection=true}/MyService/MyMethod' dor name=World
```
### Standalone Protobuf
```qore
%requires grpc
ProtobufSchema schema("./proto/", "messages.proto");
binary data = schema.encode("MyMessage", {"field": "value"});
hash msg = schema.decode("MyMessage", data);
# JSON conversion
string json = schema.toJson("MyMessage", {"field": "value"});
hash parsed = schema.fromJson("MyMessage", json);
```
## Data Provider Integration
The `GrpcDataProvider` module integrates gRPC with Qore's data provider framework, enabling
`qdp` CLI access and Qorus workflow integration.
### Unary Call via `qdp`
```bash
# Using a proto file
qdp 'grpc{url=http://localhost:50051,proto_path=./proto,proto_file=service.proto}/MyService/SayHello' dor name=World
# Using server reflection (no proto files needed)
qdp 'grpc{url=http://localhost:50051,use_reflection=true}/MyService/SayHello' dor name=World
```
### Observable Server Streaming
Server-streaming methods expose an `events` child provider for event-driven consumption:
```bash
qdp 'grpc{url=http://localhost:50051,use_reflection=true}/PriceService/WatchPrices/events' listen
```
### Interactive Bidirectional Streaming
Bidirectional-streaming methods also expose an `events` child that supports both
receiving events and sending messages, enabling interactive use with `qdp ix`:
```bash
qdp 'grpc{url=http://localhost:50051,use_reflection=true}/ChatService/Chat/events' ix
```
Programmatic usage:
```qore
%modern
%requires GrpcDataProvider
GrpcDataProvider provider({
"url": "http://localhost:50051",
"use_reflection": True,
});
# Navigate to the events child of a bidi method
AbstractDataProvider events = provider.getChildProvider("ChatService")
.getChildProvider("Chat")
.getChildProvider("events");
# Register observer and start the stream
events.registerObserver(my_observer);
events.observersReady();
# Send messages
events.sendMessage(MESSAGE_GRPC_STREAM_SEND, {"text": "hello"});
events.sendMessage(MESSAGE_GRPC_STREAM_SEND, {"text": "world"});
# Signal writes done (server will finish and close the stream)
events.stopEvents();
```
## License
MIT License - see [LICENSE](LICENSE) for details.
## Copyright
Copyright 2026 Qore Technologies, s.r.o.