Bài về middleware HTTP cho ta một mẫu đẹp: xâu chuỗi các lớp cắt ngang (log, auth) quanh handler. gRPC cần đúng thứ đó — nhưng nó không dùng http.Handler, nó có cơ chế riêng gọi là interceptor. Interceptor cho phép chèn log, xác thực, đo metrics, phục hồi panic vào mọi RPC ở một chỗ, thay vì lặp trong từng handler. Bài này dựng một chain interceptor chạy được, đo thật thứ tự thực thi và short-circuit, và chỉ ra nó song song với middleware HTTP thế nào.
Interceptor là middleware của gRPC
Một unary server interceptor có chữ ký cố định: nhận ctx, req, thông tin RPC (info), và handler tiếp theo. Nó bọc quanh handler — chạy code trước, gọi handler, chạy code sau (đúng mô hình củ hành như middleware HTTP):
func logItc(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
t0 := time.Now()
fmt.Printf("[log] -> %s\n", info.FullMethod) // tên RPC, vd /svc.Echo/Noi
res, err := handler(ctx, req) // gọi tiếp -> handler hoặc itc kế
fmt.Printf("[log] <- %s (%v)\n", info.FullMethod, time.Since(t0))
return res, err
}
info.FullMethod cho biết RPC nào đang được gọi — nền cho log và metrics theo từng method. Gọi handler(ctx, req) là "đi tiếp"; giá trị trả về là response của handler.

Hình 1: Interceptor bọc quanh handler như middleware HTTP. logItc chạy trước/sau handler; authItc đọc token từ metadata và short-circuit nếu thiếu. ChainUnaryInterceptor đăng ký một lần cho mọi RPC.
Auth: metadata và short-circuit
gRPC không có "HTTP header" ở tầng ứng dụng — nó dùng metadata (cũng đi trong header HTTP/2 bên dưới). Interceptor auth đọc token từ metadata và, nếu thiếu, trả lỗi mà không gọi handler — short-circuit y hệt middleware HTTP không gọi next:
func authItc(ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler) (any, error) {
md, _ := metadata.FromIncomingContext(ctx)
if len(md.Get("token")) == 0 {
return nil, status.Error(codes.Unauthenticated, "cần token") // KHÔNG gọi handler
}
return handler(ctx, req)
}
Đăng ký chuỗi interceptor một lần lúc tạo server, áp cho tất cả method:
gs := grpc.NewServer(grpc.ChainUnaryInterceptor(logItc, authItc)) // log ngoài, auth trong
Đo thật: củ hành và short-circuit
Chạy thật (Go 1.23, grpc 1.67) hai tình huống:

Hình 2: Request có token — log vào → auth OK → handler → log ra, kết quả vọng: chào. Request thiếu token — auth từ chối, handler không chạy, client nhận đúng mã gRPC Unauthenticated. Thứ tự củ hành và short-circuit hệt middleware HTTP.
- Có token:
[log] -> ... → [auth] OK → handler → [log] <-, kết quảvọng: chào. - Thiếu token:
[log] -> ... → [auth] TỪ CHỐI → [log] <-, handler không chạy, client nhận mãUnauthenticated.
Đúng mô hình củ hành: logItc bọc ngoài authItc, code trước handler(ctx,req) chạy lúc vào, code sau chạy lúc ra. Khi authItc trả status.Error mà không gọi handler, toàn bộ chuỗi dừng — nhưng logItc vẫn chạy phần "ra" của nó (vì nó đã gọi authItc, chính auth mới không gọi handler lõi). Client nhận mã lỗi gRPC chuẩn Unauthenticated, không phải một lỗi mơ hồ.
Đánh đổi cần cân nhắc
Stream có interceptor riêng — đừng quên. gRPC có bốn loại interceptor: unary/stream × server/client. UnaryServerInterceptor không áp cho streaming RPC; bạn phải viết StreamServerInterceptor riêng (bọc grpc.ServerStream). Nếu chỉ đăng ký unary interceptor, các stream RPC của bạn không qua log/auth — một lỗ hổng dễ bỏ sót. Đăng ký cả ChainStreamInterceptor khi có streaming.
Thứ tự interceptor quan trọng như middleware. Recover (phục hồi panic) nên ngoài cùng để bắt panic từ mọi lớp trong; Auth trước handler nghiệp vụ; Log/metrics thường ngoài để đo cả thời gian. ChainUnaryInterceptor giữ thứ tự khai báo (đầu tiên = ngoài cùng), nhưng bạn phải suy nghĩ về thứ tự đó — đặt sai gây panic không bắt được hoặc metrics thiếu.
Dùng thư viện có sẵn cho việc phổ biến. Đừng viết lại từ đầu log, metrics, retry, tracing — hệ sinh thái go-grpc-middleware cung cấp bộ interceptor đã kiểm chứng (Prometheus, OpenTelemetry, zap, recovery, auth). Viết tay interceptor cho logic đặc thù của bạn, nhưng tận dụng thư viện cho các mối quan tâm chuẩn.
Ba ý mang về
- Interceptor là middleware của gRPC: chữ ký
(ctx, req, info, handler)bọc quanh mọi RPC theo mô hình củ hành — code trướchandler(ctx,req)chạy lúc vào, code sau lúc ra;info.FullMethodcho biết RPC nào để log/metrics theo method;ChainUnaryInterceptorđăng ký một lần cho tất cả method. - Short-circuit và metadata song song với HTTP middleware: đo thật, auth thiếu token trả
status.Error(Unauthenticated)mà không gọi handler (client nhận đúng mã gRPC), và metadata từ context mang token — tương đương header của HTTP middleware. - Đừng quên interceptor cho stream và thứ tự chain: gRPC có 4 loại (unary/stream × server/client) — unary interceptor không áp cho stream, phải đăng ký
StreamServerInterceptorriêng; thứ tự chain quan trọng (recover ngoài cùng), và tận dụnggo-grpc-middlewarecho việc chuẩn.
Phần sau ta đào sâu vào chính định dạng dữ liệu mà gRPC dùng: Phần sau mổ xẻ cách Protocol Buffers mã hóa — varint, wire type, field number — và vì sao nó nhỏ gọn đến vậy so với JSON, đo từng byte.