در فضای مدرن سیستمهای توزیعشده، میکروسرویسها به شدت به ارتباطات کارآمد و امن وابستهاند. gRPC گوگل به دلیل عملکرد بالا، تایپ قوی و پشتیبانی بومی از HTTP/2 به استاندارد پیشفرض برای ارتباطات داخلی سرویس-به-سرویس تبدیل شده است. با این حال، ساخت یک معماری میکروسرویس مقاوم نیازمند مواردی فراتر از تعریف پیامهای protobuf است. این امر مستلزم مدیریت یکپارچه نگرشهای سراسری مانند امنیت، قابلیت مشاهده (Observability) و مدیریت خطا در تمام سرویسها است.
اینجاست که اینترسپتورهای gRPC وارد عمل میشوند. این اینترسپتورها به عنوان میانافزار عمل کرده و به شما اجازه میدهند تا به چرخه حیات سرور و کلاینت gRPC متصل شوید، که این امکان را فراهم میکند منطق مربوط به احراز هویت، ثبت رویداد، متریکها و تلاش مجدد را بدون شلوغ کردن منطق تجاری اصلی تزریق کنید. در این مقاله، نحوه پیادهسازی دو اینترسپتور حیاتی در Go را بررسی خواهیم کرد: یکی برای احراز هویت و دیگری برای ثبت رویدادهای درخواست با جزئیات.
درک اینترسپتورهای gRPC
اینترسپتورها در gRPC توابعی هستند که دور فراخوانیهای RPC میپیچند. آنها قبل از اجرای handler اصلی (در سمت سرور) یا پس از اینکه کلاینت درخواستی ارسال میکند (در سمت کلاینت) اجرا میشوند. این الگو مشابه میانافزار در فریمورکهای HTTP مانند Gin یا Echo است، اما در لایه انتقال RPC عمل میکند.
برای RPCهای یونری سمت سرور (درخواست-پاسخ استاندارد)، امضای اینترسپتور در Go به صورت زیر است:
func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// منطق پیش از handler
resp, err := handler(ctx, req)
// منطق پس از handler
return resp, err
}
با زنجیر کردن چندین اینترسپتور، میتوانید یک پایپلاین قدرتمند ایجاد کنید. برای مثال، ممکن است یک اینترسپتور ثبت رویداد داشته باشید که یک اینترسپتور احراز هویت را در بر میگیرد، که آن نیز منطق تجاری اصلی شما را در بر میگیرد.
پیادهسازی اینترسپتور احراز هویت
امنیت بسیار مهم است. یک الگوی رایج، اعتبارسنجی یک JWT (توکن وب JSON) است که در متادیتای درخواست gRPC ارسال میشود، قبل از اجازه دسترسی به نقاط پایانی محافظتشده. اینترسپتور زیر نحوه استخراج توکن از هدر Authorization، اعتبارسنجی امضای آن و تزریق ادعاهای کاربر (claims) به context را برای handlerهای بعدی نشان میدهد.
func AuthInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
token := tokens[0]
// فرض بر این است که validateJWT ادعاها یا خطا را برمیگرداند
claims, err := validateJWT(token)
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
// تزریق شناسه کاربر به context
ctx = context.WithValue(ctx, "userID", claims.UserID)
return handler(ctx, req)
}
این رویکرد تضمین میکند که هر نقطه پایانی محافظتشده میتواند کاربر احراز هویتشده را تنها با فراخوانی ctx.Value("userID") بازیابی کند، که منطق تجاری را تمیز و متمرکز نگه میدارد.
پیادهسازی اینترسپتور ثبت رویداد درخواست
قابلیت مشاهده (Observability) به اندازه امنیت حیاتی است. در یک معماری میکروسرویس، ردیابی جریان درخواستها بین سرویسهای مختلف برای اشکالزدایی و پایش عملکرد ضروری است. یک اینترسپتور ثبت رویداد میتواند زمان شروع، نام متد، پیکربندی درخواست (سنتز شده) و وضعیت پاسخ را ثبت کند.
func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
log.Printf("Started %s", info.FullMethod)
resp, err := handler(ctx, req)
if err != nil {
log.Printf("Finished %s in %v with error: %v", info.FullMethod, time.Since(start), err)
return resp, err
}
log.Printf("Finished %s in %v", info.FullMethod, time.Since(start))
return resp, nil
}
این اینترسپتور ساده بینش فوری درباره مدت زمانی که نقاط پایانی شما برای پاسخ دادن نیاز دارند ارائه میدهد و به شناسایی سریع درخواستهای ناموفق کمک میکند. در محیطهای تولید، احتمالاً این مورد را با یکپارچهسازی با کتابخانههای ثبت رویداد ساختاریافته مانند Zap یا Logrus و ارسال متریکها به Prometheus بهبود خواهید بخشید.
همه را در کنار هم قرار دادن
برای استفاده از این اینترسپتورها، آنها را هنگام راهاندازی سرور gRPC ثبت میکنید. میتوانید آنها را به عنوان گزینهها به grpc.NewServer() ارسال کنید. ترتیب اهمیت دارد: اولین اینترسپتور در لیست، اولین موردی است که اجرا میشود، به این معنی که دومین مورد را در بر میگیرد و به همین ترتیب.
server := grpc.NewServer(
grpc.UnaryInterceptor(LoggingInterceptor),
grpc.UnaryInterceptor(AuthInterceptor),
)
نتیجهگیری
پیادهسازی اینترسپتورهای gRPC در Go یک بهترین شیوه برای ساخت میکروسرویسهای قابل نگهداری و امن است. با تفکیک نگرشهایی مانند احراز هویت و ثبت رویداد به اینترسپتورهای قابل استفاده مجدد، منطق اصلی برنامه خود را تمیز نگه میدارید و رفتار یکپارچه را در تمام سرویسهای خود تضمین میکنید. چه در حال امنسازی API خود با JWTها باشید و چه در حال پایش عملکرد با لاگهای دقیق، اینترسپتورها مکانیزم انعطافپذیر و قدرتمندی را فراهم میکنند که برای مدیریت مؤثر نگرشهای سراسری مورد نیاز است.