الخطوة 6 في الواجهة الخلفية: CORS

مكّن تطبيق الجوال من الاتصال بالواجهة الخلفية عبر إعداد CORS.

ما ستتعلمه

  • ما هو CORS ولماذا وُجد
  • كيف تمكّن CORS في Flask
  • تقييد CORS لأغراض الأمان

الخطوة 1: ما هو CORS؟

CORS (Cross-Origin Resource Sharing) ميزة أمان في المتصفح. وهي تمنع صفحة ويب من أصل (origin) معين (مثل http://localhost:8081) من إرسال طلبات إلى أصل مختلف (مثل http://localhost:5000).

في هذا المشروع: - يعمل تطبيق React Native على http://localhost:8081 (خادم تطوير Expo) - تعمل واجهتك الخلفية في Flask على http://localhost:5000 - هذان أصلان مختلفان (منفذان مختلفان)، لذلك يحجب المتصفح الطلب

ملاحظة عن React Native: على جهاز حقيقي، لا ينطبق CORS بالطريقة نفسها. لكن خادم تطوير Expo يعمل في بيئة قائمة على المتصفح، وفيها يكون CORS مهمًا.

الخطوة 2: تثبيت flask-cors

إذا كنت قد ثبّتته بالفعل في الخطوة 1 فأنت جاهز. وإن لم تفعل:

pip install flask-cors

الخطوة 3: تحديث init.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from dotenv import load_dotenv

load_dotenv()

db = SQLAlchemy()


def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///chat.db"
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

    CORS(app)

    db.init_app(app)

    from .routes import api
    app.register_blueprint(api, url_prefix="/api")

    with app.app_context():
        db.create_all()

    return app


if __name__ == "__main__":
    app = create_app()
    app.run(debug=True, port=5000)

السطر الواحد CORS(app) يمكّن CORS لكل الأصول، ولكل الطرق، ولكل الترويسات.

الخطوة 4: تقييد CORS (اختياري لكنه مُستحسن)

بدلًا من السماح بأي أصل، اقتصر على تطبيق جوالك:

CORS(app, origins=["http://localhost:8081", "http://localhost:19006", "exp://192.168.1.100:19000"])

الخطوة 5: اختبار CORS

cd aiproject/backend
source venv/bin/activate
python app/__init__.py

افحص الترويسات:

curl -I http://127.0.0.1:5000/api/status

ابحث عن هذه الترويسة في الاستجابة:

Access-Control-Allow-Origin: *

كيف يعمل CORS

  1. يرسل تطبيق جوالك طلبًا إلى الواجهة الخلفية
  2. يتحقق المتصفح (أو Expo) مما إذا كانت الاستجابة تحمل ترويسة Access-Control-Allow-Origin
  3. إذا طابقت الترويسة أصل التطبيق، يُسمح بالطلب
  4. وإذا لم تطابق، يحجب المتصفح الطلب

يضيف CORS(app) هذه الترويسة تلقائيًا إلى كل استجابة.

الخطوة 6: إضافة المراقبة

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

نقطة نهاية فحص الصحة

أضف إلى backend/app/__init__.py:

@app.route("/api/health")
def health():
    db_ok = False
    try:
        with app.app_context():
            db.session.execute(db.text("SELECT 1"))
            db_ok = True
    except Exception:
        db_ok = False
    return jsonify({
        "status": "ok",
        "version": "1.0.0",
        "database": "connected" if db_ok else "disconnected",
        "metrics": get_metrics(),
    })

اختبرها:

curl http://127.0.0.1:5000/api/health
# → {"status":"ok","version":"1.0.0","database":"connected","metrics":{...}}

نقطة نهاية المقاييس

تتابع نقطة النهاية /api/metrics عدد الطلبات. أضف إلى backend/app/metrics.py:

_request_count = 0

def track_request(response):
    global _request_count
    _request_count += 1
    return response

def register_metrics(app):
    app.after_request(track_request)

    @app.route("/api/metrics")
    def metrics():
        return jsonify({"total_requests": _request_count})

الآن كل طلب يزيد العداد. زر /api/metrics لترى عدد الطلبات التي عالجها خادمك.

تسجيل منظّم

بدلًا من عبارات print()، استخدم تسجيلًا منظّمًا. أضف إلى backend/app/logging_config.py:

import logging
import sys

def setup_logging():
    logger = logging.getLogger("aiproject")
    logger.setLevel(logging.INFO)
    handler = logging.StreamHandler(sys.stdout)
    handler.setFormatter(logging.Formatter(
        "%(asctime)s [%(levelname)s] %(message)s"
    ))
    logger.addHandler(handler)
    return logger

ثم في routes.py:

logger = logging.getLogger("aiproject")
logger.info("Chat completed: user_msg=%s ai_msg=%s", user_msg.id, ai_msg.id)

تظهر السجلات مع طوابع زمنية:

2026-05-04 14:30:22 [INFO] Chat completed: user_msg=5 ai_msg=6

الخطوة 7: إعداد خطافات pre-commit

تشغّل خطافات pre-commit فحوصًا تلقائيًا قبل كل git commit. وإذا فشل أحد الفحوص، يُحجب الـ commit، وهذا يمنع دخول كود سيئ إلى مستودعك.

ثبّت pre-commit:

pip install pre-commit

ثبّت الخطافات في مستودعك:

cd aiproject
pre-commit install

الآن في كل مرة تشغّل فيها git commit، ينفّذ pre-commit ما يلي: 1. ruff — فحص الكود 2. ruff-format — تنسيق الكود تلقائيًا 3. التحقق من المسافات الزائدة في نهاية الأسطر 4. التحقق من الأسطر الجديدة الناقصة في نهاية الملفات

جرّبه:

git add .
git commit -m "Test pre-commit hooks"

إذا وجد ruff مشكلة، يُحجب الـ commit. أصلح المشكلة وحاول مرة أخرى.

التحقق

  1. الواجهة الخلفية تعمل
  2. يُظهر curl -I الترويسة Access-Control-Allow-Origin: *
  3. يُظهر curl /api/health أن قاعدة البيانات متصلة
  4. تعمل خطافات pre-commit قبل كل commit
  5. يستطيع تطبيق جوالك الآن الاتصال بالواجهة الخلفية (سنختبر ذلك في قسم الجوال)