بايثون على جانب الخادم: من CGI إلى تطبيقات WSGI الحديثة
كانت واجهة البوابة المشتركة (Common Gateway Interface أو CGI) أول وسيلة لتوليد محتوى ديناميكي على الويب في تسعينيات القرن الماضي. ورغم أنها كانت ثورية في زمنها، فإن CGI تعاني من قيود جوهرية: فكل طلب يُطلق عملية Python جديدة، وهذا غير فعّال ويستحيل معه التوسع. وقد قدّمت WSGI (Web Server Gateway Interface) نموذجًا أكثر كفاءة في عام 2003. أما اليوم، فتهيمن أطر WSGI مثل Flask وخوادم ASGI مثل FastAPI على المشهد. يتتبع هذا الدليل هذا التطور ويوضح لك كيفية الانتقال من CGI إلى التقنيات الحديثة.
1. CGI: البدايات
ما هي CGI؟
CGI هو معيار يحدد كيف يشغّل خادم الويب (Apache وNginx) برنامجًا خارجيًا (سكربت CGI) لمعالجة طلب HTTP وتوليد استجابة.
مسار عمل CGI:
- يرسل العميل طلب HTTP إلى خادم الويب
- يبدأ خادم الويب عملية جديدة لتنفيذ سكربت CGI
- يقرأ السكربت المدخلات (متغيرات البيئة، stdin)
- يولّد السكربت المخرجات (ترويسات HTTP + المحتوى)
- يعيد خادم الويب الاستجابة إلى العميل
- تنتهي العملية
مثال CGI minimal
لنفترض خادم ويب مهيّأ لتنفيذ سكربتات .cgi الموجودة في /cgi-bin/.
#!/usr/bin/env python
# Fichier : /cgi-bin/hello.cgi
import cgi
import cgitb
# Activer les rapports d'erreur CGI
cgitb.enable()
print("Content-Type: text/html; charset=utf-8")
print()
print("<html><body>")
print("<h1>Bonjour depuis CGI !</h1>")
print("</body></html>")
# Résultat : le serveur web envoie une réponse HTML au navigateur
نموذج CGI مع المعالجة
#!/usr/bin/env python
# Fichier : /cgi-bin/form_handler.cgi
import cgi
import html
# Parser les données du formulaire
form = cgi.FieldStorage()
nom = form.getvalue("nom", "Visiteur inconnu")
# Échapper le contenu pour éviter les injections XSS
nom_safe = html.escape(nom)
print("Content-Type: text/html; charset=utf-8")
print()
print("<html><body>")
print(f"<h1>Bienvenue, {nom_safe} !</h1>")
print("</body></html>")
# Requête : GET /form_handler.cgi?nom=Alice
# Réponse : <h1>Bienvenue, Alice !</h1>
قيود CGI
| القيد | الأثر |
|---|---|
| عملية جديدة لكل طلب | تكلفة بدء تشغيل هائلة؛ يستحيل التوسع |
وحدة cgi أصبحت متقادمة ثم أُزيلت | منذ Python 3.11 مع تحذير؛ وحُذفت نهائيًا في Python 3.13 |
| لا حالة دائمة | تُعاد فتح الاتصالات بقاعدة البيانات مع كل طلب |
| لا تحسينات | لا ذاكرة تخزين مؤقت، ولا مجمّع عمّال (workers)، ولا تزامن |
| الأمان على عاتق المطوّر | لا حماية مدمجة ضد الحقن وCSRF وغيرها |
2. WSGI: نموذج أفضل
ما هي WSGI؟
WSGI (PEP 3333) هي واجهة موحّدة بين خادم الويب وتطبيق Python. وعلى عكس CGI، يبقى التطبيق في الذاكرة بين الطلبات، مما يلغي تكلفة بدء التشغيل.
تطبيق WSGI هو دالة أو فئة قابلة للاستدعاء (callable) تقبل وسيطين:
def application(environ, start_response):
"""
environ : dictionnaire contenant les infos de la requête HTTP
start_response : fonction pour envoyer les headers de réponse
Retour : itérable de bytes (le body de la réponse)
"""
status = "200 OK"
response_headers = [("Content-Type", "text/html; charset=utf-8")]
start_response(status, response_headers)
body = "<html><body><h1>Bonjour WSGI !</h1></body></html>"
return [body.encode("utf-8")]
# Résultat : application plus efficace que CGI
مثال WSGI مع نموذج
from urllib.parse import parse_qs
import html
def app(environ, start_response):
"""Application WSGI simple avec traitement de formulaire."""
method = environ["REQUEST_METHOD"]
path = environ.get("PATH_INFO", "/")
if method == "GET" and path == "/":
# Page d'accueil avec formulaire
body = """
<html>
<body>
<h1>Entrez votre nom</h1>
<form method="post" action="/submit">
<input type="text" name="nom" required>
<button type="submit">Envoyer</button>
</form>
</body>
</html>
""".encode("utf-8")
start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])
return [body]
elif method == "POST" and path == "/submit":
# Traiter les données du formulaire
content_length = int(environ.get("CONTENT_LENGTH", 0))
body = environ["wsgi.input"].read(content_length).decode("utf-8")
data = parse_qs(body)
nom = data.get("nom", ["Visiteur"])[0]
nom_safe = html.escape(nom)
response = f"<html><body><h1>Bonjour, {nom_safe} !</h1></body></html>".encode("utf-8")
start_response("200 OK", [("Content-Type", "text/html; charset=utf-8")])
return [response]
else:
start_response("404 Not Found", [("Content-Type", "text/plain")])
return [b"Page non trouvée"]
# Exécuter avec wsgiref (serveur de développement inclus)
# python -m wsgiref.simple_server --port 8000 app:app
# Accédez à http://localhost:8000
تشغيل تطبيق WSGI خام باستخدام wsgiref
# Fichier : myapp.py
def app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain; charset=utf-8")])
return [b"Hello from WSGI!"]
if __name__ == "__main__":
from wsgiref.simple_server import make_server
server = make_server("localhost", 8000, app)
print("Serveur WSGI lancé sur http://localhost:8000")
server.serve_forever()
# Exécution :
# python myapp.py
# Puis accédez à http://localhost:8000
3. http.server للنمذجة الأولية
توفّر وحدة http.server من المكتبة القياسية خادم HTTP بسيطًا، مفيدًا لاختبار نموذج أولي بسرعة.
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class MonHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.end_headers()
self.wfile.write(b"<h1>Accueil</h1>")
elif self.path == "/api/info":
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
data = {"status": "ok", "message": "Réponse JSON"}
self.wfile.write(json.dumps(data).encode("utf-8"))
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"Not found")
if __name__ == "__main__":
server = HTTPServer(("localhost", 8000), MonHandler)
print("Serveur lancé sur http://localhost:8000")
server.serve_forever()
# Résultat : serveur simple mais sans cache, gestion d'erreur minimale, etc.
4. CGI مقابل WSGI مقابل ASGI: مقارنة
| الجانب | CGI | WSGI | ASGI |
|---|---|---|---|
| العمليات | عملية جديدة لكل طلب | عملية دائمة واحدة | عامل واحد أو عدة عمّال |
| النموذج | متزامن (bash/Perl/Python) | متزامن (Python) | غير متزامن (async/await) |
| بدء التشغيل | بطيء جدًا (fork في نظام التشغيل) | سريع (دالة في الذاكرة) | سريع |
| إدارة الحالة | مستحيلة | ممكنة (ذاكرة تخزين مؤقت، قاعدة بيانات) | ممكنة |
| قابلية التوسع | ضعيفة | جيدة | ممتازة |
| الأطر | لا شيء (cgi فقط) | Flask, Django | FastAPI, Starlette |
| أمثلة الاستخدام | تاريخي (التسعينيات) | المدونات، المنتجات الأولية القابلة للتطبيق (MVP)، التطبيقات الكلاسيكية | واجهات API الفورية، WebSockets |
5. الانتقال من CGI إلى Flask
إليك كيفية إعادة هيكلة سكربت CGI بسيط إلى Flask حديث.
قبل: CGI
#!/usr/bin/env python
# cgi-bin/calculator.cgi
import cgi
form = cgi.FieldStorage()
a = float(form.getvalue("a", 0))
b = float(form.getvalue("b", 0))
operation = form.getvalue("op", "add")
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
else:
result = 0
print("Content-Type: text/html; charset=utf-8")
print()
print(f"<h1>Résultat : {result}</h1>")
بعد: Flask
# app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/calculate", methods=["GET", "POST"])
def calculate():
a = float(request.args.get("a", 0))
b = float(request.args.get("b", 0))
operation = request.args.get("op", "add")
if operation == "add":
result = a + b
elif operation == "subtract":
result = a - b
else:
result = 0
# Répondre en JSON (moderne) au lieu d'HTML
return jsonify({"a": a, "b": b, "operation": operation, "result": result})
if __name__ == "__main__":
# pip install flask
# python app.py
# Accédez à http://localhost:5000/calculate?a=10&b=5&op=add
app.run(debug=True, port=5000)
# Avantages :
# - Pas de parsing manuel
# - Réponse JSON structurée
# - Gestion d'erreur intégrée
# - Extensible facilement (middleware, templates, BD)
6. أفضل الممارسات
- تخلَّ عن CGI. فقد أُزيلت وحدة
cgiمنذ Python 3.13. ولا يوجد أي سبب للبقاء عليها. - اختر إطار WSGI (Flask وDjango) أو ASGI (FastAPI وStarlette). لا تكتب WSGI خامًا في بيئة الإنتاج.
- استخدم خادم تطبيقات: Gunicorn (لـ WSGI) أو Uvicorn (لـ ASGI)، وليس خادم التطوير.
- ثبّت إصدارات تبعياتك:
pip freeze > requirements.txtأو استخدم Poetry/Pipenv. - أدِر الحالة عبر قاعدة بيانات، وليس في الذاكرة. فالعمّال (workers) عديمو الحالة (stateless).
- اختبر تطبيقك. اكتب اختبارات الوحدة واختبارات التكامل.
الخاتمة
أصبحت CGI، رغم أهميتها التاريخية وأناقتها في زمنها، قديمة. وقد جلبت WSGI الكفاءة بفضل العمليات الدائمة. أما اليوم، فتذهب ASGI وأطر مثل FastAPI إلى أبعد من ذلك بفضل البرمجة غير المتزامنة والدعم الأصلي لـ WebSockets. وإذا كنت تصون شيفرة CGI موروثة، فإن الانتقال إلى Flask أو FastAPI خطوة مهمة للحفاظ على الأمان والأداء وقابلية الصيانة. أما في المشاريع الجديدة، فابدأ مباشرة بـ FastAPI للاستفادة من المعايير الحديثة وأفضل أداء.
نبذة عن InSkillCoach
خبير في التدريب والتقنيات
مدرّب متخصص في التقنيات المتقدمة والذكاء الاصطناعي، تدعمه شركة GNeurone Inc.
الشهادات:
- AWS Certified Solutions Architect – Professional
- شهادات Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش