ข้ามไปยังเนื้อหา

Interceptors

interceptor คือ middleware เวอร์ชันของ gRPC: function ที่ห่อ call ไว้ เพื่อให้คุณรัน code ก่อน และ หลัง handler จริง ถ้าคุณเคยใช้ HTTP middleware มาก่อน นี่คือแนวคิดเดียวกัน แต่ใช้กับ RPC แทน route

จุดประสงค์คือเก็บ logic แบบ cross-cutting — logging, authentication, metrics, tracing, retry — ออกจาก business handler คุณเขียน concern นั้นครั้งเดียวเป็น interceptor แล้ว interceptor จะ apply กับทุก method อัตโนมัติ

interceptor ประกอบกันเป็น chain แต่ละตัวรับ call, ทำงานของตัวเอง แล้วเรียกตัวถัดไป — จนถึงตัวในสุดคือ handler ของคุณ ตอนไหลกลับออกมา แต่ละ interceptor ตรวจ response หรือ error ได้

flowchart LR
  call["incoming call"] --> log["logging
interceptor"]
  log --> auth["auth
interceptor"]
  auth --> metrics["metrics
interceptor"]
  metrics --> handler["handler ของคุณ"]
  handler -->|response / error| metrics
  metrics --> auth
  auth --> log
  log -->|response| call
interceptor chain ที่ห่อ handler ไว้

ลำดับสำคัญ: วาง logging ไว้นอกสุดเพื่อให้บันทึกได้ครบทุกอย่าง, auth ถัดมาเพื่อ reject call ที่ยังไม่ authenticate ก่อนจะถึงงานที่แพง, และ metrics ไว้ใกล้ handler เพื่อให้เวลาที่จับได้สะท้อนงานจริง

เพราะ gRPC มีทั้ง call แบบ unary และ streaming จึงมี interceptor สอง ชนิด:

  • unary interceptor ห่อ call แบบ request/response ตัวเดียว interceptor แบบนี้เห็น request, เรียก handler, แล้วเห็น response — จบในช็อตเดียว
  • stream interceptor ห่อ call แบบ streaming โดย default ไม่เห็นแต่ละ message เพราะห่อที่ระดับ stream object ดังนั้นถ้าจะสังเกตแต่ละ message คุณต้องห่อ method send/receive ของ stream

ความไม่สมมาตรตรงนี้ทำคนสะดุด: logging interceptor ที่เขียนไว้สำหรับ unary จะไม่ log ทุก message ที่ stream อัตโนมัติ — คุณต้องห่อ stream เอง

ด้านล่างคือ logging ฝั่ง server ที่บันทึก method และเวลาที่ใช้ของทุก unary call

func loggingUnary(
ctx context.Context, req any, info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (any, error) {
start := time.Now()
resp, err := handler(ctx, req) // call the next link (eventually your handler)
log.Printf("method=%s took=%s err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}
server := grpc.NewServer(grpc.ChainUnaryInterceptor(loggingUnary))

interceptor ทำงานได้ทั้งสองฝั่ง: server interceptor บังคับ auth และบันทึก metrics; client interceptor แนบ token, เพิ่ม trace id และทำ retry พร้อม backoff แนวคิด chain เดียวกันใช้ได้ทั้งสองทิศทาง

  • Logging & tracing — ที่เดียวสำหรับบันทึก method, latency, status และ trace id
  • Auth — ตรวจ token จาก metadata แล้ว reject เร็ว ๆ ด้วย UNAUTHENTICATED
  • Metrics — นับ call และวัด latency ต่อ method สำหรับ dashboard
  • Retry — client interceptor สามารถ retry call ที่ idempotent เมื่อเจอ transient failure ได้แบบโปร่งใส
  • Recovery — จับ panic/exception ใน handler แล้วเปลี่ยนเป็น status INTERNAL ที่สะอาด แทนที่จะทำให้ connection พัง
interceptor ใน gRPC คืออะไร?
ทำไมควรวาง auth interceptor ไว้ก่อน handler ใน chain?
ทำไม unary logging interceptor ถึงไม่ log ทุก message ที่ stream อัตโนมัติ?
งานไหนเหมาะกับ client-side interceptor?