Spring Cloud Gateway
Spring Cloud Gateway
في الدرس السابق تعلّمت الحجة النظرية لوجود API Gateway. هذا الدرس عملي بالكامل: ستبني بوابة تعمل فعلياً باستخدام Spring Cloud Gateway (التنفيذ التفاعلي غير المحجوب المبني على Spring WebFlux ومشروع Reactor). بنهاية الدرس ستفهم كيف تُعلن المسارات، وتُركّب الشروط، وتُطبّق الفلاتر، وتتعامل مع التداعيات الأمنية والتشغيلية لكل قرار.
إضافة التبعية
Spring Cloud Gateway هو مشغّل (starter) منفصل. أنشئ مشروع Spring Boot 3 جديداً (أو وحدة) وأضفه إلى pom.xml:
spring-boot-starter-web إلى جانب مشغّل البوابة. Spring Cloud Gateway مبني على WebFlux (تفاعلي وغير محجوب). دمجه مع spring-boot-starter-web القائم على Servlet في نفس مسار الفئات يُسبب تعارضاً عند بدء التشغيل. استخدم مشغّل البوابة فقط؛ إذا احتجت الأمان أضف spring-boot-starter-security وسيُضبط المتغير التفاعلي من Spring Security تلقائياً.
المسار: اللبنة الأساسية
يمثّل المسار (Route) المفهوم الجوهري في Spring Cloud Gateway. لكل مسار ثلاثة أجزاء:
- المعرّف (ID) — سلسلة نصية فريدة تُستخدم في السجلات والمقاييس.
- عنوان URI — الوجهة التي يُوجَّه إليها الطلب عند المطابقة.
- الشروط (Predicates) — شروط يجب أن تتحقق جميعها لكي يتطابق المسار مع الطلب.
- الفلاتر (Filters) — تحويلات تُطبَّق على الطلب قبل إعادة توجيهه أو على الاستجابة قبل إعادتها.
يمكن إعلان المسارات في application.yml (الأكثر شيوعاً)، أو في حبة Java من نوع RouteLocator، أو ديناميكياً عبر عميل الاكتشاف. ابدأ بـ YAML:
ما يفعله هذا: أي طلب يبدأ مساره بـ /api/orders/ يُوجَّه إلى http://localhost:8081. يُزيل فلتر StripPrefix=1 المقطع الأول من المسار (/api) قبل إعادة التوجيه، فيتحوّل الطلب GET /api/orders/42 إلى GET /orders/42 عند الخدمة الخلفية.
الشروط (Predicates) بالتفصيل
الشرط هو اختبار منطقي على كائن ServerWebExchange الوارد. يأتي Spring Cloud Gateway مزوَّداً بمجموعة غنية من الشروط المدمجة. الشروط المتعددة على مسار واحد تُجمع بعملية AND.
شرط Path — يطابق على مسار URL باستخدام أحرف Ant البديلة:
شرط Method — يُقيّد بفعل HTTP:
شرط Header — يطابق عندما يوجد رأس وقيمته تستوفي تعبيراً نمطياً:
شرط Query — يطابق عندما يوجد معامل استعلام (ويطابق تعبيراً نمطياً اختيارياً):
شروط After / Before / Between — توجيه ذو نافذة زمنية، مفيد للصيانة المجدولة أو علامات الميزات:
شرط Weight — يُستخدم لنشر canary؛ يُوجّه نسبة مئوية من حركة المرور إلى إصدار جديد:
Path=/** دائماً في النهاية.
فلاتر البوابة (Gateway Filters) بالتفصيل
تعمل الفلاتر على كائن التبادل. يُطبَّق GatewayFilter على مسار واحد؛ ويُطبَّق GlobalFilter على كل مسار. تغطي الفلاتر المدمجة أكثر احتياجات البوابة شيوعاً.
AddRequestHeader — حقن رأس قبل إعادة التوجيه:
تستطيع الخدمات الخلفية التحقق من هذا الرأس للتأكد من أن الطلبات مرّت عبر البوابة لا مباشرةً.
AddResponseHeader — حقن رأس في طريق العودة:
RewritePath — إعادة كتابة المسار بتعبيرات نمطية كاملة، أقوى من StripPrefix:
RequestRateLimiter — تحديد معدل الطلبات المدمج عبر خوارزمية دلو الرموز مع Redis. أضف مشغّل Redis التفاعلي ثم اضبط:
يحدّد مُحلل المفتاح (key resolver) كيفية تمييز المُستدعي. أبسط مُحلل يستخدم عنوان IP البعيد:
في الإنتاج، قم بالتحليل على موضوع JWT أو مفتاح API بدلاً من IP كي لا تتشارك عناوين NAT نفس الدلو بشكل غير عادل.
فلتر CircuitBreaker — يتكامل مع Resilience4j لفتح الدائرة عند فشل الخدمة الخلفية:
كتابة GatewayFilter مخصص
عندما تكون الفلاتر المدمجة غير كافية يمكنك كتابة الخاص بك. نفّذ GatewayFilterFactory وسجّله كحبة Spring:
استخدم المصنع بالاسم في YAML (اسم الفئة بدون لاحقة GatewayFilterFactory):
GatewayFilterFactory بحسب اسم الفئة. يجب أن يتطابق مفتاح YAML بالضبط مع البادئة قبل GatewayFilterFactory (مع مراعاة حالة الأحرف). خطأ في هذا يُنتج استثناء FilterDefinitionNotFoundException عند بدء التشغيل.
مسارات Java DSL
YAML مريح للمسارات البسيطة، لكن Java DSL يمنحك دعم IDE الكامل والأمان في أنواع البيانات والمنطق البرمجي (مثل قراءة المسارات من قاعدة بيانات):
يُخبر نظام URI ذو البادئة lb://order-service البوابةَ بحل اسم الخدمة عبر سجل Eureka وتوزيع التحميل عبر مثيلاتها باستخدام Spring Cloud LoadBalancer — الآلية نفسها التي تناولناها في الدرس الثالث.
التداعيات الأمنية
- لا تثق أبداً بالرؤوس التي يُرسلها المُستدعون. إذا اعتمدت الخدمات الخلفية على الرؤوس
X-User-IdأوX-Rolesالتي تُمرّرها البوابة، فاحذف هذه الرؤوس من الطلب الوارد أولاً ثم أضف رؤوسك الخاصة بعد المصادقة. وإلا يستطيع أي عميل تزويرها. - البوابة ليست جداراً نارياً. تأكد من أن الخدمات الخلفية غير قابلة للوصول مباشرةً من خارج شبكتك الخاصة. البوابة هي نقطة الدخول الوحيدة بالسياسة وبطبولوجيا الشبكة — لا بالاتفاقية وحسب.
- سجّل معرّفات الارتباط. أضف
GlobalFilterيُولّد UUID لكل طلب، يُرفق به كرأس (X-Correlation-Id)، ويُمرّره إلى الخدمات الخلفية. هذا يُتيح التتبع الموزع عبر خدمات متعددة.
الخلاصة
يُضبط Spring Cloud Gateway حول المسارات (routes)، تُطابق كل منها الطلبات الواردة عبر شروط (predicates) قابلة للتركيب وتُحوّلها عبر فلاتر (filters). تغطي الشروط المسار والأسلوب والرأس والاستعلام والوقت وتقسيم حركة المرور بالوزن. تتعامل الفلاتر المدمجة مع إعادة كتابة المسارات وحقن الرؤوس وتحديد معدل الطلبات وقطع الدائرة؛ أما حبوب GatewayFilterFactory المخصصة فتُتيح إضافة أي منطق عابر للخدمات تحتاجه. استخدم نظام URI ذا البادئة lb:// للتكامل السلس مع الخدمات المسجلة في Eureka. في الدرس القادم سترى كيف تُطبَّق المصادقة وCORS والتسجيل وتحديد معدل الطلبات فوق البوابة كاهتمامات عابرة للخدمات.