Go - Sample gRPC Streaming Service
Beginner 10/10
Teacher 10/10
Architect 10/10
Overview
This sample shows a basic gRPC server exposing a server-streaming RPC with a simple unary interceptor for logging.
Project Layout
app/
cmd/srv/main.go
proto/echo.proto
internal/svc/echo.go
go.mod
go.mod
module example.com/app
go 1.22.0
require (
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
)
proto/echo.proto
syntax = "proto3";
package echo;
option go_package = "example.com/app/proto/echo;echo";
service Echo {
rpc Stream(EchoReq) returns (stream EchoResp) {}
}
message EchoReq { string msg = 1; }
message EchoResp { string msg = 1; int32 n = 2; }
internal/svc/echo.go
package svc
import (
"context"
"time"
pb "example.com/app/proto/echo"
)
type EchoSrv struct{ pb.UnimplementedEchoServer }
func (s *EchoSrv) Stream(req *pb.EchoReq, ss pb.Echo_StreamServer) error {
for i := 0; i < 5; i++ {
if err := ss.Send(&pb.EchoResp{Msg: req.Msg, N: int32(i)}); err != nil { return err }
time.Sleep(200 * time.Millisecond)
}
return nil
}
func UnaryLog(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler)(any, error){
start := time.Now()
resp, err := handler(ctx, req)
log.Printf("%s took %s", info.FullMethod, time.Since(start))
return resp, err
}
cmd/srv/main.go
package main
import (
"log"
"net"
"google.golang.org/grpc"
pb "example.com/app/proto/echo"
"example.com/app/internal/svc"
)
func main(){
lis, err := net.Listen("tcp", ":50051")
if err != nil { log.Fatal(err) }
srv := grpc.NewServer(grpc.ChainUnaryInterceptor(svc.UnaryLog))
pb.RegisterEchoServer(srv, &svc.EchoSrv{})
log.Println("gRPC listening on :50051")
log.Fatal(srv.Serve(lis))
}
Generate Stubs (example)
# requires: protoc, protoc-gen-go, protoc-gen-go-grpc in PATH
protoc -I proto --go_out=./ --go-grpc_out=./ proto/echo.proto
Test Streaming (with grpcurl)
grpcurl -plaintext -d '{"msg":"hi"}' localhost:50051 echo.Echo/Stream
Go Client Example
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "example.com/app/proto/echo"
)
func main(){
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil { log.Fatal(err) }
defer conn.Close()
c := pb.NewEchoClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := c.Stream(ctx, &pb.EchoReq{Msg:"hi"})
if err != nil { log.Fatal(err) }
for {
resp, err := stream.Recv()
if err != nil { break }
log.Printf("%s %d", resp.GetMsg(), resp.GetN())
}
}
Makefile (optional)
PROTO=proto/echo.proto
proto-gen:
protoc -I proto --go_out=./ --go-grpc_out=./ $(PROTO)
run:
go run ./cmd/srv
run-client:
go run ./cmd/client
lint:
go vet ./...
test:
go test ./...