Interceptors
Middleware สำหรับ RPC
หัวข้อที่มีชื่อว่า “Middleware สำหรับ RPC”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 อัตโนมัติ
chain ห่อ handler ไว้
หัวข้อที่มีชื่อว่า “chain ห่อ handler ไว้”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
ลำดับสำคัญ: วาง logging ไว้นอกสุดเพื่อให้บันทึกได้ครบทุกอย่าง, auth ถัดมาเพื่อ reject call ที่ยังไม่ authenticate ก่อนจะถึงงานที่แพง, และ metrics ไว้ใกล้ handler เพื่อให้เวลาที่จับได้สะท้อนงานจริง
สองแบบ: unary และ streaming
หัวข้อที่มีชื่อว่า “สองแบบ: unary และ streaming”เพราะ 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 interceptor แบบ minimal
หัวข้อที่มีชื่อว่า “logging interceptor แบบ minimal”ด้านล่างคือ 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))class LoggingInterceptor(grpc.ServerInterceptor): def intercept_service(self, continuation, handler_call_details): method = handler_call_details.method start = time.time() handler = continuation(handler_call_details) # the next link logging.info("method=%s registered", method) return handler # wrap handler.unary_unary to time each call in practice
server = grpc.server(executor, interceptors=[LoggingInterceptor()])// @grpc/grpc-js exposes interceptors on the client; on the server,// wrap handlers or use a middleware library such as nice-grpc.const loggingUnary: ServerMiddleware = async function* (call, ctx) { const start = Date.now(); try { return yield* call.next(call.request, ctx); } finally { console.log(`method=${ctx.path} took=${Date.now() - start}ms`); }};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 พัง