grpc.md - maoxiaoyue/hypgo GitHub Wiki

pkg/grpc — gRPC Server 封裝

v0.8.5+ 此套件為 HypGo v0.8.5 新增功能,v0.8.1 版本不包含。

grpc 套件提供 gRPC Server 的統一封裝,包含 listener 管理、TLS、reflection、health check、interceptor(中間件)和 graceful shutdown。

設計理念

HypGo 的 gRPC 與 REST 採用獨立 Server 架構:

pkg/server   → HTTP/1.1 + HTTP/2 + HTTP/3(REST + WebSocket)  :8080
pkg/grpc     → gRPC Server                                      :9090

兩者共用:
  app/models/     ← 資料結構
  app/services/   ← 業務邏輯
  pkg/errors/     ← Error Catalog
  pkg/hidb/       ← 資料庫
  pkg/logger/     ← 日誌
  pkg/config/     ← 設定

快速上手

建立 gRPC 專案

hyp new grpc userservice && cd userservice && go mod tidy
make proto    # 編譯 .proto → Go code
go run .

在既有專案中加入 gRPC

import (
    hypgrpc "github.com/maoxiaoyue/hypgo/pkg/grpc"
    "github.com/maoxiaoyue/hypgo/pkg/grpc/interceptor"
)

grpcSrv := hypgrpc.New(hypgrpc.Config{
    Addr:              ":9090",
    EnableReflection:  true,
    EnableHealthCheck: true,
}, logger,
    grpc.ChainUnaryInterceptor(
        interceptor.Recovery(logger),
        interceptor.Logger(logger),
    ),
)

// 註冊服務
pb.RegisterUserServiceServer(grpcSrv.GRPCServer(), &UserServer{})

// 啟動
go grpcSrv.Start()

// 優雅關閉
grpcSrv.GracefulStop()

Config

type Config struct {
    Addr              string      // 監聽地址(預設 ":9090")
    TLS               *TLSConfig  // TLS 配置(nil = plaintext)
    EnableReflection  bool        // 啟用 gRPC reflection(grpcurl 偵錯)
    EnableHealthCheck bool        // 啟用 gRPC health check 服務
    MaxRecvMsgSize    int         // 最大接收訊息大小(預設 4MB)
    MaxSendMsgSize    int         // 最大發送訊息大小(預設 4MB)
}

type TLSConfig struct {
    CertFile string
    KeyFile  string
}

Server API

方法 說明
New(cfg, logger, ...grpc.ServerOption) 建立 Server(自動配置 TLS、reflection、health)
GRPCServer() 取得底層 *grpc.Server(用於 pb.RegisterXxxServer
Start() 啟動(阻塞)
GracefulStop() 優雅關閉(等待進行中的 RPC 完成)
Stop() 立即停止
Addr() 取得實際監聽地址
IsShuttingDown() 是否正在關閉(atomic.Bool)
SetServingStatus(service, status) 設定 health check 狀態

Interceptor(gRPC 中間件)

對應 pkg/middleware/ 的 HTTP 中間件:

Interceptor 功能 對應 HTTP middleware
interceptor.Recovery(logger) Panic → codes.Internal middleware.Recovery
interceptor.Logger(logger) 記錄方法、耗時、狀態碼 middleware.Logger
interceptor.Auth(authFn, skipMethods...) Token 驗證 + context 注入 middleware.JWT
interceptor.RateLimit(config) IP 限流 + 背景清理 middleware.RateLimit

使用 Interceptor

s := hypgrpc.New(cfg, logger,
    grpc.ChainUnaryInterceptor(
        interceptor.Recovery(logger),                    // 最外層:捕獲 panic
        interceptor.Logger(logger),                      // 記錄每個 RPC
        interceptor.Auth(myAuthFunc,                     // 驗證 token
            "/grpc.health.v1.Health/Check",              // 跳過 health check
        ),
        interceptor.RateLimit(interceptor.RateLimitConfig{
            MaxRequests: 100,                            // 每分鐘 100 次
            Window:      time.Minute,
        }),
    ),
)

Auth Interceptor 詳解

// 定義驗證函式
authFn := func(ctx context.Context, token string) (interface{}, error) {
    // 驗證 JWT token
    claims, err := jwt.Validate(token)
    if err != nil {
        return nil, err  // → codes.Unauthenticated
    }
    return claims, nil  // 存入 context
}

// 在 RPC handler 中取出使用者
func (s *Server) GetUser(ctx context.Context, req *pb.GetUserReq) (*pb.UserResp, error) {
    user, ok := interceptor.UserFromContext(ctx)
    if !ok {
        return nil, status.Error(codes.Internal, "no user in context")
    }
    // user 是 authFn 回傳的物件
}

RateLimit 安全設計

  • 根據客戶端 IP 限流(從 peer.FromContext 取得)
  • 背景 goroutine 定期清理過期 key(預設每 5 分鐘)
  • 超過限制回傳 codes.ResourceExhausted

專案結構

hyp new grpc userservice
userservice/
├── app/
│   ├── proto/userservicepb/
│   │   └── userservice.proto      ← Protobuf 定義
│   ├── rpc/
│   │   └── userservice_server.go  ← gRPC 服務實作
│   ├── models/                    ← 共用資料結構
│   ├── services/                  ← 共用業務邏輯
│   └── config/config.yaml
├── main.go
├── Makefile                       ← make proto
└── go.mod

新增更多服務

hyp generate proto order
# → app/proto/orderpb/order.proto
# → app/rpc/order_server.go

與 REST 的共存

同一個專案可以同時有 REST 和 gRPC:

func main() {
    // REST Server
    srv := server.New(cfg, log)
    routers.Setup(srv.Router())

    // gRPC Server
    grpcSrv := hypgrpc.New(hypgrpc.Config{Addr: ":9090"}, log)
    pb.RegisterUserServiceServer(grpcSrv.GRPCServer(), &UserServer{})

    // 同時啟動
    go srv.Start()    // :8080 REST
    go grpcSrv.Start() // :9090 gRPC

    // 優雅關閉
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    srv.Shutdown(ctx)
    grpcSrv.GracefulStop()
}

共用 Service 層

Controller (REST)  ─┐
                    ├─→ UserService (app/services/)  ─→  Database
RPC Handler (gRPC) ─┘

Controller 和 RPC handler 都是薄 adapter,真正的邏輯在 app/services/ 中共用。

安全

  • TLS:支援 mTLS,MinVersion = TLS 1.2
  • Auth:未提供 token → codes.Unauthenticated(安全預設)
  • Auth skip:可跳過不需驗證的方法(如 Health check)
  • RateLimit:背景清理防記憶體洩漏
  • Recovery:panic 不 crash,回傳 codes.Internal
  • GracefulStopatomic.Bool 防止重複關閉

必要工具

gRPC 開發需要安裝:

# protoc 編譯器
# https://github.com/protocolbuffers/protobuf/releases

# Go protobuf 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 偵錯工具(可選)
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

測試

# 啟動 server 後,用 grpcurl 測試
grpcurl -plaintext localhost:9090 list
grpcurl -plaintext -d '{"name":"test"}' localhost:9090 userservicepb.UserserviceService/CreateUserservice