sequenceDiagram participant Client participant Server Client->>Server: GET /v1/images/search HTTP/1.1 activate Server Server->>+Client: HTTP/1.1 200 OK deactivate Server
25 التخاطب عبر الشبكة
رأينا في الدرس السابق كيفية التواصل مع خادم عن طريق مكتبة العميل التابعة للخدمة (GeoPy). لكن ما الآلية التي جرَّدتها عنا هذه المكتبة وأخفتها؟
تخضع المراسلات بين البرمجيات التطبيقية لقواعد تواصل تسمى HTTP (Hyper-Text Transfer Protocol)؛ وهي حرفيًّا بمعنى قواعد تناقل النص الفائق. وسيأتي بيان معنى الفائقية عند شرح HTML (Hyper-text Markup Language) لأن هذين المفهومين مرتبطان. لكن الذي يهمنا الآن أنها أحد أكثر لغات التواصل استخدامًا على الشبكة. فالاتصال بين الخادم والمخدوم يكون بها وإن كان البرنامج نفسه مكتوبًا بلغة أخرى مثل بايثون. ومن نظائرها:
- SMTP, IMAP, POP للبريد الإلكتروني
- FTP لنقل الملفات
- Web Socket لتبادل المعلومات على خط حي مفتوح
فحتى نصل إلى خدمات كثيرة على الشبكة ونستفيد منها؛ لابد أن نفهم هذه الآلية. وهي مبنيَّة على أمور، منها: العنوان، والطلب، والجواب.
عنوان المورِد الموحَّد
إن اصطلاح عنوان المورِد الموحَّد URL (Uniform Resource Locator) هو الذي نقصده عندما نقول رابط (Link أو Hyper-link reference وتختصر href
) لأن صياغة الروابط عادةً ما تتبع هذه الصيغة الموحَّدة. وهي التي تتضمَّنُ عادةً جزئيَّة com.
أو net.
ونحوهما؛ وتتضمن بعدها مسارًا فرعيًّا. ونمثل على ذلك بمثال بسيط:
https://github.com/HassanAlgoz/python
ففيه:
- الصياغة:
https
- العنوان الأساسي:
github.com
- المسار الفرعي:
HassanAlgoz/python/
- المورِد:
python
أما مصطلح المورِد (Resource) فعامٌّ يشمل شيئًا مُجرَّدًا يتم الوصول إليه بعنوان مُصاغٍ بصيغة متفقٍ عليها. سواءٌ كان موجودًا قبل الوصول إليه، أو يتمُّ إنتاجه متى طُلِب. فقد يكون ملفَّ بيانات أو صفحةً تُعرَض أو معالجةً قيِّمة.
فلو جربت نسخ الرابط السابق ووضعته في الشريط الأعلى للمتصفح وكبست زر Enter
فسيأخذك المتصفح إلى تلك الصفحة. وسيأتي الكلام عن بناء الصفحات في حينه إن شاء الله.
الطلب والجواب
ومن مفاهيم لغة التخاطب عبر تطبيقات الشبكة أيضًا:
الطلب
الطلب (Request) وهو الرسالة التي تشمل:
- المحتوى:
body / content
- العميل الذي يمثِّل المستخدم (المُرسِل):
user-agent
- المستضيف الذي عليه الخادم (المُرسَل إليه):
Host
نوع الطلب (Method) وأهمها:
GET
لطلب الحصول على مورِد معيَّنDELETE
لطلب حذف مورِد معيَّن
ففيهما يتمُّ تحديد عنوان المورِد.
ثم لدينا:
POST
لطلب إنشاء مورِد؛ يتمُّ تحديد تفاصيل الإنشاء في محتوى الرسالةPUT
لطلب تعديل مورِد؛ يتمُّ تحديد تفاصيل التعديل في محتوى الرسالة
الرؤوس (Headers) هي معلومات عن المعلومات التي في الطلب نفسه أو محتواه؛ بعضها أصلي وبعضها إضافي.
- مثلاً:
Content-Type: text/csv
تعني أن البيانات المرسلة عبارة عن ملف بصيغة CSV. وهو أصلي. - أما الإضافي فيبدأ بحرف
X
على هذا النحو:x-api-key: 1234567890
هو مفتاح التطبيق الذي يسمح للطلب بالتعريف بصاحب الحساب لإتاحة الخدمة له.
المحتوى (Body) هي البيانات المُرْسَلة أو المُسْتَلَمة؛ سواءٌ في الطلب أو جوابه.
مثلا بيانات عبارة عن قاموس بصيغة JSON:
جواب الطلب
جواب الطلب (Response) وهو مثل الطلب في خصائصه؛ إلا أنَّه بعكس الاتجاه: من الخادم إلى العميل.
رمز حالة الطلب (Status Code) وتنقسم إلى نطاقات، وكثيرٌ منها مُهمَل غير مُستعمل:
- نطاق
100-199
(فقط للعلم - ولا تهمنا) - نطاق
200-299
تعني أن الطلب تمُّ إنجازه بنجاح. - نطاق
300-399
إعادة توجيه - نطاق
400-499
إشكال من جهة العميل400 -> Bad Request
البيانات المُرسلة ليست صالحة401 -> Unauthorized
المفتاح مفقود أو غير صالح403 -> Forbidden
المفتاح صالح لكن ليس كافيًا للوصول404 -> Not Found
ما طلبته غير موجود
- نطاق
500-599
إشكال من جهة الخادم
ولمزيد من التفاصيل راجع: HTTP overview.
مثال: خدمة صور القطط
ماذا لو لم تتوفَّر مكتبة خاصَّة بمزوِّد الخدمة؟ في هذه الحالة سنكتب نحن تفاصيل الاتصال بالخادم المزوِّد. وذلك يتطلب معرفة لغة التخاطب بين الخادم والعميل (HTTP).
يجب علينا أولاً تثبيت مكتبة httpx
باستعمال uv
:
ونمثل لمزود خدمة معلومات عن القطط (The Cat API)، وقد حصلنا على مفتاح التطبيق (API KEY) من خلال التسجيل في الموقع.
ولمعرفة الاستفادة من أي مزود خدمة، فإننا ندخل إلى صفحة المطورين، وتسمى (API Documentation). ومنها نعرف أن المسار الذي يجب أن نطلبه هو https://api.thecatapi.com/v1/images/search
، وهو يعطينا صورة قط عشوائية.
باستعمال مكتبة عامة لعميل HTTP يمكننا الوصول لأي خدمة مقدَّمة من جهة خادم يتخاطب بلغة HTTP. وهو ما يُعرف أيضًا بخادم ويب (Web Server). فها نحن هنا نحدد جميع ما نريد:
- نوع الطلب:
GET
- المسار:
/v1/images/search
- العنوان الرئيسي:
api.thecatapi.com
(وهو الموقع الذي يوجد عليه الملف) - الرؤوس:
x-api-key: 1234567890
(وهو مفتاح التطبيق الذي يسمح للطلب بالوصول إلى الخدمة)
وفي الواقع يتم تكوين الطلب كنص (string) بهذا الشكل (ونحن هنا نطبعه بصيغته النصية لغرضٍ تعليمي):
print(f"""
{request.method} {request.url.path} HTTP/1.1
{"\n".join([f"{k}: {v}" for k, v in request.headers.items()])}
""")
GET /v1/images/search HTTP/1.1
host: api.thecatapi.com
accept: */*
accept-encoding: gzip, deflate
connection: keep-alive
user-agent: python-httpx/0.28.1
x-api-key: live_9Cj8P0h75D5h2D7Y2H8MYEuuTmTXjT412xNlbVBouHxn2sEnAjr1dr4JMfIn4Mr4
فأما السطر الأول:
GET /v1/images/search HTTP/1.1
فمكون من ثلاثة أجزاء:
- نوع الطلب:
GET
ذلك أننا نريد حصول على معلومات (لا إنشاءها ولا تغييرها) - المسار:
/v1/images/search
هو المسار الفرعي الذي يحدد الخدمة المطلوبة - نسخة قوانين التواصل:
HTTP/1.1
فشكل الطلب والجواب يعتمد على هذه النسخة
وأما الوُجهة فمحددة بالرأس Host
على هذا النحو:
host: api.thecatapi.com
والترويسة الأخيرة x-api-key
هي ليست من الترويسات المعرَّفة في HTTP، ولكنه اتفاق بين العميل والخادم:
x-api-key: ...
وقد أضاف العميل رؤسًا لم نعيِّنها وهي: accept, accept-encoding, connection, user-agent
، وإليك معناها:
accept: */*
وتعني أننا نقبل الجواب بأي صيغة؛ سواء كانت بصيغة JSON أو HTML أو أي صيغة أخرىaccept-encoding: gzip, deflate
تعني أن العميل يتوقع المحتوى مضغوطًا بصيغة ضغط معينةconnection: keep-alive
تعني أن العميل يريد الحفاظ على الاتصال بالخادمuser-agent: python-httpx/0.28.1
هي ترويسة إجبارية ولا يهم ما تكون قيمتها. لكنها تعرِّف بهوية العميل
وبعد ذلك نرسل الطلب بالإجراء send
ونحصل على جواب response
:
والجواب نفسه له الصيغة النصية التالية:
print(f"""
{response.status_code} {response.reason_phrase}
{"\n".join([f"{k}: {v}" for k, v in response.headers.items()])}
{response.text}
""")
200 OK
x-dns-prefetch-control: off
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=15552000; includeSubDomains
x-download-options: noopen
x-content-type-options: nosniff
x-xss-protection: 1; mode=block
vary: Origin
expires: Tue, 03 Jul 2001 06:00:00 GMT
last-modified: Thu Mar 27 2025 21:03:30 GMT+0000 (Coordinated Universal Time)
cache-control: post-check=0, pre-check=0
authenticated: false
content-type: application/json; charset=utf-8
x-response-time: 236ms
x-cloud-trace-context: 198a37888b0f0aecb61c4481a201abe7
date: Thu, 27 Mar 2025 21:03:30 GMT
server: Google Frontend
content-length: 89
[{"id":"2ml","url":"https://cdn2.thecatapi.com/images/2ml.jpg","width":500,"height":340}]
ولاحظ أول سطر في نص الجواب: 200 OK
تعني أن الطلب تمُّ إنجازه بنجاح. وراجع الرؤس (Headers) إن أردت معرفة معنى كل ترويسة هنا (وليست تهمنا الآن). لكن ما يبدأ بحرف x-
هو إضافي وليس من أساس لغة HTTP المتفق عليها، ولذلك قد لا تجده في التوثيق العام، وإنما تجده في توثيق مزوِّد للخدمة.
ولاحظ أن آخر سطرٍ هو المحتوى:
[{"id": ... ,"url": ... , ... }]
وإذا نظرت إلى النصّ الموجود في محتوى الرد (response.text
) فإنك ستلاحظ أنه نصُّ مقوْلَب بصيغة JSON التي سبق الحديث عنها:
<class 'str'>
[{"id":"2ml","url":"https://cdn2.thecatapi.com/images/2ml.jpg","width":500,"height":340}]
ولكن هذا النص لا يمكن التعامل معه كما هو، لذلك نستخدم الإجراء json()
لتفسيره إلى شيء في بايثون (قائمة):
<class 'list'>
[{'id': '2ml', 'url': 'https://cdn2.thecatapi.com/images/2ml.jpg', 'width': 500, 'height': 340}]
الآن أصبح في هيكل بيانات يمكن التعامل معه. فنريد استخراج رابط الصورة منه:
فهذا الرابط، لو نسخته وأدخلته في المتصفح فستظهر لك صورة القط.
مثال: خدمة الطقس
والرابط يشبه استدعاء الإجراء. لاحظ أننا في بايثون نستدعي الإجراء pow
من الوحدة math
ونمرر العوامل 2, 3
إليه على هذا النحو:
وهكذا نشبِّه ذلك بطريقة العنوان الموحَّد باعتبار أن المورِد هو معالجة؛ وهي سؤالٌ عن الطقس في مدينة لندن:
Origin (الأصل) | Path (المسار) | Query (المعاملات) |
---|---|---|
https://api.openweathermap.org |
/weather |
?city=London |
math |
pow |
2, 3 |
- فكما أننا نطلب الإجراء
pow
من الوحدةmath
ونمرر العوامل2, 3
إليه - فكذلك نطلب المورِد
/weather
من الموقعhttps://api.openweathermap.org
ونمرر العوامل?city=London
إليه- العامل يبتدأ بعلامة الاستفهام
?
ثم اسم العاملcity
وقيمتهLondon
وهو اسم المدينة
- العامل يبتدأ بعلامة الاستفهام
لنأخذ مثالاً آخر على استخدام خدمة برمجية، وهي خدمة الطقس من OpenWeatherMap. هذه الخدمة تتيح لنا معرفة حالة الطقس في أي مدينة في العالم.
أولاً، نحتاج إلى مفتاح API من الموقع (يمكنك الحصول عليه مجاناً بعد التسجيل). ثم نستخدم مكتبة httpx
للاتصال بالخدمة.
ففي صفحة التوثيق قالوا إن طريقة الطلب هي على النحو التالي:
نشرح الرابط حتى تتبين أجزاؤه ليسهل عليك بعد ذلك قراءة أية رابط:
https://api.openweathermap.org/data/3.0/onecall?lat={lat}&lon={lon}&exclude={part}&appid={API key}
:
https://api.openweathermap.org
هو الأصل (Origin)/data/3.0/onecall
هو المسار الفرعي (Path)?
العلامة الفاصلة بين المسار والعوامل&
علامة فاصلة بين العوامل نفسهاlat={latitude}
هو تعيين للعامل الأوَّل بقيمةlatitude
أي: خط العرضlon={longitude}
هو تعيين للعامل الثاني بقيمةlongitude
أي: خط الطولexclude={part}
اختيار البيانات التي تريد استبعادها في جواب الطلبappid={API key}
هو مفتاح التطبيق الذي يسمح للطلب بالوصول إلى الخدمة
ونحن نكتبها في بايثون مع مكتبة httpx
على النحو التالي:
import httpx
latitude = 24.7136
longitude = 46.6753
client = httpx.Client()
request = client.build_request(
method="GET",
url="https://api.openweathermap.org/data/3.0/onecall",
params={
"lat": round(latitude, 4),
"lon": round(longitude, 4),
"appid": "4a5417dd3a781b7f64f05178ed423a23"
}
)
response = client.send(request)
print(response.text)
{"cod":401, "message": "Please note that using One Call 3.0 requires a separate subscription to the One Call by Call plan. Learn more here https://openweathermap.org/price. If you have a valid subscription to the One Call by Call plan, but still receive this error, then please see https://openweathermap.org/faq#error401 for more info."}
أو اختصارًا باستعمال httpx.get
مباشرةً هكذا:
import httpx
latitude = 24.7136
longitude = 46.6753
response = httpx.get(
url="https://api.openweathermap.org/data/3.0/onecall",
params={
"lat": round(latitude, 4),
"lon": round(longitude, 4),
"appid": "4a5417dd3a781b7f64f05178ed423a23"
}
)
print(response.text)
{"cod":401, "message": "Please note that using One Call 3.0 requires a separate subscription to the One Call by Call plan. Learn more here https://openweathermap.org/price. If you have a valid subscription to the One Call by Call plan, but still receive this error, then please see https://openweathermap.org/faq#error401 for more info."}
انطلق بالتطبيق
وبهذا تكون قادرًا على التعامل مع أي برمجيَّة توفِّر خدماتها عبر الشبكة. تحتاج فقط أن تُقدِم وتجرِّب حتى تأخذ يدك على الأمر!
انتقل إلى المسائل.فإن أردت البحث عن واجهة برمجية لعمل شيء ما، فاكتب الكلمات المفتاحية + “API” في محرك البحث؛ مثلاً: Google Maps API.
ملاحظة: بعض الواجهات تتطلب التسجيل للحصول على مفتاح API. وبعضها يحتاج إضافة إلى ذلك شحن الحساب برصيد مثل 5 دولارات. وكل ذلك مبين في التوثيق نفسه.