10.1. نقطة النهاية الأولى الخاصة بك¶
قبل أن تتمكن الكاميرا من القيام بأي شيء مثير للاهتمام، يجب أن تكون بقية الشبكة قادرة على الوصول إليها. وأرخص شيء يثبت أن الخادم يعمل هو نقطة نهاية HTTP بمسار واحد تُرجع بعض بيانات JSON:
from microdot import Microdot
app = Microdot()
frame_count = 0
trigger_count = 0
@app.get('/status')
async def status(request):
return {'frames': frame_count, 'triggers': trigger_count}
app.run(host='0.0.0.0', port=80)
شغّله في الـ IDE. من أي جهاز آخر على الشبكة المحلية افتح http://<cam-ip>/status. سيعرض المتصفح:
{"frames": 0, "triggers": 0}
العدّادات هي عناصر نائبة -- ولا شيء يلمسها بعد -- لكن الطلب عبر الشبكة، ووجّهته الكاميرا، وشغّلت معالجًا، وأرسلت JSON كردّ.
10.1.1. ماذا يفعل كل سطر¶
نسخة واحدة من microdot.Microdot لكل برنامج نصي. تمتلك النسخة جدول التوجيه، ومعالجات الأخطاء، ودورة الحياة (التشغيل، والخدمة، والإيقاف). التطبيقات الكبيرة تنقسم إلى عدة وحدات Python لكنها تظل تتشارك كائن app واحدًا.
@app.get('/status') هو مُزخرِف المسار. نحن نستخدم هنا microdot.Microdot.get() فقط؛ وتظهر post() و put() و delete() في صفحات لاحقة عندما تبدأ الكاميرا بقبول عمليات الكتابة.
كل معالج مسار هو coroutine من asyncio ويتلقى الطلب كوسيطته الأولى. ليس على المعالج أن يستخدم request -- هذا المعالج يتجاهله -- لكن الوسيطة موجودة دائمًا حتى يكون التوقيع متسقًا.
إرجاع قاموس dict هو أقصر طريقة لإرسال JSON. يقوم Microdot بتحويل القاموس إلى JSON تلقائيًا ويضبط Content-Type: application/json على الاستجابة. وإرجاع سلسلة نصية يرسل text/plain. أما إرجاع microdot.Response صراحةً فهو الصيغة المطوّلة -- وهي لازمة عندما يكون المتن ثنائيًا أو عندما تريد الاستجابة ترويسات مخصصة.
app.run(host='0.0.0.0', port=80) يبدأ الخادم. 0.0.0.0 تعني الاستماع على كل واجهة لدى الكاميرا -- إيثرنت السلكي وواجهة الواي فاي STA معًا، إن كانتا تعملان. المنفذ 80 هو الافتراضي لـ HTTP، لذا لا تحتاج المتصفحات إلى كتابة رقم منفذ.
10.1.2. طلب واحد، من البداية إلى النهاية¶
يفتح الهاتف اتصال TCP، ويكتب سطر الطلب والترويسات، ثم ينتظر. تقرأ الكاميرا البايتات من المقبس، وتحللها إلى كائن microdot.Request، وتطابق المسار والطريقة مع جدول التوجيه، وتنتظر coroutine المعالج، وتحوّل ما أرجعه إلى تسلسل، وتكتب سطر الحالة والترويسات والمتن عبر المقبس، ثم تغلق الاتصال (الافتراضي في HTTP/1.0) أو تعيد استخدامه (HTTP/1.1 مع Connection: keep-alive). يستغرق التبادل بأكمله ما يقارب زمن الذهاب والإياب عبر الشبكة بالإضافة إلى ما فعله المعالج.
10.1.3. ملاحظة حول الحجب¶
run() حاجبة -- فهي لا تعود أبدًا حتى يتوقف الخادم. هذا جيد لخادم ذي غرض واحد. أما التطبيق الذي يلتقط أيضًا إطارات أو يشغّل coroutines أخرى فيستخدم start_server() داخل asyncio.run() بدلًا من ذلك، حتى يتمكن خادم HTTP من مشاركة الحلقة مع كل شيء آخر.
يجيب التطبيق على عنوان URL واحد.