404 Not Found

404 Not Found


nginx

الوسائط — اعتراض الطلبات والاستجابات وتحسينها

الوسائط مثل نقطة التفتيش الأمنية في المطار—كل راكب (طلب) يجب أن يمر عبر الجمارك والأمن وبوابة الصعود بالتسلسل، وفي أي من هذه المحطات قد يُسمح للراكب بالمتابعة أو إيقافه، بينما رحلة العودة (الاستجابة) تتبع الترتيب العكسي.

1. ما ستتعلمه


2. قصة Alice الحقيقية

(1) المشكلة: واجهة API تتعرض لارتفاعات حركة مرور خبيثة ولا يمكن تتبعها

بعد إطلاق Alice لواجهة PriceTracker API، اكتشفت أن عنوان IP معين كان يرسل 5000 طلب في الدقيقة، مما أدى إلى ارتفاع حمل قاعدة البيانات بشكل كبير. لزيادة الأمور سوءًا، عندما استدعى واجهة Bob الأمامية واجهة API من localhost:3000، تم حظر الطلبات من خلال سياسة CORS الخاصة بالمتصفح، مما أدى إلى فشل جميع الطلبات. احتاجت Alice إلى حل مشكلتي الوصول عبر الأصل وتحديد معدل الطلبات في وقت واحد، لكن Flask يفتقر إلى آلية وسائط موحدة.

(2) حلول وسائط FastAPI

توفر FastAPI/Starlette وسائط بنمط البصل، حيث تعالج كل طبقة مشكلة محددة: وسيطة CORS تعالج الطلبات عبر الأصل، ووسيطة تحديد المعدل تتحكم في تكرار الطلبات، ووسيطة التسجيل تسجل الطلبات—لا تتداخل مع بعضها البعض وتصبح سارية فور التسجيل.

PYTHON
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["http://localhost:3000"])

(3) العائد

حل Bob مشكلة الوصول عبر الأصل بواجهة الأمامية بخمسة أسطر فقط من التعليمات البرمجية. قللت وسيطة تحديد المعدل الطلبات من عناوين IP الخبيثة من 5000 إلى 1000 طلب في الدقيقة، وخصصت وسيطة التسجيل معرفًا فريدًا لكل طلب لسهولة التتبع.


3. نموذج البصل للوسائط

(1) مبدأ نموذج البصل

100%
flowchart TD
    Request[Client Request] --> CORS[CORS Middleware]
    CORS --> Logging[Logging Middleware]
    Logging --> RateLimit[Rate Limit Middleware]
    RateLimit --> App[FastAPI Application]
    App --> RateLimit2[Rate Limit Response]
    RateLimit2 --> Logging2[Logging Response]
    Logging2 --> CORS2[CORS Response]
    CORS2 --> Response[Client Response]
    
    RateLimit -.->|429 Too Many Requests| Reject[Short-circuit Rejection]
    Reject --> Logging2
الميزة الوصف
اتجاه الطلب من الخارج إلى الداخل (الطبقة الخارجية المسجلة أولاً)
اتجاه الاستجابة من الداخل إلى الخارج (عكس اتجاه الطلب)
قدرة الدائرة القصيرة أي وسيطة يمكنها إرجاع استجابة مبكرًا، دون الانتقال إلى الطبقة التالية
ترتيب التسجيل آخر وسيطة مسجلة هي أول من يعالج الطلب

(1) ▶مثال: ترتيب تسجيل الوسائط وترتيب التنفيذ

PYTHON
from fastapi import FastAPI, Request
import time

app = FastAPI()

# الوسيطة المسجلة أولاً = الطبقة الخارجية (تنفذ أولاً عند الطلب)
@app.middleware("http")
async def outer_middleware(request: Request, call_next):
    print("Outer: before request")
    response = await call_next(request)
    print("Outer: after response")
    return response

# الوسيطة المسجلة أخيرًا = الطبقة الداخلية (تنفذ أخيرًا عند الطلب)
@app.middleware("http")
async def inner_middleware(request: Request, call_next):
    print("Inner: before request")
    response = await call_next(request)
    print("Inner: after response")
    return response

@app.get("/test")
async def test():
    print("Handler: processing")
    return {"ok": True}

الناتج:

TEXT
Outer: before request
Inner: before request
Handler: processing
Inner: after response
Outer: after response

4. وسيطة CORS

(1) تكوين مشاركة الموارد عبر الأصل

CORS (مشاركة الموارد عبر الأصل) هي سياسة أمان المتصفح التي تقيّد صفحات الويب من أصول مختلفة من الوصول إلى واجهات API. عندما تصل واجهة Bob الأمامية (localhost:3000) إلى واجهة Alice API (localhost:8000)، يشكل ذلك طلبًا عبر الأصل.

(1) ▶مثال: تكوين CORS لبيئة التطوير

PYTHON
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # واجهة Bob الأمامية
    allow_credentials=True,
    allow_methods=["*"],  # جميع طرق HTTP
    allow_headers=["*"],  # جميع الرؤوس
)

الناتج:

TEXT
# تم التنفيذ بنجاح

(2) ▶مثال: تكوين CORS لبيئة الإنتاج

PYTHON
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware

app = FastAPI()

ALLOWED_ORIGINS = [
    "https://pricetracker.example.com",
    "https://admin.pricetracker.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=ALLOWED_ORIGINS,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

الناتج:

TEXT
# تم التنفيذ بنجاح
إعدادات CORS بيئة التطوير بيئة الإنتاج
allow_origins ["*"] أو localhost قائمة بأسماء النطاقات المحددة
allow_credentials True True (إذا كانت ملفات تعريف الارتباط مطلوبة)
allow_methods ["*"] الطرق المطلوبة فقط
allow_headers ["*"] الرؤوس المطلوبة فقط
max_age الافتراضي 3600 (تخزين الفحص المسبق المؤقت)

5. وسائط مخصصة

(1) وسيطة توقيت الطلبات

(1) ▶مثال: تسجيل وقت المعالجة لكل طلب

PYTHON
from fastapi import FastAPI, Request
import time

app = FastAPI()

@app.middleware("http")
async def timing_middleware(request: Request, call_next):
    start_time = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = f"{process_time:.4f}s"
    return response

@app.get("/products")
async def list_products():
    return [{"id": 1, "name": "Widget"}]

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) ▶مثال: وسيطة حقن معرف الطلب

PYTHON
import uuid
from fastapi import FastAPI, Request

app = FastAPI()

@app.middleware("http")
async def request_id_middleware(request: Request, call_next):
    request_id = str(uuid.uuid4())
    request.state.request_id = request_id  # إرفاق بحالة الطلب
    response = await call_next(request)
    response.headers["X-Request-ID"] = request_id
    return response

@app.get("/health")
async def health_check(request: Request):
    return {
        "status": "healthy",
        "request_id": request.state.request_id,
    }

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(3) ▶مثال: وسيطة تحديد معدل API (نسخة مبسطة)

PYTHON
from fastapi import FastAPI, Request, HTTPException
from collections import defaultdict
import time

app = FastAPI()

# محدد معدل بسيط في الذاكرة
rate_limits: dict[str, list[float]] = defaultdict(list)
RATE_LIMIT = 1000  # طلبات في الدقيقة
WINDOW = 60  # ثوانٍ

@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
    client_ip = request.client.host if request.client else "unknown"
    now = time.time()
    
    # تنظيف الإدخالات القديمة
    rate_limits[client_ip] = [
        t for t in rate_limits[client_ip] if now - t < WINDOW
    ]
    
    # التحقق من الحد
    if len(rate_limits[client_ip]) >= RATE_LIMIT:
        raise HTTPException(
            status_code=429,
            detail=f"Rate limit exceeded: {RATE_LIMIT} requests per {WINDOW}s",
        )
    
    rate_limits[client_ip].append(now)
    response = await call_next(request)
    response.headers["X-RateLimit-Limit"] = str(RATE_LIMIT)
    response.headers["X-RateLimit-Remaining"] = str(
        RATE_LIMIT - len(rate_limits[client_ip])
    )
    return response

الناتج:

TEXT
# تم تعريف الدالة بنجاح

6. وسائط قائمة على الأصناف واستدعاءات ASGI

(1) كيفية كتابة وسائط قائمة على الأصناف

لمنطق الوسائط الأكثر تعقيدًا، نوصي باستخدام نهج قائم على الأصناف يتلاعب مباشرة بنطاق ASGI واستقباله وإرساله.

(1) ▶مثال: وسيطة تسجيل طلبات قائمة على الأصناف

PYTHON
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response
import logging
import time

logger = logging.getLogger("pricetracker")

class LoggingMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next) -> Response:
        start = time.perf_counter()
        logger.info(f"Request: {request.method} {request.url.path}")
        
        response = await call_next(request)
        
        duration = time.perf_counter() - start
        logger.info(
            f"Response: {request.method} {request.url.path} "
            f"status={response.status_code} duration={duration:.4f}s"
        )
        return response

# تسجيل وسيطة قائمة على الأصناف
app = FastAPI()
app.add_middleware(LoggingMiddleware)

الناتج:

TEXT
# تم تعريف الدالة بنجاح

(2) مقارنة أنواع الوسائط

النهج السيناريوهات القابلة للتطبيق المزايا العيوب
@app.middleware("http") اعتراض بسيط أقل قدر من التعليمات البرمجية يعالج HTTP فقط
BaseHTTPMiddleware تعقيد متوسط قابل للتكوين يعالج HTTP فقط
صنف ASGI خالص تحكم كامل يعالج أيضًا WebSockets تعليمات برمجية معقدة

❓أسئلة شائعة

س لماذا يكون ترتيب تسجيل الوسائط مهمًا؟
ج الوسيطة المسجلة أخيرًا هي أول من يعالج الطلبات (الطبقة الداخلية). يجب تسجيل CORS أخيرًا (الطبقة الخارجية) لضمان أن جميع الاستجابات تتضمن رؤوس CORS. يجب تسجيل تحديد المعدل مبكرًا (الطبقات الداخلية) بحيث تمر الطلبات المرفوضة عبر CORS.
س هل يمكن استخدام @app.middleware و add_middleware معًا؟
ج نعم، لكن الوسيطة المسجلة بـ @app.middleware تأتي قبل add_middleware (على مستوى أعلى). يُوصى باستخدام نهج متسق.
س ما الحل الذي يجب استخدامه لتحديد المعدل في الإنتاج؟
ج تحديد المعدل البسيط في الذاكرة مناسب فقط لبيئات العملية الواحدة. في الإنتاج، استخدم Redis + SlowAPI أو وسيطة تحديد معدل Redis مخصصة، والتي تدعم البيئات الموزعة ومتعددة العمليات.
س هل يمكنني استخدام * لإعداد allow_origins في CORS؟
ج يمكن قبوله في بيئة التطوير، لكن في بيئة الإنتاج يجب تحديد نطاق محدد. لا يمكنك استخدام allow_credentials=True عند تحديد *؛ سيرفضه المتصفح.
س هل يمكن للوسيطة تعديل محتوى الطلب؟
ج نعم، لكن يجب قراءة المحتوى أولاً ثم إعادة بنائه. توجد مشكلة معروفة مع BaseHTTPMiddleware (استنزاف الدفق بعد قراءة المحتوى)؛ للسيناريوهات المعقدة، نوصي باستخدام وسيطة ASGI خالصة.
س كيف أقوم بتعطيل وسيطة محددة؟
ج لا يوجد مفتاح تبديل مدمج. نوصي باستخدام متغير البيئة: if settings.ENABLE_RATE_LIMIT: app.add_middleware(RateLimitMiddleware).

📖ملخص


📝تمارين

  1. مشكلة أساسية (الصعوبة ⭐): أضف وسيطة CORS إلى PriceTracker للسماح بالوصول عبر الأصل من http://localhost:3000، وتحقق في المتصفح أن واجهة Bob الأمامية يمكنها استدعاء API. تلميح: app.add_middleware(CORSMiddleware, ...)
  2. تمرين متقدم (الصعوبة ⭐⭐): نفّذ وسيطة توقيت طلبات تضيف X-Process-Time إلى رؤوس الاستجابة، واستخدم Swagger UI لعرض رؤوس الاستجابة. تلميح: @app.middleware("http") + time.perf_counter()
  3. تحدي (الصعوبة ⭐⭐⭐): نفّذ وسيطة تحديد معدل بناءً على IP (1000 طلب في الدقيقة). عند تجاوز الحد، أرجع رمز الحالة 429 ورأس الاستجابة Retry-After، مع عرض الحصة المتبقية في رأس الاستجابة. تلميح: defaultdict(list) + تنظيف نافذة الزمن

---|

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%