واجهات RESTful API
درس 1 من 5
1واجهات RESTful API: استهلاكًا وبناءً وتأمينًا (Consuming, Building & Securing)
الصورة الكبيرةكيف تتحدث البرامج مع بعضها عبر HTTP: تقرأ عقد REST وأساسيات البروتوكول، تستهلك واجهة بـcurl وبايثون، تبني واحدة بـhttp.server ثم Flask، تؤمّنها، وتوثّقها بمعيار OpenAPI.
ماذا تشاهد في الرسم: العميل يرسل POST لينشئ مشروبًا فيرد الخادم 201، ثم يرسل GET ليقرأه فيرد 200 ومعه البيانات.
خارطة هذا الجزء
المفاهيم بالترتيب، اضغط أيًّا منها للقفز إليه:
طريقان إلى نفس الفكرة
الواجهة البرمجية مثل نافذة طلبات في مطعم: لا تدخل المطبخ، بل تكتب طلبك على ورقة بصيغة متفق عليها («أريد طبق رقم 3»)، فيعود إليك الطبق ومعه رمز يقول إن الطلب نجح أو أن الطبق غير موجود. REST مجموعة قواعد تجعل كل النوافذ تعمل بنفس الطريقة.
في REST كل مورد له رابط مثل /tasks/7، والفعل يحدد العملية: GET يقرأ وPOST ينشئ وPUT/PATCH يعدّل وDELETE يحذف. الرد يحمل رمز حالة (200 و201 و404 و500) وجسمًا بصيغة JSON. تستهلكها بـcurl أو requests، وتبنيها بـFlask، وتصفها بمعيار OpenAPI.
الأساسيات المطلوبة
القواميس والقوائم (درس هياكل البيانات)
جسم الطلب والرد غالبًا JSON، وهو في بايثون قاموس أو قائمة. من يرتاح مع dict.get سيرتاح مع الواجهات.
التسلسل JSON (درس التسلسل)
json.dumps و json.loads هما ما يحدث عند كل طلب ورد، وإن كانت المكتبات تخفيهما.
المزخرفات (درس أنماط التصميم)
Flask يربط الروابط بالدوال بمزخرف: @app.get("/drinks"). وستكتب مزخرفًا للمصادقة بنفسك.
درس بايثون وقواعد البيانات: قواعد البيانات
واجهة حقيقية تقف غالبًا أمام قاعدة بيانات؛ هنا نستخدم قاموسًا في الذاكرة ليبقى التركيز على HTTP، وتبديله بالجلسة التي تعلّمتها سهل.
المفاهيم الأساسية
الواجهة البرمجية (API) عقد بين برنامجين: «أرسل لي طلبًا بهذا الشكل أعطك ردًا بذاك». و REST أسلوب لتصميم هذا العقد فوق HTTP بحيث يكون موحّدًا ويسهل توسّعه.
1ما هو REST؟ القيود الستة
صاغ Roy Fielding هذه القيود في أطروحته. الواجهة التي تحترمها تسمى RESTful:
| القيد | معناه | ما يعنيه لك عمليًا |
|---|---|---|
| عميل-خادم | فصل الواجهة عن تخزين البيانات ليتطور كلٌّ مستقلًا. | تطبيق الجوال والموقع يستعملان نفس الخادم. |
| بلا حالة Stateless | كل طلب يحمل كل ما يلزم لفهمه؛ الخادم لا يتذكر «من أنت» بين الطلبات. | هوية المستخدم تُرسل مع كل طلب (ترويسة Authorization). |
| قابل للتخزين المؤقت | الرد يُوسَم بأنه قابل للتخزين أو لا. | GET للقراءة يمكن تخزينه؛ POST عادةً لا. |
| واجهة موحّدة | موارد لها معرّفات، وتُعالج بتمثيلات (مثل JSON)، ورسائل تصف نفسها. | /drinks/2 يعني دائمًا «المشروب 2»، والمنهج (GET/DELETE) يحدد الفعل. |
| نظام طبقي | كل مكوّن يرى الطبقة المجاورة فقط. | يمكن وضع موازِن حمل أو CDN أمام الخادم دون تغيير العميل. |
| شيفرة عند الطلب (اختياري) | العميل يمكنه تنزيل شيفرة تمدد قدراته. | نادرًا ما تحتاجه في واجهات JSON. |
2تشريح HTTP: الطلب والرد
POST /drinks HTTP/1.1 <- سطر الطلب: المنهج + المسار + الإصدار
Host: localhost:5055 <- ترويسات (Headers)
Content-Type: application/json
Authorization: Bearer <token>
<- سطر فارغ
{"name": "V60", "price_halalas": 1800} <- الجسم (Body)
HTTP/1.1 201 CREATED <- سطر الحالة: الإصدار + الرمز + العبارة
Content-Type: application/json
Location: /drinks/3
<- سطر فارغ
{"id": 3, "name": "V60", "price_halalas": 1800}العنوان URL نفسه له أجزاء: https://api.cafe.example:443/drinks/2?size=large#top = المخطط (https) + المضيف + المنفذ + المسار + معاملات الاستعلام (بعد ?) + الجزء (بعد # ولا يصل إلى الخادم أصلًا).
3HTTP مقابل HTTPS
HTTPS هو HTTP نفسه داخل قناة مشفّرة بـTLS. يعطيك ثلاثة أشياء: سرّية (لا يقرأ الوسطاء المحتوى)، سلامة (لا يعدّله أحد دون كشف)، وهوية الخادم (شهادة تثبت أنك تكلّم الموقع الصحيح). المنفذ الافتراضي 80 لـHTTP و443 لـHTTPS. لاحظ أنه يحمي الطريق لا الخادم: واجهة سيئة التصميم تبقى سيئة فوق HTTPS.
4المناهج (Methods): الفعل المطلوب
| المنهج | الفعل المعتاد | آمن Safe؟ | متكافئ Idempotent؟ | مثال |
|---|---|---|---|---|
GET | قراءة مورد | نعم | نعم | GET /drinks/2 |
POST | إنشاء مورد جديد (أو تنفيذ إجراء) | لا | لا | POST /drinks |
PUT | استبدال مورد كاملًا | لا | نعم | PUT /drinks/2 |
PATCH | تعديل جزئي | لا | لا (قد يكون) | PATCH /drinks/2 |
DELETE | حذف مورد | لا | نعم | DELETE /drinks/2 |
HEAD / OPTIONS | ترويسات فقط / ما المسموح | نعم | نعم | OPTIONS /drinks |
آمن = لا يغيّر حالة الخادم. متكافئ = تكرار الطلب عدة مرات له نفس أثر مرة واحدة: حذف المشروب 2 مرتين يترك الحالة النهائية نفسها (وقد يتغير الرد من 204 إلى 404). أما POST فتكراره ينشئ نسخًا جديدة، ولهذا يحذر العملاء من إعادة إرساله تلقائيًا بعد انقطاع.
5رموز الحالة (Status Codes)
| العائلة | المعنى | الرموز التي ستستخدمها |
|---|---|---|
2xx | نجح | 200 OK · 201 Created (مع ترويسة Location) · 204 No Content (نجاح بلا جسم) |
3xx | إعادة توجيه | 301 Moved Permanently · 304 Not Modified |
4xx | خطأ من العميل | 400 Bad Request · 401 Unauthorized · 403 Forbidden · 404 Not Found · 405 Method Not Allowed · 409 Conflict · 415 Unsupported Media Type · 429 Too Many Requests |
5xx | خطأ من الخادم | 500 Internal Server Error · 502 · 503 Service Unavailable |
200 OK دائمًا وإخفاء الفشل داخل الجسم ({"success": false}). العملاء والوسطاء والأدوات يقرؤون رمز الحالة أولًا؛ رمز كاذب يكسر إعادة المحاولة والتخزين المؤقت والمراقبة. الرمز يقول ما حدث، والجسم يشرح لماذا.
6تصميم الموارد والروابط
أسماء لا أفعال
GET /drinks وليس /getDrinks. الفعل يحمله المنهج.
الجمع للمجموعات
/drinks مجموعة، و /drinks/2 عنصر منها.
التصفية في الاستعلام
/drinks?max_price=1500&sort=name لا مسارات جديدة لكل تصفية.
الإصدار
/v1/drinks يسمح بتغيير العقد لاحقًا دون كسر العملاء القدامى.
7استهلاك واجهة من سطر الأوامر: curl
| الخيار | وظيفته |
|---|---|
-i | يطبع ترويسات الرد مع الجسم |
-s | صامت: يخفي شريط التقدم |
-X METHOD | يحدد المنهج (POST و DELETE…) |
-H "Name: value" | يضيف ترويسة |
-d '...' | يرسل جسم الطلب (ويجعل المنهج POST افتراضيًا) |
-w "%{http_code}" | يطبع بعد الانتهاء معلومة مثل رمز الحالة |
-o /dev/null | يرمي الجسم (مفيد مع -w) |
-L | يتبع إعادات التوجيه |
8استهلاك واجهة من بايثون: requests
timeout دائمًا
بدونه قد ينتظر برنامجك للأبد؛ requests لا تضع مهلة افتراضيًا.
raise_for_status()
يرفع HTTPError لرموز 4xx/5xx. ونجاح r.json() لا يعني نجاح الطلب لأن رسائل الخطأ نفسها JSON.
json= و params=
json= يُرمّز القاموس ويضبط Content-Type؛ و params= يبني سلسلة الاستعلام.
الاستثناءات
ConnectionError (الشبكة) و Timeout و HTTPError كلها ترث RequestException.
9بناء واجهة: http.server ثم Flask
http.server في المكتبة القياسية يريك الآلية عارية: صنف ترثه وتكتب do_GET و do_POST، وتصيغ الرد بيدك (send_response ثم send_header ثم end_headers ثم الكتابة في wfile). لا يوجد توجيه ولا تحقق ولا تحويل JSON، وتوثيق بايثون نفسه يحذّر بأنه غير موصى به للإنتاج ويطبّق فحوصًا أمنية أساسية فقط. Flask يعطيك ما ينقصه: جدول توجيه بالمزخرفات، ومحوّلات للمسار مثل <int:drink_id>، و jsonify، ومعالجات أخطاء.
http.server | Flask | |
|---|---|---|
| التوجيه | if self.path == … بيدك | مزخرف @app.get("/drinks/<int:id>") |
| JSON | json.dumps + ترميز + Content-Length | jsonify أو إرجاع قاموس |
| الأخطاء | أنت تصيغ كل رد | return …, 404 و @app.errorhandler |
| الاستخدام | تعلّم الآلية وأدوات صغيرة | واجهات حقيقية (خلف خادم إنتاج) |
app.run() و flask run للتجربة فقط، ووضع debug يفتح مصحّحًا تفاعليًا يمكنه تشغيل شيفرة بايثون عشوائية من المتصفح: لا تشغّله على خادم عام أبدًا. للإنتاج استخدم خادم WSGI مخصصًا (راجع صفحة النشر في توثيق Flask).
الأمن والمصادقة
المصادقة Authentication
من أنت؟ تثبت هويتك. فشلها يعني 401 Unauthorized مع ترويسة WWW-Authenticate تقول للعميل كيف يحاول.
التفويض Authorization
ماذا يحق لك؟ هويتك معروفة لكن صلاحيتك لا تكفي: 403 Forbidden. (وقد يُرجع الخادم 404 ليُخفي وجود المورد.)
Basic
Authorization: Basic base64(user:password). تذكّر: base64 ترميز عكوس وليس تشفيرًا، فلا يصلح إلا فوق HTTPS.
Bearer Token
Authorization: Bearer <token>: «من يحمل هذه التذكرة فهو صاحبها». سلّم التذكرة بعد تسجيل الدخول، وأرسلها مع كل طلب (تماشيًا مع قيد Stateless).
JWT
تذكرة من ثلاثة أجزاء header.payload.signature: الأولان JSON مرمّز بـbase64url (يُقرأ بلا مفتاح!)، والثالث توقيع يمنع التزوير. لا تضع فيه أسرارًا، وتحقق من التوقيع والانتهاء exp.
كلمات المرور
لا تُخزَّن أبدًا كما هي: خزّن بصمة بخوارزمية مخصصة بطيئة (مثل scrypt أو bcrypt)، ولا تكتبها في الكود ولا في المستودع ولا في الرابط.
فحوصات يومية
تحقق من كل مدخل (النوع والطول والمدى)، وارفض ما لا تدعمه بـ405 (منهج) أو 415 (نوع محتوى)، وحدّد معدل الطلبات (429)، وسجّل إخفاقات المصادقة.
1️⃣1️⃣ التوثيق والمعايير: OpenAPI
OpenAPI (كان اسمه Swagger Specification) صيغة YAML أو JSON تصف الواجهة: المسارات والمناهج والمعاملات وشكل الطلب والرد والمصادقة. الميزة أنها مقروءة للبشر والآلات معًا: من الملف نفسه تُولَّد صفحة توثيق تفاعلية (Swagger UI)، وعملاء جاهزون، وهياكل خوادم، وتُفحص الردود تلقائيًا في الاختبارات.
| جزء الملف | يصف |
|---|---|
openapi / info / servers | إصدار المعيار، اسم الواجهة وإصدارها، وعناوين الخوادم |
paths | كل مسار ومناهجه، والمعاملات، وأجسام الطلب، والردود برموزها |
components/schemas | أشكال البيانات المعاد استخدامها (مثل Drink) وتُشار بـ$ref |
components/securitySchemes | طرق المصادقة (مثل bearer) وأي عملية تتطلبها |
كيف تعمل الواجهة خطوة بخطوة؟
أربع صور: مسار طلب واحد من العميل إلى الخادم، شجرة اختيار رمز الحالة، الفرق بين http.server و Flask، وتذكرة المصادقة.
الرسم 32: رحلة طلب إنشاء: الرد يحمل 201 وعنوان المورد الجديد في Location ليعرف العميل أين يجده.
الرسم 33: شجرة اختيار رمز الحالة: اسأل الأسئلة بهذا الترتيب (الهوية، فالصلاحية، فصحة المدخل، فوجود المورد) فيخرج الرمز الصحيح تلقائيًا.
الرسم 34: نفس المهمة بمسارين: في http.server أنت تبني كل خطوة، وفي Flask الإطار يبني التوجيه والترميز ويترك لك منطق العمل.
الرسم 35: تذكرة المصادقة: تُصدر مرة بعد تسجيل الدخول وتُرسل مع كل طلب لاحق، ولا يحتفظ الخادم بجلسة بين الطلبات.
مثال تطبيقي: واجهة قائمة المقهى من الصفر
نبني واجهة قائمة مقهانا (/drinks) ونجرّبها بالأدوات الثلاث. كل الأكواد والمخرجات التالية جُرّبت فعليًا.
الخطوة 1: صمّم الموارد قبل أن تكتب سطرًا
| الطلب | الفعل | الرد عند النجاح | أخطاء متوقعة |
|---|---|---|---|
GET /drinks | قائمة المشروبات | 200 | ، |
GET /drinks/{id} | مشروب واحد | 200 | 404 |
POST /drinks | إنشاء مشروب | 201 + Location | 400 |
DELETE /drinks/{id} | حذف مشروب | 204 | 404 · 401 |
الخطوة 2: الخادم بـFlask
from flask import Flask, jsonify, request
app = Flask(__name__)
drinks = {1: {"id": 1, "name": "Flat White", "price_halalas": 1400},
2: {"id": 2, "name": "Cortado", "price_halalas": 1200}}
next_id = 3
@app.get("/drinks")
def list_drinks():
return jsonify(list(drinks.values()))
@app.get("/drinks/<int:drink_id>")
def get_drink(drink_id):
drink = drinks.get(drink_id)
if drink is None:
return jsonify(error="drink not found"), 404
return jsonify(drink)
@app.post("/drinks")
def create_drink():
global next_id
data = request.get_json(silent=True)
if not data or "name" not in data or not isinstance(data.get("price_halalas"), int):
return jsonify(error="name and integer price_halalas are required"), 400
drink = {"id": next_id, "name": data["name"], "price_halalas": data["price_halalas"]}
drinks[next_id] = drink
next_id += 1
resp = jsonify(drink)
resp.status_code = 201
resp.headers["Location"] = f"/drinks/{drink['id']}"
return resp
@app.delete("/drinks/<int:drink_id>")
def delete_drink(drink_id):
if drinks.pop(drink_id, None) is None:
return jsonify(error="drink not found"), 404
return "", 204
if __name__ == "__main__":
app.run(port=5055)الخطوة 3: جرّبه بـcurl
$ curl -i localhost:5055/drinks
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 99
[{"id":1,"name":"Flat White","price_halalas":1400},{"id":2,"name":"Cortado","price_halalas":1200}]
$ curl -i localhost:5055/drinks/99
HTTP/1.1 404 NOT FOUND
{"error":"drink not found"}
$ curl -i -X POST localhost:5055/drinks -H 'Content-Type: application/json' \
-d '{"name":"V60","price_halalas":1800}'
HTTP/1.1 201 CREATED
Location: /drinks/3
{"id":3,"name":"V60","price_halalas":1800}
$ curl -i -X POST localhost:5055/drinks -H 'Content-Type: application/json' -d '{"name":"V60"}'
HTTP/1.1 400 BAD REQUEST
{"error":"name and integer price_halalas are required"}
$ curl -s -o /dev/null -w "%{http_code}\n" -X DELETE localhost:5055/drinks/3
204الخطوة 4: استهلكه من بايثون
import requests
BASE = "http://localhost:5055"
r = requests.get(f"{BASE}/drinks", timeout=5)
print(r.status_code, r.headers["Content-Type"], r.json()[0])
# 200 application/json {'id': 1, 'name': 'Flat White', 'price_halalas': 1400}
r = requests.get(f"{BASE}/drinks/99", timeout=5)
print(r.status_code, r.json()) # 404 {'error': 'drink not found'}
try:
r.raise_for_status()
except requests.HTTPError as e:
print("HTTPError:", e) # HTTPError: 404 Client Error: NOT FOUND for url: ...
r = requests.post(f"{BASE}/drinks", json={"name": "Mocha", "price_halalas": 1600}, timeout=5)
print(r.status_code, r.headers["Location"], r.json())
# 201 /drinks/3 {'id': 3, 'name': 'Mocha', 'price_halalas': 1600}الخطوة 5: النسخة العارية بـhttp.server (لترى ما يخفيه Flask)
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
DRINKS = [{"id": 1, "name": "Flat White"}, {"id": 2, "name": "Cortado"}]
class Handler(BaseHTTPRequestHandler):
def _send(self, status, payload):
body = json.dumps(payload).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/drinks":
self._send(200, DRINKS)
else:
self._send(404, {"error": "not found"})
HTTPServer(("127.0.0.1", 5056), Handler).serve_forever()جرّبناها: curl -i localhost:5056/drinks تعطي HTTP/1.0 200 OK مع Content-Type: application/json; charset=utf-8، وأي مسار آخر يعطي 404. لاحظ أنك كتبت بيدك الترميز وطول المحتوى وكل ترويسة، ولا مكان هنا لـPOST إلا بقراءة self.rfile.read(length) بنفسك والتحقق منه يدويًا.
الخطوة 6: احمِ عملية الحذف بتذكرة
import hmac
from functools import wraps
from flask import request, jsonify
API_TOKEN = "..." # من متغير بيئة في الواقع، لا من الكود
def require_token(view):
@wraps(view)
def wrapper(*args, **kwargs):
scheme, _, token = request.headers.get("Authorization", "").partition(" ")
if scheme != "Bearer" or not hmac.compare_digest(token, API_TOKEN):
return jsonify(error="authentication required"), 401, {"WWW-Authenticate": "Bearer"}
return view(*args, **kwargs)
return wrapper
@app.delete("/drinks/<int:drink_id>")
@require_token
def delete_drink(drink_id):
...جرّبناه بعميل اختبار Flask: الطلب بلا ترويسة يعطي 401 مع WWW-Authenticate: Bearer، وتذكرة خاطئة تعطي 401، والتذكرة الصحيحة تعطي 204. لاحظ استخدام hmac.compare_digest لمقارنة ثابتة الزمن بدل ==.
from werkzeug.security import generate_password_hash, check_password_hash
h = generate_password_hash("correct horse") # يبدأ بـ scrypt:... ويحمل الملح داخله
check_password_hash(h, "correct horse") # True
check_password_hash(h, "nope") # Falseالخطوة 7: شاهد ما بداخل JWT (لتفهم لماذا لا تضع فيه أسرارًا)
import base64, hashlib, hmac, json
def b64(b): return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
header = b64(json.dumps({"alg": "HS256", "typ": "JWT"}, separators=(",", ":")).encode())
payload = b64(json.dumps({"sub": "noura", "role": "barista", "exp": 1790000000}, separators=(",", ":")).encode())
sig = b64(hmac.new(b"demo-secret", f"{header}.{payload}".encode(), hashlib.sha256).digest())
token = f"{header}.{payload}.{sig}"
print(base64.urlsafe_b64decode(payload + "=" * (-len(payload) % 4)))
# b'{"sub":"noura","role":"barista","exp":1790000000}' <- بلا أي مفتاح!exp و iss و aud، ولا تدع ترويسة التذكرة تختار خوارزمية التحقق.
الخطوة 8: وثّق العقد بـOpenAPI
ملف YAML واحد (فحصنا صحة بنيته بقارئ YAML) يصف الواجهة التي بنيناها. ألصقه في Swagger Editor لترى صفحة التوثيق التفاعلية:
openapi: 3.0.3
info:
title: Coffee Menu API
version: "1.0.0"
servers:
- url: http://localhost:5055
paths:
/drinks:
get:
summary: List all drinks
responses:
"200":
description: A JSON array of drinks
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Drink"
post:
summary: Create a drink
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NewDrink"
responses:
"201":
description: Created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: "#/components/schemas/Drink"
"400":
description: Invalid input
/drinks/{drinkId}:
parameters:
- name: drinkId
in: path
required: true
schema:
type: integer
get:
summary: Get one drink
responses:
"200":
description: The drink
content:
application/json:
schema:
$ref: "#/components/schemas/Drink"
"404":
description: Not found
delete:
summary: Delete a drink
security:
- bearerAuth: []
responses:
"204":
description: Deleted
"401":
description: Missing or invalid token
"404":
description: Not found
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
schemas:
NewDrink:
type: object
required: [name, price_halalas]
properties:
name:
type: string
price_halalas:
type: integer
minimum: 0
Drink:
allOf:
- $ref: "#/components/schemas/NewDrink"
- type: object
properties:
id:
type: integerخرافة مقابل حقيقة
| الخرافة | الحقيقة |
|---|---|
| «REST = JSON فوق HTTP». | JSON مجرد تمثيل شائع. جوهر REST قيود التصميم: موارد، واجهة موحّدة، بلا حالة، طبقات. ويمكن أن يكون التمثيل XML أو غيره. |
| «HTTPS يجعل واجهتي آمنة». | يحمي الطريق فقط. تبقى المصادقة والتفويض والتحقق من المدخلات ومعالجة الأخطاء مسؤوليتك. |
| «base64 يخفي كلمة المرور في Basic Auth». | base64 ترميز يُفكّ بسطر واحد. الحماية الوحيدة هي HTTPS. |
«PUT و POST متشابهان». | PUT متكافئ: يكرّر نفس النتيجة. POST غير متكافئ: كل تكرار قد ينشئ مواردًا جديدة. |
| «خادم التطوير يكفي للنشر». | http.server و flask run ليسا للإنتاج حسب توثيقهما نفسه. |
أين تُستخدم الواجهات فعليًا؟
تطبيقات الجوال والويب
الواجهة الأمامية تكلّم الخادم عبر REST؛ نفس الواجهة تخدم كل العملاء.
ربط الأنظمة
الدفع والخرائط والرسائل والتقويم كلها واجهات تستهلكها برمجيًا بدل أن تبنيها.
أدوات الذكاء الاصطناعي
استدعاء نموذج لغوي من برنامجك هو في جوهره طلب POST بجسم JSON مع ترويسة مصادقة.
الاختبار والأتمتة
curl و requests وعميل اختبار Flask تسمح بفحص الواجهة آليًا في كل تغيير.
جرّب بنفسك
أنشأ الخادم موردًا جديدًا بنجاح بعد POST. أي رمز حالة يناسب؟
201 تقول «أُنشئ شيء جديد»، ويرافقها عادةً عنوان المورد الجديد أو بياناته.
بطاقات مراجعة سريعة
Location)، و204 لنجاح بلا جسم (كالحذف).GET و PUT و DELETE. أما POST فلا.timeout في requests؟header.payload.signature: الأولان JSON يُقرآن بلا مفتاح، والتوقيع يكشف التلاعب.اختبر فهمك
1. تحذف مشروبًا بـDELETE /drinks/2 فينجح (204)، ثم تعيد الطلب نفسه فتحصل على 404. هل خالفت الواجهة قاعدة التكافؤ؟
لا. التكافؤ يخص حالة الخادم النهائية لا رمز الرد: بعد الطلبين الأول والثاني المشروب غير موجود في الحالتين. اختلاف الرد (204 ثم 404) طبيعي.
2. واجهة ترجع 200 مع {"error": "not found"} عندما لا يوجد المورد. ما المشكلة؟
الرمز يكذب: الأدوات والوسطاء تقرأ الرمز أولًا فتظن أن الطلب نجح (وقد تخزّنه مؤقتًا). الصحيح 404 مع الرسالة في الجسم.
3. (اكتشف الخطأ) مستخدم مسجّل دخوله يطلب حذف مشروب ليس من صلاحياته فتعيد الواجهة 401. ما الرمز الأدق؟
403 Forbidden: هويته معروفة لكن صلاحيته لا تكفي. 401 للحالة التي لا تُعرف فيها هويته أصلًا.
4. لماذا لا نخزّن كلمات المرور كما هي حتى في قاعدة بيانات خاصة؟
لأن تسرّب القاعدة (نسخة احتياطية، حقن، موظف) يكشفها كلها. تخزين بصمة بطيئة مملّحة يجعل الاستخراج مكلفًا حتى بعد التسرّب.
5. (قرار) تريد إتاحة GET /drinks للجميع و DELETE للمصادَقين فقط. كيف تنفّذ ذلك بـFlask دون تكرار الشيفرة؟
بمزخرف require_token تضعه فقط فوق دوال العرض المحمية، فيبقى منطق المصادقة في مكان واحد (كما في الخطوة 6).
مصادر هذا الدرس
مرتّبة بالنوع. ابدأ بالتوثيق الرسمي إذا تبي المرجع الدقيق، وبالمقال أو الفيديو إذا تبي الشرح أولًا.
توثيق رسمي3
خلّصت الدرس؟
سجّل الدخول ليُحفظ تقدّمك.