{"id":20388318,"url":"https://github.com/omgolab/drpc","last_synced_at":"2026-05-09T09:04:14.757Z","repository":{"id":240960731,"uuid":"801364266","full_name":"omgolab/drpc","owner":"omgolab","description":"DistributedRPC: A Go library that enables http/curl over libp2p with gRPC and gRPC-web support. Perfect for building peer-to-peer applications with enhanced RPC capabilities.","archived":false,"fork":false,"pushed_at":"2025-02-11T11:34:06.000Z","size":3551,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-03-04T23:42:20.484Z","etag":null,"topics":["connectrpc","curl-over-libp2p","distributed-systems","grpc","grpc-go","grpc-over-libp2p","grpc-ts","grpc-web","grpc-web-proxy","libp2p"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omgolab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-16T05:03:06.000Z","updated_at":"2025-02-11T11:34:10.000Z","dependencies_parsed_at":"2025-03-04T23:42:25.773Z","dependency_job_id":"962302a2-5091-45e2-8fa6-4cc481c6f60a","html_url":"https://github.com/omgolab/drpc","commit_stats":null,"previous_names":["omgolab/drpc"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/omgolab/drpc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omgolab%2Fdrpc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omgolab%2Fdrpc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omgolab%2Fdrpc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omgolab%2Fdrpc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omgolab","download_url":"https://codeload.github.com/omgolab/drpc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omgolab%2Fdrpc/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262422576,"owners_count":23308702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["connectrpc","curl-over-libp2p","distributed-systems","grpc","grpc-go","grpc-over-libp2p","grpc-ts","grpc-web","grpc-web-proxy","libp2p"],"created_at":"2024-11-15T03:08:44.594Z","updated_at":"2026-05-09T09:04:14.751Z","avatar_url":"https://github.com/omgolab.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dRPC: ConnectRPC over libp2p\n\ndRPC is a Go library that allows you to use ConnectRPC services over the libp2p network. It combines the ease of use of ConnectRPC with the decentralized and resilient nature of libp2p.\n\n## Features\n\n- **ConnectRPC Compatibility:** Works seamlessly with existing ConnectRPC services.\n- **libp2p Transport:** Uses libp2p for peer discovery and connection management.\n- **Decentralized Architecture:** Enables building decentralized applications.\n- **Resilience:** Provides resilience against network failures.\n- **HTTP Gateway:** Offers an HTTP gateway for non-libp2p clients.\n\n## Architecture\n\n```\n[ConnectRPC Client] \u003c-\u003e [libp2p] \u003c-\u003e [dRPC Server] \u003c-\u003e [ConnectRPC Service]\n```\n\nThe dRPC server can also expose an HTTP gateway:\n\n```\n[HTTP Client] \u003c-\u003e [HTTP Gateway] \u003c-\u003e [dRPC Server]\n```\n\n## Getting Started\n\n### Prerequisites\n\n- Go 1.20 or later\n- [libp2p](https://libp2p.io/) (installed automatically as a Go dependency)\n- [ConnectRPC](https://connectrpc.com/) (installed automatically as a Go dependency)\n- [buf](https://buf.build/) (for generating code from `.proto` files)\n\n### Installation\n\n```bash\ngo get github.com/omgolab/drpc\n```\n\n### Usage\n\nFirst define your service using protocol buffer, for example save the following into `proto/greeter/v1/greeter.proto`\n\n```protobuf\nsyntax = \"proto3\";\n\npackage greeter.v1;\n\noption go_package = \"github.com/omgolab/drpc/demo/gen/go/greeter/v1;greeterv1\";\n\nservice GreeterService {\n  rpc SayHello (SayHelloRequest) returns (SayHelloResponse) {}\n}\n\nmessage SayHelloRequest {\n  string name = 1;\n}\n\nmessage SayHelloResponse {\n  string message = 1;\n}\n```\n\nThen run following command to generate go code from proto definition\n\n```\nbuf generate\n```\n\n#### Server\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/libp2p/go-libp2p\"\n\tgv1connect \"github.com/omgolab/drpc/demo/gen/go/greeter/v1/greeterv1connect\"\n\t\"github.com/omgolab/drpc/demo/greeter\"\n\t\"github.com/omgolab/drpc/internal/gateway\"\n\t\"github.com/omgolab/drpc/pkg/drpc/server\"\n\tglog \"github.com/omgolab/go-commons/pkg/log\"\n)\n\nfunc main() {\n\tlog, _ := glog.New(glog.WithFileLogger(\"server.log\"))\n\n\t// Create context with cancellation\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\t// Setup signal handling\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)\n\n\t// Create ConnectRPC mux \u0026 register greeter\n\tmux := http.NewServeMux()\n\tpath, handler := gv1connect.NewGreeterServiceHandler(\u0026greeter.Server{})\n\tmux.Handle(path, handler)\n\n\tserver, err := drpc.NewServer(ctx, mux,\n\t\tdrpc.WithLibP2POptions(\n\t\t\tlibp2p.ListenAddrStrings(\"/ip4/0.0.0.0/tcp/9090\"),\n\t\t\tlibp2p.DisableRelay(),\n\t\t\tlibp2p.NoSecurity, // Disable TLS\n\t\t),\n\t\tdrpc.WithHTTPPort(8080), // Use port 8080\n\t\tdrpc.WithLogger(log),\n\t\tdrpc.WithForceCloseExistingPort(true),\n\t\tdrpc.WithHTTPHost(\"localhost\"),\n\t\tdrpc.WithNoBootstrap(true),\n\t)\n\tif err != nil {\n\t\tlog.Fatal(\"failed to create server\", err)\n\t}\n\tdefer server.Close()\n\n\t// Add p2pinfo handler\n\tmux.HandleFunc(\"/p2pinfo\", gateway.P2PInfoHandler(server.P2PHost()))\n\n\t// Print listening addresses\n\tlog.Println(\"Server listening on:\")\n\tfor _, addr := range server.Addrs() {\n\t\tlog.Printf(\"  %s\\n\", addr)\n\t}\n\n\t// Wait for shutdown signal\n\t\u003c-sigChan\n}\n\n```\n\n#### Client\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"connectrpc.com/connect\"\n\t\"github.com/libp2p/go-libp2p\"\n\t\"github.com/libp2p/go-libp2p/core/peer\"\n\tgv1 \"github.com/omgolab/drpc/demo/gen/go/greeter/v1\"\n\tgv1connect \"github.com/omgolab/drpc/demo/gen/go/greeter/v1/greeterv1connect\"\n\t\"github.com/omgolab/drpc/pkg/drpc/client\"\n\t\"github.com/omgolab/drpc/demo/cmd/client\"\n)\n\nfunc main() {\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n\n\tserverMultiaddr, err := client.GetServerInfo()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to get server info: %v\", err)\n\t}\n\n\t// Create context\n\tctx := context.Background()\n\n\t// Direct libp2p connection\n\tfmt.Println(\"\\n=== Scenario 1: Direct libp2p connection ===\")\n\tlibp2pClient, err := newLibp2pClient(serverMultiaddr)\n\tif err != nil {\n\t\tlog.Printf(\"Failed to create libp2p client: %v\", err)\n\t\tfmt.Println(\"Testing direct libp2p connection...\")\n\t} else {\n\t\t// Test unary call\n\t\tfmt.Println(\"Testing unary call via direct libp2p...\")\n\t\tresp, err := libp2pClient.SayHello(ctx, connect.NewRequest(\u0026gv1.SayHelloRequest{\n\t\t\tName: \"Direct libp2p\",\n\t\t}))\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Direct libp2p call failed: %v\", err)\n\t\t} else {\n\t\t\tfmt.Printf(\"Response: %s\\n\", resp.Msg.Message)\n\t\t}\n\t}\n\n\tfmt.Println(\"\\n=== Scenario 2: HTTP Connect-RPC -\u003e libp2p ===\")\n\tif err := testHTTPConnect(ctx); err != nil {\n\t\tlog.Printf(\"HTTP Connect error: %v\", err)\n\t}\n\n\tfmt.Println(\"\\n=== Scenario 3: Connect-RPC Gateway -\u003e libp2p ===\")\n\tif err := testGateway(ctx, serverMultiaddr); err != nil {\n\t\tlog.Printf(\"Gateway error: %v\", err)\n\t}\n\n\tfmt.Println(\"\\n=== Scenario 4: Server Streaming (via all methods) ===\")\n\n\tif err := testStreaming(ctx, serverMultiaddr); err != nil {\n\t\tlog.Printf(\"Streaming error: %v\", err)\n\t}\n\n}\n\nfunc newLibp2pClient(addrStr string) (gv1connect.GreeterServiceClient, error) {\n\t// Create a libp2p host for the client\n\thost, err := libp2p.New(\n\t\tlibp2p.NoListenAddrs,\n\t\tlibp2p.NoSecurity, // Disable TLS\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create libp2p host: %w\", err)\n\t}\n\n\t// Parse the server's multiaddr\n\taddr, err := peer.AddrInfoFromString(addrStr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse address %s: %v\", addrStr, err)\n\t}\n\n\t// Connect to the server\n\tif err := host.Connect(context.Background(), *addr); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to connect to %s: %v\", addrStr, err)\n\t}\n\n\t// Successfully connected\n\treturn drpc.NewClient(host, addr.ID, []string{addrStr}, gv1connect.NewGreeterServiceClient), nil\n}\n\nfunc testHTTPConnect(ctx context.Context) error {\n\t// Create an HTTP client\n\thttpClient := gv1connect.NewGreeterServiceClient(\n\t\thttp.DefaultClient,\n\t\t\"http://localhost:8080\",\n\t)\n\n\t// Test unary call\n\tfmt.Println(\"Testing unary call via HTTP...\")\n\tresp, err := httpClient.SayHello(ctx, connect.NewRequest(\u0026gv1.SayHelloRequest{\n\t\tName: \"HTTP Connect\",\n\t}))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"HTTP call failed: %w\", err)\n\t}\n\n\tfmt.Printf(\"Response: %s\\n\", resp.Msg.Message)\n\treturn nil\n}\n\nfunc testGateway(ctx context.Context, serverMultiaddr string) error {\n\t// Test unary call via gateway...\n\tfmt.Println(\"Testing unary call via gateway...\")\n\t// Use fixed HTTP gateway address instead of extracting from multiaddr.\n\tgatewayBaseURL := \"http://localhost:8080\"\n\tfmt.Printf(\"Gateway Base URL: %s\\n\", gatewayBaseURL) // Debug print\n\n\t// Create custom HTTP client with proper configuration\n\thttpClient := \u0026http.Client{\n\t\tTransport: \u0026http.Transport{\n\t\t\tForceAttemptHTTP2: true,\n\t\t},\n\t}\n\n\t// Create Connect-RPC client with proper configuration\n\tgatewayClient := gv1connect.NewGreeterServiceClient(\n\t\thttpClient,\n\t\tgatewayBaseURL,\n\t\tconnect.WithHTTPGet(),\n\t)\n\n\t// Create request with proper headers\n\treq := connect.NewRequest(\u0026gv1.SayHelloRequest{\n\t\tName: \"Gateway\",\n\t})\n\n\t// Add headers to request\n\treq.Header().Set(\"Content-Type\", \"application/connect+proto\")\n\treq.Header().Set(\"Accept\", \"application/connect+proto\")\n\treq.Header().Set(\"Connect-Protocol-Version\", \"1\")\n\treq.Header().Set(\"Connect-Raw-Response\", \"1\")\n\treq.Header().Set(\"Accept-Encoding\", \"identity\")\n\treq.Header().Set(\"Content-Encoding\", \"identity\")\n\treq.Header().Set(\"User-Agent\", \"connect-go/1.0\")\n\treq.Header().Set(\"Connect-Timeout-Ms\", \"15000\")\n\treq.Header().Set(\"Connect-Accept-Encoding\", \"gzip\")\n\n\tresp, err := gatewayClient.SayHello(ctx, req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"gateway call failed: %w\", err)\n\t}\n\n\tfmt.Printf(\"Response: %s\\n\", resp.Msg.Message)\n\treturn nil\n}\n\nfunc testStreaming(ctx context.Context, serverMultiaddr string) error {\n\t// Test streaming via direct libp2p\n\tfmt.Println(\"Testing streaming via direct libp2p...\")\n\tlibp2pClient, err := newLibp2pClient(serverMultiaddr)\n\tif err != nil {\n\t\tfmt.Printf(\"Failed to create libp2p client: %v\\n\", err)\n\t\tfmt.Println(\"Skipping direct libp2p streaming test...\")\n\t} else {\n\t\tstream, err := libp2pClient.StreamingEcho(ctx, connect.NewRequest(\u0026gv1.StreamingEchoRequest{\n\t\t\tMessage: \"Direct libp2p stream\",\n\t\t}))\n\t\tif err != nil {\n\t\t\tfmt.Printf(\"Failed to start libp2p stream: %v\\n\", err)\n\t\t} else {\n\t\t\tfor stream.Receive() {\n\t\t\t\tfmt.Printf(\"Received from direct libp2p: %s\\n\", stream.Msg().Message)\n\t\t\t}\n\t\t\tif err := stream.Err(); err != nil {\n\t\t\t\tfmt.Printf(\"Stream error: %v\\n\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Test streaming via HTTP\n\tfmt.Println(\"\\nTesting streaming via HTTP...\")\n\tfmt.Println(\"Skipping HTTP Connect tests...\")\n\t// httpClient := gv1connect.NewGreeterServiceClient(\n\t// \thttp.DefaultClient,\n\t// \t\"http://localhost:8080\",\n\t// )\n\n\t// stream, err := httpClient.StreamingEcho(ctx, connect.NewRequest(\u0026gv1.StreamingEchoRequest{\n\t// \tMessage: \"HTTP stream\",\n\t// }))\n\t// if err != nil {\n\t// \treturn fmt.Errorf(\"failed to start HTTP stream: %w\", err)\n\t// }\n\n\t// for stream.Receive() {\n\t// \tfmt.Printf(\"Received from HTTP: %s\\n\", stream.Msg().Message)\n\t// }\n\t// if err := stream.Err(); err != nil {\n\t// \treturn fmt.Errorf(\"stream error: %w\", err)\n\t// }\n\n\t// Test streaming via gateway\n\tfmt.Println(\"\\nTesting streaming via gateway...\")\n\tgatewayAddrStr := \"http://localhost:8080\"\n\n\tgatewayClient := gv1connect.NewGreeterServiceClient(\n\t\thttp.DefaultClient,\n\t\tgatewayAddrStr,\n\t)\n\n\tfmt.Printf(\"Gateway URL: %s\\n\", gatewayAddrStr) // Debug print\n\n\tstream, err := gatewayClient.StreamingEcho(ctx, connect.NewRequest(\u0026gv1.StreamingEchoRequest{\n\t\tMessage: \"Gateway stream\",\n\t}))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to start gateway stream: %w\", err)\n\t}\n\n\tfor stream.Receive() {\n\t\tfmt.Printf(\"Received from gateway: %s\\n\", stream.Msg().Message)\n\t}\n\tif err := stream.Err(); err != nil {\n\t\treturn fmt.Errorf(\"stream error: %w\", err)\n\t}\n\n\treturn nil\n}\n```\n\nThese examples use the `demo` service. You'll need to adapt them to your specific service definition.\n\n## Running the Examples\n\n1.  **Generate the code:** From the `demo` directory, run `buf generate`.\n2.  **Build:** From the root of the repository, run:\n    ```bash\n    go build ./demo/cmd/server\n    go build ./demo/cmd/client\n    ```\n3.  **Run the server:**\n    ```bash\n    ./server\n    ```\n    This will start the server in detached mode, logging output to `server.log` and errors to `server.err`.\n4.  **Run the client:** In a separate terminal, run:\n    ```bash\n    ./client\n    ```\n    The client will attempt to connect using direct libp2p, HTTP, and the gateway (if the server is running with the gateway enabled).\n\n## License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomgolab%2Fdrpc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomgolab%2Fdrpc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomgolab%2Fdrpc/lists"}