أتقن برمجيات FastAPI الوسيطة من الألف إلى الياء. يغطي هذا الدليل المتعمق البرمجيات الوسيطة المخصصة، والمصادقة، وتسجيل العمليات، ومعالجة الأخطاء، وأفضل الممارسات لبناء واجهات برمجة تطبيقات قوية.
برمجيات FastAPI الوسيطة في بايثون: دليل شامل لمعالجة الطلبات والاستجابات
في عالم تطوير الويب الحديث، يعد الأداء والأمان وقابلية الصيانة أمورًا بالغة الأهمية. اكتسب إطار عمل FastAPI في بايثون شعبية سريعة بفضل سرعته المذهلة وميزاته سهلة الاستخدام للمطورين. إحدى أقوى ميزاته والتي يساء فهمها أحيانًا هي البرمجيات الوسيطة (middleware). تعمل البرمجيات الوسيطة كحلقة وصل حاسمة في سلسلة معالجة الطلبات والاستجابات، مما يسمح للمطورين بتنفيذ التعليمات البرمجية، وتعديل البيانات، وفرض القواعد قبل أن يصل الطلب إلى وجهته أو قبل إرسال الاستجابة مرة أخرى إلى العميل.
صُمم هذا الدليل الشامل لجمهور عالمي من المطورين، بدءًا من المبتدئين في FastAPI وصولًا إلى المحترفين المتمرسين الذين يتطلعون إلى تعميق فهمهم. سنستكشف المفاهيم الأساسية للبرمجيات الوسيطة، ونوضح كيفية بناء حلول مخصصة، ونتناول حالات استخدام عملية وواقعية. بنهاية هذا الدليل، ستكون مجهزًا للاستفادة من البرمجيات الوسيطة لبناء واجهات برمجة تطبيقات أكثر قوة وأمانًا وكفاءة.
ما هي البرمجيات الوسيطة في سياق أطر عمل الويب؟
قبل الغوص في الشيفرة البرمجية، من الضروري فهم المفهوم. تخيل دورة الطلب-الاستجابة في تطبيقك كخط أنابيب أو خط تجميع. عندما يرسل العميل طلبًا إلى واجهة برمجة التطبيقات الخاصة بك، فإنه لا يصل فورًا إلى منطق نقطة النهاية. بدلًا من ذلك، يمر عبر سلسلة من خطوات المعالجة. وبالمثل، عندما تنشئ نقطة النهاية الخاصة بك استجابة، فإنها تعود عبر هذه الخطوات قبل الوصول إلى العميل. مكونات البرمجيات الوسيطة هي هذه الخطوات ذاتها في خط الأنابيب.
هناك تشبيه شائع وهو نموذج البصلة. جوهر البصلة هو منطق عمل تطبيقك (نقطة النهاية). كل طبقة من البصلة تحيط بالجوهر هي قطعة من البرمجيات الوسيطة. يجب على الطلب أن يقشر كل طبقة خارجية للوصول إلى الجوهر، وتعود الاستجابة للخارج عبر نفس الطبقات. يمكن لكل طبقة فحص وتعديل الطلب في طريقه للدخول والاستجابة في طريقها للخروج.
في جوهرها، البرمجية الوسيطة هي دالة أو فئة يمكنها الوصول إلى كائن الطلب، وكائن الاستجابة، والبرمجية الوسيطة التالية في دورة الطلب-الاستجابة للتطبيق. وتشمل أغراضها الأساسية ما يلي:
- تنفيذ التعليمات البرمجية: أداء إجراءات لكل طلب وارد، مثل تسجيل العمليات أو مراقبة الأداء.
- تعديل الطلب والاستجابة: إضافة ترويسات، أو ضغط أجسام الاستجابة، أو تحويل تنسيقات البيانات.
- اختصار الدورة: إنهاء دورة الطلب-الاستجابة مبكرًا. على سبيل المثال، يمكن لبرمجية وسيطة للمصادقة حظر طلب غير مصادق عليه قبل أن يصل إلى نقطة النهاية المقصودة.
- إدارة الاهتمامات العامة: معالجة الاهتمامات الشاملة مثل معالجة الأخطاء، و CORS (مشاركة الموارد عبر المصادر)، وإدارة الجلسات في مكان مركزي.
تم بناء FastAPI فوق مجموعة أدوات Starlette، والتي توفر تنفيذًا قويًا لمعيار ASGI (واجهة بوابة الخادم غير المتزامنة). تعد البرمجيات الوسيطة مفهومًا أساسيًا في ASGI، مما يجعلها مكونًا أساسيًا في نظام FastAPI البيئي.
أبسط أشكالها: برمجيات FastAPI الوسيطة باستخدام المُزخرف (Decorator)
يوفر FastAPI طريقة مباشرة لإضافة البرمجيات الوسيطة باستخدام المُزخرف @app.middleware("http"). هذا مثالي للمنطق البسيط والمستقل الذي يحتاج إلى التشغيل لكل طلب HTTP.
لننشئ مثالًا كلاسيكيًا: برمجية وسيطة لحساب وقت المعالجة لكل طلب وإضافته إلى ترويسات الاستجابة. هذا مفيد للغاية لمراقبة الأداء.
مثال: برمجية وسيطة لحساب وقت المعالجة
أولاً، تأكد من أن لديك FastAPI وخادم ASGI مثل Uvicorn مثبتين:
pip install fastapi uvicorn
الآن، لنكتب الشيفرة البرمجية في ملف باسم main.py:
import time
from fastapi import FastAPI, Request
app = FastAPI()
# تعريف الدالة الوسيطة
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
# تسجيل وقت البدء عند وصول الطلب
start_time = time.time()
# المتابعة إلى البرمجية الوسيطة التالية أو نقطة النهاية
response = await call_next(request)
# حساب وقت المعالجة
process_time = time.time() - start_time
# إضافة الترويسة المخصصة إلى الاستجابة
response.headers["X-Process-Time"] = str(process_time)
return response
@app.get("/")
async def root():
# محاكاة بعض العمل
time.sleep(0.5)
return {"message": "Hello, World!"}
لتشغيل هذا التطبيق، استخدم الأمر:
uvicorn main:app --reload
الآن، إذا أرسلت طلبًا إلى http://127.0.0.1:8000 باستخدام أداة مثل cURL أو عميل API مثل Postman، سترى ترويسة جديدة في الاستجابة، X-Process-Time، بقيمة تقارب 0.5 ثانية.
تفكيك الشيفرة البرمجية:
@app.middleware("http"): يسجل هذا المزخرف دالتنا كقطعة من برمجيات HTTP الوسيطة.async def add_process_time_header(request: Request, call_next):: يجب أن تكون الدالة الوسيطة غير متزامنة. تستقبل كائنRequestالوارد ودالة خاصة،call_next.response = await call_next(request): هذا هو السطر الأكثر أهمية.call_nextيمرر الطلب إلى الخطوة التالية في خط الأنابيب (إما برمجية وسيطة أخرى أو عملية المسار الفعلية). يجب عليك `await` هذا الاستدعاء. النتيجة هي كائنResponseالذي تم إنشاؤه بواسطة نقطة النهاية.response.headers[...] = ...: بعد استلام الاستجابة من نقطة النهاية، يمكننا تعديلها، وفي هذه الحالة، عن طريق إضافة ترويسة مخصصة.return response: أخيرًا، يتم إرجاع الاستجابة المعدلة لإرسالها إلى العميل.
صياغة برمجياتك الوسيطة المخصصة باستخدام الفئات (Classes)
في حين أن نهج المزخرف بسيط، إلا أنه قد يصبح محدودًا في السيناريوهات الأكثر تعقيدًا، خاصة عندما تتطلب برمجياتك الوسيطة تكوينًا أو تحتاج إلى إدارة بعض الحالات الداخلية. لهذه الحالات، يدعم FastAPI (عبر Starlette) البرمجيات الوسيطة القائمة على الفئات باستخدام BaseHTTPMiddleware.
يوفر النهج القائم على الفئات بنية أفضل، ويسمح بحقن التبعية في مُنشئها (constructor)، وهو بشكل عام أكثر قابلية للصيانة للمنطق المعقد. يكمن المنطق الأساسي في دالة dispatch غير متزامنة.
مثال: برمجية وسيطة للمصادقة على مفتاح API قائمة على الفئات
لنقم ببناء برمجية وسيطة أكثر عملية لتأمين واجهة برمجة التطبيقات الخاصة بنا. ستتحقق من وجود ترويسة معينة، X-API-Key، وإذا كان المفتاح غير موجود أو غير صالح، فستعيد فورًا استجابة خطأ 403 Forbidden. هذا مثال على "اختصار" مسار الطلب.
في main.py:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.responses import Response
# قائمة بمفاتيح API الصالحة. في تطبيق حقيقي، ستأتي هذه من قاعدة بيانات أو مخزن آمن.
VALID_API_KEYS = ["my-super-secret-key", "another-valid-key"]
class APIKeyMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
api_key = request.headers.get("X-API-Key")
if api_key not in VALID_API_KEYS:
# قطع مسار الطلب وإرجاع استجابة خطأ
return JSONResponse(
status_code=403,
content={"detail": "Forbidden: Invalid or missing API Key"}
)
# إذا كان المفتاح صالحًا، فاستمر في معالجة الطلب
response = await call_next(request)
return response
app = FastAPI()
# إضافة البرمجية الوسيطة إلى التطبيق
app.add_middleware(APIKeyMiddleware)
@app.get("/")
async def root():
return {"message": "Welcome to the secure zone!"}
الآن، عند تشغيل هذا التطبيق:
- الطلب بدون ترويسة
X-API-Key(أو بقيمة خاطئة) سيستقبل رمز حالة 403 ورسالة خطأ JSON. - الطلب مع الترويسة
X-API-Key: my-super-secret-keyسينجح ويستقبل استجابة 200 OK.
هذا النمط قوي للغاية. لا تحتاج شيفرة نقطة النهاية في / إلى معرفة أي شيء عن التحقق من مفتاح API؛ هذا الاهتمام منفصل تمامًا في طبقة البرمجيات الوسيطة.
حالات استخدام شائعة وقوية للبرمجيات الوسيطة
البرمجيات الوسيطة هي الأداة المثالية للتعامل مع الاهتمامات الشاملة (cross-cutting concerns). دعنا نستكشف بعض حالات الاستخدام الأكثر شيوعًا وتأثيرًا.
1. تسجيل العمليات المركزي
يعد تسجيل العمليات الشامل أمرًا غير قابل للتفاوض للتطبيقات في بيئة الإنتاج. تسمح لك البرمجيات الوسيطة بإنشاء نقطة واحدة حيث تسجل المعلومات الهامة حول كل طلب والاستجابة المقابلة له.
مثال لبرمجية وسيطة للتسجيل:
import logging
from fastapi import FastAPI, Request
import time
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
start_time = time.time()
# تسجيل تفاصيل الطلب
logger.info(f"Incoming request: {request.method} {request.url.path}")
response = await call_next(request)
process_time = time.time() - start_time
# تسجيل تفاصيل الاستجابة
logger.info(f"Response status: {response.status_code} | Process time: {process_time:.4f}s")
return response
تقوم هذه البرمجية الوسيطة بتسجيل أسلوب الطلب والمسار عند وروده، ورمز حالة الاستجابة ووقت المعالجة الإجمالي عند خروجه. يوفر هذا رؤية لا تقدر بثمن حول حركة مرور تطبيقك.
2. معالجة الأخطاء الشاملة
بشكل افتراضي، سيؤدي استثناء غير معالج في شيفرتك البرمجية إلى خطأ 500 Internal Server Error، مما قد يكشف عن تتبعات المكدس وتفاصيل التنفيذ للعميل. يمكن لبرمجية وسيطة لمعالجة الأخطاء الشاملة التقاط جميع الاستثناءات، وتسجيلها للمراجعة الداخلية، وإرجاع استجابة خطأ موحدة وسهلة الاستخدام.
مثال لبرمجية وسيطة لمعالجة الأخطاء:
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import logging
logger = logging.getLogger(__name__)
app = FastAPI()
@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
try:
return await call_next(request)
except Exception as e:
logger.error(f"An unhandled error occurred: {e}", exc_info=True)
return JSONResponse(
status_code=500,
content={"detail": "An internal server error occurred. Please try again later."}
)
@app.get("/error")
async def cause_error():
return 1 / 0 # هذا سيثير خطأ ZeroDivisionError
مع وجود هذه البرمجية الوسيطة، لن يؤدي طلب إلى /error إلى تعطل الخادم أو كشف تتبع المكدس. بدلاً من ذلك، سيعيد برشاقة رمز حالة 500 مع جسم JSON نظيف، بينما يتم تسجيل الخطأ الكامل من جانب الخادم ليقوم المطورون بالتحقيق فيه.
3. CORS (مشاركة الموارد عبر المصادر)
إذا كان تطبيق الواجهة الأمامية الخاص بك يتم تقديمه من نطاق أو بروتوكول أو منفذ مختلف عن الواجهة الخلفية لـ FastAPI، فستقوم المتصفحات بحظر الطلبات بسبب سياسة نفس المصدر (Same-Origin Policy). CORS هي الآلية لتخفيف هذه السياسة. يوفر FastAPI برمجية CORSMiddleware مخصصة وعالية التكوين لهذا الغرض تحديدًا.
مثال لتكوين CORS:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# تعريف قائمة المصادر المسموح بها. استخدم "*" لواجهات برمجة التطبيقات العامة، ولكن كن محددًا لأمان أفضل.
origins = [
"http://localhost:3000",
"https://my-production-frontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True, # السماح بتضمين ملفات تعريف الارتباط في الطلبات عبر المصادر
allow_methods=["*"], # السماح بجميع أساليب HTTP القياسية
allow_headers=["*"], # السماح بجميع الترويسات
)
هذه واحدة من أولى قطع البرمجيات الوسيطة التي من المحتمل أن تضيفها إلى أي مشروع بواجهة أمامية منفصلة، مما يسهل إدارة سياسات عبر المصادر من موقع مركزي واحد.
4. ضغط GZip
يمكن أن يؤدي ضغط استجابات HTTP إلى تقليل حجمها بشكل كبير، مما يؤدي إلى أوقات تحميل أسرع للعملاء وتكاليف نطاق ترددي أقل. يتضمن FastAPI برمجية GZipMiddleware للتعامل مع هذا تلقائيًا.
مثال لبرمجية GZip الوسيطة:
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
app = FastAPI()
# إضافة برمجية GZip الوسيطة. يمكنك تعيين حجم أدنى للضغط.
app.add_middleware(GZipMiddleware, minimum_size=1000)
@app.get("/")
async def root():
# هذه الاستجابة صغيرة ولن يتم ضغطها.
return {"message": "Hello World"}
@app.get("/large-data")
async def large_data():
# سيتم ضغط هذه الاستجابة الكبيرة تلقائيًا بواسطة البرمجية الوسيطة.
return {"data": "a_very_long_string..." * 1000}
مع هذه البرمجية الوسيطة، سيتم ضغط أي استجابة أكبر من 1000 بايت إذا أشار العميل إلى أنه يقبل ترميز GZip (وهو ما تفعله جميع المتصفحات والعملاء الحديثة تقريبًا).
المفاهيم المتقدمة وأفضل الممارسات
كلما أصبحت أكثر كفاءة في استخدام البرمجيات الوسيطة، من المهم فهم بعض الفروق الدقيقة وأفضل الممارسات لكتابة شيفرة برمجية نظيفة وفعالة ويمكن التنبؤ بها.
1. ترتيب البرمجيات الوسيطة مهم!
هذه هي القاعدة الأكثر أهمية التي يجب تذكرها. تتم معالجة البرمجيات الوسيطة بالترتيب الذي تمت إضافتها به إلى التطبيق. أول برمجية وسيطة تمت إضافتها هي الطبقة الخارجية لـ "البصلة".
خذ بعين الاعتبار هذا الإعداد:
app.add_middleware(ErrorHandlingMiddleware) # الطبقة الخارجية
app.add_middleware(LoggingMiddleware)
app.add_middleware(AuthenticationMiddleware) # الطبقة الداخلية
سيكون تدفق الطلب على النحو التالي:
- تستقبل
ErrorHandlingMiddlewareالطلب. وتغلف استدعاءcall_nextالخاص بها في كتلةtry...except. - تستدعي
next، وتمرر الطلب إلىLoggingMiddleware. - تستقبل
LoggingMiddlewareالطلب، وتسجله، وتستدعيnext. - تستقبل
AuthenticationMiddlewareالطلب، وتتحقق من بيانات الاعتماد، وتستدعيnext. - يصل الطلب أخيرًا إلى نقطة النهاية.
- تعيد نقطة النهاية استجابة.
- تستقبل
AuthenticationMiddlewareالاستجابة وتمررها لأعلى. - تستقبل
LoggingMiddlewareالاستجابة، وتسجلها، وتمررها لأعلى. - تستقبل
ErrorHandlingMiddlewareالاستجابة النهائية وتعيدها إلى العميل.
هذا الترتيب منطقي: معالج الأخطاء في الخارج حتى يتمكن من التقاط الأخطاء من أي طبقة لاحقة، بما في ذلك البرمجيات الوسيطة الأخرى. طبقة المصادقة في الداخل بعمق، لذلك لا نكلف أنفسنا عناء تسجيل أو معالجة الطلبات التي سيتم رفضها على أي حال.
2. تمرير البيانات باستخدام request.state
في بعض الأحيان، تحتاج البرمجية الوسيطة إلى تمرير معلومات إلى نقطة النهاية. على سبيل المثال، قد تقوم برمجية وسيطة للمصادقة بفك تشفير JWT واستخراج معرف المستخدم. كيف يمكنها إتاحة معرف المستخدم هذا لدالة عملية المسار؟
الطريقة الخاطئة هي تعديل كائن الطلب مباشرة. الطريقة الصحيحة هي استخدام كائن request.state. إنه كائن بسيط فارغ تم توفيره لهذا الغرض تحديدًا.
مثال: تمرير بيانات المستخدم من البرمجية الوسيطة
# في دالة dispatch الخاصة بالبرمجية الوسيطة للمصادقة:
# ... بعد التحقق من صحة الرمز وفك تشفير المستخدم ...
user_data = {"id": 123, "username": "global_dev"}
request.state.user = user_data
response = await call_next(request)
# في نقطة النهاية الخاصة بك:
@app.get("/profile")
async def get_user_profile(request: Request):
current_user = request.state.user
return {"profile_for": current_user}
هذا يحافظ على نظافة المنطق ويتجنب تلويث مساحة أسماء كائن Request.
3. اعتبارات الأداء
بينما تكون البرمجيات الوسيطة قوية، فإن كل طبقة تضيف قدرًا صغيرًا من الحمل الزائد. بالنسبة للتطبيقات عالية الأداء، ضع هذه النقاط في اعتبارك:
- اجعلها خفيفة: يجب أن يكون منطق البرمجيات الوسيطة سريعًا وفعالًا قدر الإمكان.
- كن غير متزامن: إذا كانت برمجياتك الوسيطة بحاجة إلى إجراء عمليات إدخال/إخراج (مثل فحص قاعدة البيانات)، فتأكد من أنها
asyncبالكامل لتجنب حظر حلقة أحداث الخادم. - استخدمها لغرض: لا تضف برمجيات وسيطة لا تحتاجها. كل واحدة تضيف إلى عمق مكدس الاستدعاءات ووقت المعالجة.
4. اختبار برمجياتك الوسيطة
البرمجيات الوسيطة جزء حاسم من منطق تطبيقك ويجب اختبارها بدقة. يجعل TestClient في FastAPI هذا الأمر مباشرًا. يمكنك كتابة اختبارات ترسل طلبات مع وبدون الشروط المطلوبة (على سبيل المثال، مع وبدون مفتاح API صالح) والتأكد من أن البرمجية الوسيطة تتصرف كما هو متوقع.
مثال لاختبار APIKeyMiddleware:
from fastapi.testclient import TestClient
from .main import app # استيراد تطبيق FastAPI الخاص بك
client = TestClient(app)
def test_request_without_api_key_is_forbidden():
response = client.get("/")
assert response.status_code == 403
assert response.json() == {"detail": "Forbidden: Invalid or missing API Key"}
def test_request_with_valid_api_key_is_successful():
headers = {"X-API-Key": "my-super-secret-key"}
response = client.get("/", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "Welcome to the secure zone!"}
الخاتمة
تعد برمجيات FastAPI الوسيطة أداة أساسية وقوية لأي مطور يقوم ببناء واجهات برمجة تطبيقات ويب حديثة. إنها توفر طريقة أنيقة وقابلة لإعادة الاستخدام للتعامل مع الاهتمامات الشاملة، وفصلها عن منطق عملك الأساسي. من خلال اعتراض ومعالجة كل طلب واستجابة، تسمح لك البرمجيات الوسيطة بتنفيذ تسجيل عمليات قوي، ومعالجة مركزية للأخطاء، وسياسات أمان صارمة، وتحسينات في الأداء مثل الضغط.
من المزخرف البسيط @app.middleware("http") إلى الحلول المعقدة القائمة على الفئات، لديك المرونة لاختيار النهج الصحيح لاحتياجاتك. من خلال فهم المفاهيم الأساسية، وحالات الاستخدام الشائعة، وأفضل الممارسات مثل ترتيب البرمجيات الوسيطة وإدارة الحالة، يمكنك بناء تطبيقات FastAPI أنظف وأكثر أمانًا وقابلة للصيانة بشكل كبير.
الآن حان دورك. ابدأ في دمج البرمجيات الوسيطة المخصصة في مشروع FastAPI التالي الخاص بك وافتح مستوى جديدًا من التحكم والأناقة في تصميم واجهة برمجة التطبيقات الخاصة بك. الإمكانيات واسعة، وإتقان هذه الميزة سيجعلك بلا شك مطورًا أكثر فعالية وكفاءة.