بناء طلبات API
بناء طلب API يعني تحويل توثيق الواجهة البرمجية إلى رسالة HTTP واحدة دقيقة: طريقة (Method)، وعنوان URL، وكل مُدخل في المكان الذي تتوقعه الواجهة، أي في المسار (Path) أو سلسلة الاستعلام (Query String) أو إحدى الترويسات (Headers) أو جسم الطلب (Body)، مرمَّزاً كما تنص الواجهة. والقيمة الموضوعة في غير مكانها قد يتجاهلها الخادم، أو قد تغيّر ما يفعله.
مثلاً، «طلبات الشراء المدفوعة للعميل 58، الصفحة 2» تصبح GET /api/v1/customers/58/orders?status=paid&page=2 بلا جسم. و«غيّر المدينة في العنوان 3 لهذا العميل» تصبح PATCH /api/v1/customers/58/addresses/3 مع Content-Type: application/json والجسم {"city": "Jeddah"}.
لماذا يهم
الطلب المبني خطأً لا يختبر شيئاً، أو يختبر شيئاً آخر. ضع مرشِّحاً (Filter) في جسم طلب GET، وستجد خوادم كثيرة تتجاهله وتعيد القائمة كاملة، فينجح فحص «القائمة تُحمَّل» مع أن المرشِّح لم يُختبر قط. واترك الرمز & دون ترميز داخل عبارة بحث، فيستقبل الخادم معاملين بدلاً من معامل واحد.
والتحكم في كل جزء من الطلب يسهّل أيضاً تحديد العيوب الحقيقية: تغيّر مُدخلاً واحداً في كل مرة، وتعيد إنتاج الخطأ دون واجهة المستخدم، وتسلّم المطورين طلباً يستطيعون تشغيله.
أربعة أماكن يوضع فيها المُدخل
ابدأ من مرجع الواجهة البرمجية. وكثير من الفرق تنشره في صورة مستند OpenAPI، وهو وصف قياسي لواجهة REST تقرؤه الآلة، ويُكتب بصيغة YAML أو JSON. وفيه لكل معامل (Parameter) حقل in يسمّي موضعه، مثل path أو query أو header، ويوصف جسم الطلب تحت requestBody. والمقتطف أدناه هو نقطة النهاية (Endpoint) المستخدمة في المثال التطبيقي.
- معاملات المسار (Path Parameters) تحدد المورد، مثل
{customerId}في/customers/{customerId}. وهي مطلوبة دائماً؛ استبدل العنصر النائب كله، بما فيه القوسان. - معاملات الاستعلام (Query Parameters) تضبط الطلب: التصفية أو الترتيب أو التقسيم إلى صفحات أو البحث أو تشغيل خيار ما. وهي اختيارية غالباً ولها قيمة افتراضية، لذا فإن حذف أحدها حالة اختبار أيضاً. ورمّز المحارف المحجوزة داخل القيم بترميز النسبة (Percent-encoding).
- الترويسات تصف الطلب:
Content-Typeما الجسم، وAcceptما تريده في الاستجابة، وAuthorizationمن المتصل. ولا يدرج OpenAPI هذه الثلاث ضمن المعاملات، بل تحددهاrequestBodyوالاستجابات وقسمsecurity. - جسم الطلب يحمل البيانات التي تنشئها أو تغيّرها. ولا يعطي RFC 9110 المحتوى في طلب GET أو DELETE معنى محدداً بوجه عام، لذا لا يكون لهذين الطلبين جسم في العادة.
# server: https://api.example.com/api/v1
/customers/{customerId}/addresses/{addressId}:
patch:
parameters:
- name: customerId
in: path
required: true
schema: { type: integer }
- name: addressId
in: path
required: true
schema: { type: integer }
- name: notify
in: query
required: false
description: Send the customer an SMS about the change
schema: { type: boolean, default: true }
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/AddressChange' }
responses:
'200':
description: The updated address
content:
application/json:
schema: { $ref: '#/components/schemas/Address' }
security:
- bearerAuth: []جسم الطلب: JSON والنماذج وmultipart
يجب أن تصف ترويسة Content-Type البايتات التي ترسلها فعلاً. وثلاث صيغ تغطي معظم الواجهات البرمجية؛ والمثال يرسل التقييم نفسه بكل منها.
application/json: الصيغة المعتادة في واجهات REST. تحافظ على الأنواع، فـ4رقم و"4"نص، وتسمح بتداخل الكائنات والمصفوفات.application/x-www-form-urlencoded(معيار WHATWG URL): صيغة نماذج HTML التقليدية، أزواجname=valueيربط بينها&، مرمَّزة بترميز النسبة، والمسافة فيها+. وكل القيم فيها نصوص. وبعض نقاط نهاية تسجيل الدخول وإصدار الرموز (Tokens) تشترطها.multipart/form-data(RFC 7578): لرفع الملفات. تُفصل الأجزاء بفاصل (Boundary) يُعلَن فيContent-Type؛ فاترك العميل يولّده، لأن كتابةmultipart/form-dataيدوياً بلا الفاصل تجعل الخادم عاجزاً عن تحليل الجسم.- الصيغة التي لا تقبلها نقطة النهاية يجب أن تُرفض، وعادةً بالرمز
415 Unsupported Media Type، وهذا اختبار سلبي مفيد.
POST /api/v1/stores/riyadh-01/feedback HTTP/1.1
Host: api.example.com
Content-Type: application/json
{"rating": 4, "subscribe": true}
POST /api/v1/stores/riyadh-01/feedback HTTP/1.1
Host: api.example.com
Content-Type: application/x-www-form-urlencoded
rating=4&subscribe=true
POST /api/v1/stores/riyadh-01/feedback HTTP/1.1
Host: api.example.com
Content-Type: multipart/form-data; boundary=x7Kq9
--x7Kq9
Content-Disposition: form-data; name="rating"
4
--x7Kq9
Content-Disposition: form-data; name="photo"; filename="box.jpg"
Content-Type: image/jpeg
(the bytes of the image)
--x7Kq9--مثال تطبيقي: من العقد إلى الطلب
تقول حالة اختبار: غيّر مدينة العنوان 3 لعميل الاختبار 58 إلى جدة، والرمز البريدي إلى 23436، دون إرسال رسالة SMS إلى العميل. اعمل على المقتطف أعلاه، مُدخلاً بعد مُدخل.
- الطريقة: يعرّف العقد
patchلهذا المسار، أي تعديلاً جزئياً. - المسار: عنوان الخادم، ثم المسار بعد استبدال العنصرين النائبين:
/api/v1/customers/58/addresses/3. - الاستعلام: المعامل
notifyيرسل رسالة SMS وقيمته الافتراضيةtrue، فحذفه يرسل الرسالة؛ لذا أضف?notify=false. - الترويسات:
Content-Type: application/jsonمنrequestBody، وAccept: application/jsonمن الاستجابة، وAuthorizationمع رمز Bearer لحساب اختبار منsecurity. - الجسم: الحقول المتغيرة فقط، بالأنواع التي يحددها المخطط (Schema). ويشير
$refإلى المخططAddressChangeفي موضع آخر من المستند، وهو يعرّفcityوpostalCodeنصّين، لذا يوضع الرمز البريدي بين علامتي تنصيص:{"city": "Jeddah", "postalCode": "23436"}. - البدائل: غيّر جزءاً واحداً في كل مرة، كأن ترسل
postalCodeرقماً، أو تستخدم عنواناً يخص عميلاً آخر، أو تزيلContent-Type، فتشير كل نتيجة إلى مُدخل واحد.
الطلب
الجزء 1:PATCH /api/v1/customers/الجزء 2:58/addresses/الجزء 2:3?الجزء 3:notify=false HTTP/1.1 الجزء 4:Host: api.example.com الجزء 5:Content-Type: application/json Accept: application/json الجزء 6:Authorization: Bearer eyJhbGciOi... الجزء 7:{"city": "Jeddah", "postalCode": "23436"}
- 2–3مُعلنة تحت
parametersفي العقد - 5–7مستمدة من
requestBodyوالاستجابات وsecurity
- 1الطريقة (Method)يعرّف العقد
patchلهذا المسار: تعديل جزئي. - 2معاملات المسار
58و3يحلّان محل{customerId}و{addressId}، وكلاهما مطلوب. - 3معامل الاستعلاماختياري وقيمته الافتراضية
true؛ يُضبط علىfalseكي لا يرسل الاختبار رسالة SMS. - 4الخادم (عنوان URL الأساسي)المضيف (Host) من عنوان URL للخادم في العقد، ومساره
/api/v1هو بداية مسار الطلب. واحفظ عنوان URL الأساسي في متغير لكل بيئة. - 5ترويسات الصيغة
Content-Type: الجسم بصيغة JSON. وAccept: أعِد الاستجابة بصيغة JSON. - 6بيانات الاعتمادرمز Bearer لحساب اختبار، في ترويسة لا في عنوان URL أبداً.
- 7جسم الطلبالحقول المتغيرة فقط بصيغة JSON؛ والرمز البريدي نص.
النسخة النصية
PATCH /api/v1/customers/58/addresses/3?notify=false HTTP/1.1
Host: api.example.com
Content-Type: application/json
Accept: application/json
Authorization: Bearer eyJhbGciOi...
{"city": "Jeddah", "postalCode": "23436"}إرسال الطلب من عميل API
عميل API الرسومي، مثل Postman أو أداة «منشئ طلبات API» من Mutqan في المتصفح، يحوّل هذه القرارات إلى حقول: الطريقة، وعنوان URL، وجدول للمعاملات، وجدول للترويسات، وإعدادات التفويض، ومحرر للجسم. أما cURL، وهو عميل يعمل من سطر الأوامر، فيحوّلها إلى خيارات مثل -X و-H و-d. والعادات التالية أهم من الأداة نفسها:
- احفظ عنوان URL الأساسي وحساب الاختبار والرمز (Token) في متغيرات (يكتبها Postman بالشكل
{{baseUrl}})، ليعمل الطلب المحفوظ نفسه على كل بيئة. - أدخل معاملات الاستعلام في الجدول واختر نوع الجسم، ليبني العميل سلسلة الاستعلام ويضبط
Content-Type؛ وإن كتبت هذه الترويسة يدوياً أيضاً فتأكد من تطابقهما. والعملاء يختلفون في المحارف التي يرمّزونها، فتحقّق من أن قيمة مثلtea & coffeeتصل معاملاً واحداً. - تحقّق مما أُرسل فعلاً: فالعملاء يضيفون ترويسات لم تكتبها، مثل
User-Agent. أرسل الطلب مرة واحدة إلى رابط التقاط (Capture URL) من أداة «فاحص طلبات HTTP» في Mutqan وقارن. - صدّر الطلب بصيغة cURL لتقارير الأخطاء، مع وضع متغير في سطر الأوامر مثل
$TOKENمكان الرمز (Token). والمثال مكتوب بعلامات التنصيص الخاصة بـbash، وأداة «مولّد أوامر cURL» من Mutqan تكتبه أيضاً لـPowerShell ولـcmd في Windows. - في cURL يرسل الخيار
-dالجسمَ ويجعل الطريقة POST ما لم يحدد-Xطريقة أخرى، وبلا ترويسةContent-Typeيَسِم الجسم بالنوعapplication/x-www-form-urlencoded، حتى لو كان JSON.
curl -X PATCH "https://api.example.com/api/v1/customers/58/addresses/3?notify=false" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"city": "Jeddah", "postalCode": "23436"}'جرّبها: منشئ طلبات API جرّبها: مولّد أوامر cURL جرّبها: فاحص طلبات HTTP
أخطاء شائعة
- وضع المرشِّحات أو عبارات البحث في جسم طلب GET بدلاً من سلسلة الاستعلام.
- كتابة قيم الاستعلام في عنوان URL دون ترميزها بترميز النسبة، مثل علامة
+التي تقرؤها خوادم كثيرة على أنها مسافة؛ ودرس عناوين URL ومعاملات المسار والاستعلام يبيّن ما يجب ترميزه. - إرسال JSON دون
Content-Type: application/json، أو كتابةmultipart/form-dataيدوياً دون الفاصل. - وضع الأرقام والقيم المنطقية بين علامتي تنصيص (
"4"و"true") مع أن المخطط يعرّفها أرقاماً أو قيماً منطقية. - حذف المعاملات الاختيارية دون معرفة قيمها الافتراضية: فحذف
notifyقد يرسل رسائل حقيقية من اختبار.
من منظور ضمان الجودة
تبني الطلبات في ثلاث لحظات. عند المراجعة: إن لم تستطع بناء طلب صالح من التوثيق وحده، ففي التوثيق عيب. وعند الاختبار: طلب محفوظ لكل سيناريو، مع متغيرات للبيئة والبيانات. وفي تقرير الخطأ: الطلب نفسه بصيغة cURL بعد حذف الرمز (Token).
اسأل المطورين مبكراً عمّا يتركه العقد مفتوحاً، مثل الترويسات المطلوبة كمعرّف الارتباط (Correlation ID) الذي يربط الطلب بسجلات الخادم، والقيمة الافتراضية لكل معامل اختياري، وهل تُرسل القوائم بمفاتيح مكررة أم مفصولةً بفواصل، ثم سجّل الإجابات في الطلبات المحفوظة (كمجموعة Collection في Postman) أو في حالة الاختبار.
تمرين عملي
ابنِ طلبين من مرجع API
باستخدام المرجع أدناه، ابنِ طلبين: (1) ابحث في المتجر riyadh-01 عن "tea & coffee" في الفئتين drinks وgifts، الأرخص أولاً، وهو استكمال للبحث الذي رمّزته في درس عناوين URL ومعاملات المسار والاستعلام في صورة طلب كامل؛ (2) انشر مراجعة بتقييم 4 نجوم مع التعليق "Fresh, well packed" للمنتج 9917 في المتجر نفسه.
اكتب كلاً منهما بصيغة HTTP الخام وبصيغة cURL. ويمكنك اختيارياً أن تنشئ رابط التقاط في أداة «فاحص طلبات HTTP»، وتضعه مكان عنوان URL الأساسي، وترسل الطلبين إليه (برمز وهمي ودون بيانات شخصية حقيقية)، ثم تقارن ما وصل.
Base URL: https://api.example.com/api/v1
Every call: Authorization: Bearer <token>
GET /stores/{storeId}/products
storeId path string required
q query string optional free-text search
category query string optional repeat the key for several values
sort query string optional price_asc | price_desc (default: relevance)
Response: application/json
POST /stores/{storeId}/products/{productId}/reviews
storeId path string required
productId path integer required
Body: application/json
rating integer required 1 to 5
comment string optional up to 500 characters- حدّد في المرجع موضع كل مُدخل في المهمة: المسار أو الاستعلام أو الترويسة أو الجسم.
- اكتب الطريقة وعنوان URL، مع ترميز قيم الاستعلام.
- أضف الترويسات التي يحتاجها كل طلب فقط، والجسم حيث يوجد.
- حوّل كل طلب إلى cURL وقارنه بالصيغة الخام.
إظهار الإجابة النموذجية
الطلب 1 بصيغة HTTP الخام: GET /api/v1/stores/riyadh-01/products?q=tea%20%26%20coffee&category=drinks&category=gifts&sort=price_asc HTTP/1.1 مع Host: api.example.com وAccept: application/json وAuthorization، بلا جسم وبلا Content-Type. والاستعلام هو البحث المرمَّز نفسه من درس عناوين URL؛ والجديد في هذا الطلب اختيار الترويسات: Accept لأن المرجع يذكر استجابة JSON، وAuthorization لأن كل استدعاء يحتاجها، ولا شيء يصف جسماً. وقد يكتب العميل المسافات بالشكل + (q=tea+%26+coffee)، وهي كتابة ترميز النماذج، والخادم الذي يقرأ الاستعلام بوصفه بيانات نموذج يفك ترميزها إلى القيمة نفسها.
الطلب 2 بصيغة HTTP الخام: POST /api/v1/stores/riyadh-01/products/9917/reviews HTTP/1.1 مع Host وContent-Type: application/json وAccept وAuthorization، والجسم {"rating": 4, "comment": "Fresh, well packed"}، حيث 4 رقم لا النص "4".
صيغة cURL للطلب 1: curl "https://api.example.com/api/v1/stores/riyadh-01/products?q=tea%20%26%20coffee&category=drinks&category=gifts&sort=price_asc" -H "Accept: application/json" -H "Authorization: Bearer $TOKEN". واكتب عنوان URL بين علامتي تنصيص، وإلا عدّ سطر الأوامر (Shell) الرمز & عاملاً (Operator) من عوامله. ولا حاجة إلى -X لأن GET هي الطريقة الافتراضية.
صيغة cURL للطلب 2: curl "https://api.example.com/api/v1/stores/riyadh-01/products/9917/reviews" -H "Content-Type: application/json" -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -d '{"rating": 4, "comment": "Fresh, well packed"}'. والخيار -d يجعل الطريقة POST من تلقاء نفسه، فكتابة -X POST اختيارية؛ أما ترويسة Content-Type فتمنع cURL من وسم JSON على أنه بيانات نموذج.
في أداة الفحص، يجب أن يُفك ترميز الاستعلام إلى q = "tea & coffee" مع قيمتين لـcategory، وأن يُحلَّل جسم المراجعة على أنه JSON صالح.
أسئلة شائعة
كيف يحدد مرجع OpenAPI طريقة إرسال قائمة في الاستعلام؟
بالحقلين style وexplode في تعريف المعامل. والقيمة الافتراضية لمعامل الاستعلام هي style: form مع explode: true، أي تكرار المفتاح: ?category=drinks&category=gifts. ومع explode: false تُجمع القيم مفصولةً بفواصل: ?category=drinks,gifts. وإن لم يحدد المعامل أياً منهما فتنطبق هذه القيم الافتراضية. أما المرجع النصي، كالذي في التمرين، فيذكر ذلك بالكلمات ("repeat the key")؛ وإن سكت عنه فاسأل، لأن الخوادم تختلف.
ماذا أفعل حين يختلف المرجع عن سلوك الخادم؟
لا تتبع أياً منهما تلقائياً: فالاختلاف نفسه هو ما تبلّغ عنه. سجّل الطلب المبني من المرجع، والاستجابة، ونص المرجع، واسأل الفريق أيهما المقصود. ثم يُصلَح الخادم أو يُصحَّح المرجع، وبعد ذلك فقط عدّل طلبك المحفوظ، ليبقى يختبر العقد لا ما يفعله الخادم اليوم.
هل يكون جسم الطلب JSON أم ترميز نماذج؟
ما يعلنه العقد لنقطة النهاية تلك. فمعظم واجهات REST تتوقع application/json، ونماذج HTML وبعض نقاط نهاية إصدار الرموز تتوقع application/x-www-form-urlencoded، ورفع الملفات يستخدم multipart/form-data. ويجب أن تطابق ترويسة Content-Type الجسم الذي ترسله.
لماذا يفشل أمر cURL منسوخ على Windows؟
لكل سطر أوامر (Shell) قواعده في علامات التنصيص. فصيغة bash في هذا الدرس تضع جسم JSON بين علامتي تنصيص مفردتين، أما cmd في Windows فلا يعدّهما علامتي تنصيص، فينكسر الأمر. وفي Windows PowerShell 5.1 يكون curl اسماً بديلاً (Alias) للأمر Invoke-WebRequest الذي لا يقبل خيارات cURL، فاكتب curl.exe بدلاً منه. وأداة «مولّد أوامر cURL» من Mutqan تكتب الطلب نفسه لـPowerShell ولـcmd.
المراجع
- RFC 9110: HTTP Semanticsمعاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
- OpenAPI Specificationالمعيار لوصف واجهات REST: المسارات والعمليات والمعاملات والمخططات والاستجابات وأنظمة الأمان.
- URL Standardكيف تحلّل المتصفحات عناوين URL فعلياً وتسلسلها وترمّزها، بما فيها application/x-www-form-urlencoded.
- RFC 7578: Returning Values from Forms: multipart/form-dataترميز multipart/form-data المستخدم لرفع الملفات وإرسال النماذج المختلطة.
تدرّب بأدوات متقن
أنشئ طلبات HTTP وأرسلها (الطريقة وعنوان URL والترويسات والمصادقة والجسم) وافحص الاستجابة — بديل خفيف لـ Postman داخل متصفحك.
إنشاء أوامر cURL من الطريقة وعنوان URL والترويسات والمتن وخيارات المصادقة.
إنشاء سلاسل استعلام مرمّزة ترميزاً صحيحاً لاستدعاءات API مع معاملات التصفح (pagination) والتصفية والترتيب.
إنشاء ترويسات الطلبات لواجهات REST API (المصادقة والتفاوض على المحتوى والتتبّع) وتصديرها إلى cURL أو fetch أو axios أو HTTP.
تكوين متون طلبات JSON من محرّر حقول مرئي والتحقق منها قبل الإرسال.
اطّلع بالضبط على ما يرسله عميلك: الطريقة والترويسات والاستعلام والجسم وعنوان IP ومعلومات TLS.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.