folk-plugin-grpc 0.1.3

gRPC plugin for Folk — unary call passthrough to PHP workers via tonic
Documentation

folk-plugin-grpc

gRPC plugin for Folk — unary call passthrough to PHP workers via tonic. No Rust protobuf codegen required.

Status: in active development. See folk-spec for the roadmap.

Requirements

  • Rust 1.85+
  • folk-api
  • PHP with google/protobuf or the protobuf PECL extension

Installation

# Cargo.toml
folk-plugin-grpc = "0.1"

Quick start

Add to folk.build.toml:

[[plugin]]
crate_name = "folk-plugin-grpc"
version = "0.1"
config_key = "grpc"

Configure in folk.toml:

[grpc]
listen = "0.0.0.0:50051"
reflection = false

Write a PHP gRPC service class:

<?php
use Folk\Sdk\Grpc\GrpcModeHandler;

class GreeterService implements GrpcModeHandler
{
    public function call(string $service, string $method, string $payload): string
    {
        // $service = "helloworld.Greeter"
        // $method  = "SayHello"
        // $payload = raw protobuf bytes

        $request = new \Helloworld\HelloRequest();
        $request->mergeFromString($payload);

        $reply = new \Helloworld\HelloReply();
        $reply->setMessage('Hello, ' . $request->getName());

        return $reply->serializeToString();
    }
}

Register in the worker entry point:

$loop = new \Folk\Sdk\Worker\WorkerLoop();
$loop->register('grpc.call', function (mixed $params): string {
    $handler = new GreeterService();
    return $handler->call($params['service'], $params['method'], $params['payload']);
});
$loop->run();

Test with grpcurl:

grpcurl -plaintext localhost:50051 list
# helloworld.Greeter

grpcurl -plaintext -d '{"name": "Folk"}' localhost:50051 helloworld.Greeter/SayHello
# { "message": "Hello, Folk" }

Configuration

Key Type Default Description
listen SocketAddr "0.0.0.0:50051" Address and port for the gRPC server.
reflection bool false Enable gRPC server reflection (for grpcurl/Postman discovery).

How it works

Passthrough model

The key design decision: Rust does not decode or encode protobuf messages. It treats protobuf payloads as opaque bytes. PHP handles all protobuf serialization using its own generated classes.

This means:

  • No .proto files needed at build time in Rust.
  • No protobuf codegen step in the Rust build.
  • PHP owns the service contract entirely.
  • Adding or changing protobuf services requires no Rust recompilation.

Request flow

  1. A gRPC client sends an HTTP/2 request to /{ServiceName}/{MethodName}.
  2. The plugin extracts the service and method names from the URI path.
  3. The 5-byte gRPC framing (compression flag + length prefix) is stripped.
  4. The raw protobuf bytes are wrapped in a GrpcEnvelope:
    { service: "helloworld.Greeter", method: "SayHello", payload: <bytes> }
    
  5. The envelope is serialized to MessagePack and sent to a PHP worker via executor.execute().
  6. The PHP worker decodes protobuf, executes logic, and returns serialized protobuf bytes.
  7. The plugin re-applies gRPC framing and returns the response.

Error handling

  • Bad path format → gRPC status UNIMPLEMENTED (12)
  • Body read failure → gRPC status INTERNAL (13)
  • Worker execution error → gRPC status INTERNAL (13)

RPC method

grpc.services — Lists registered gRPC service names via the admin socket.

License

MIT