عناوين URL ومعاملات المسار والاستعلام
عنوان URL هو عنوان المورد على الويب، ومعاملاته (Parameters) مدخلات مكتوبة داخل هذا العنوان. ففي https://shop.example.com/stores/riyadh-01/products?q=green%20tea&page=2 يحدد معامل المسار (Path Parameter) riyadh-01 المتجر، ويحدد معاملا الاستعلام (Query Parameters) q وpage كلمة البحث وصفحة النتائج. أما %20 فهي مسافة مرمَّزة، لأن عنوان URL لا يقبل المسافة كما هي.
رأيت في درس طرق HTTP أن كل طلب يقرن طريقةً بهدف. وهذا الدرس يفتح ذلك الهدف: مكوّنات عنوان URL كما يعرّفها RFC 3986، ومعاملات المسار والاستعلام، والترميز الذي يحفظ القيم سليمة، وطريقة اختبار كل معامل.
لماذا يهم
المعاملات حقول إدخال تخفيها الواجهة. فكلمات البحث والمعرّفات وعوامل التصفية وأرقام الصفحات تنتقل في عنوان URL، ولذلك تحتاج إلى تصميم الاختبار نفسه الذي يحتاجه حقل في نموذج. وإذا عولجت بشكل خاطئ فإن البحث عن tea & coffee يعيد منتجات خاطئة، أو يُتجاهَل عامل تصفية دون أي رسالة، أو يؤدي تغيير /orders/1001 إلى /orders/1002 إلى عرض طلب عميل آخر. وهذا هو كسر التفويض على مستوى الكائن (Broken Object Level Authorization)، أول المخاطر في قائمة OWASP لأهم عشرة مخاطر في أمن الواجهات البرمجية لعام 2023.
وتعديل عنوان URL هو أيضاً أسرع طريقة لتجربة مُدخل جديد أو إعادة إنتاج بلاغ، والعنوان الدقيق بصيغته المرمَّزة أدق دليل يمكن أن يحمله تقرير الخطأ.
المكوّنات الخمسة لعنوان URL
يعرّف RFC 3986 الصياغة العامة لمعرّف الموارد الموحّد (URI) في خمسة مكوّنات: scheme://authority/path?query#fragment. وينتهي كل مكوّن عند محرف فاصل ثابت، فيمكنك تقسيم أي عنوان يدوياً؛ ومعيار WHATWG URL الذي تتبعه المتصفحات يقسمه بالطريقة نفسها.
يستخدم العميل المخطط والجهة للاتصال، ويرسل المسار والاستعلام بوصفهما هدف الطلب (Request Target)، ويضع اسم المضيف في الترويسة Host. أما الجزء (Fragment) فلا يغادر المتصفح أبداً، فلا يراه سجل الخادم ولا اختبار الواجهة البرمجية.
- المخطط (Scheme):
httpsأوhttp، ويحدد أيضاً المنفذ الافتراضي: 443 أو 80. - الجهة (Authority): اسم المضيف مثل
shop.example.com، ومعه المنفذ:portاختيارياً. ولا يفرّق المخطط ولا المضيف بين الأحرف الكبيرة والصغيرة. - المسار (Path):
/stores/riyadh-01/products، وهو مقاطع يفصل بينها/وتحدد المورد. - الاستعلام (Query): كل ما بعد
?حتى#. - الجزء (Fragment): كل ما بعد
#مثل#reviews، أي موضع داخل الصفحة أو مسار داخل تطبيق الصفحة الواحدة.
riyadh-01 المتجر، ويضبط q وpage القائمة، أما الجزء #reviews فلا يصل إلى الخادم أبداً.العنوان في شريط العناوين
الجزء 1:https://
سطر الطلب الذي يستقبله الخادم
GET
الترويسة Host
Host:
- 3–6هدف الطلب: المسار والاستعلام
- 5–6الاستعلام: أزواج
key=valueيربط بينها&
- 1المخطط (Scheme)يحدد المنفذ الافتراضي، وهو 443 مع
https، فلا يُكتب:port - 2الجهة (اسم المضيف)يُستخدم للاتصال ثم يُرسل في الترويسة
Host، ولا يفرّق بين الأحرف الكبيرة والصغيرة - 3مقاطع المسار الثابتةثابتة لكل متجر في القالب
/stores/{storeId}/products - 4معامل المسار
storeIdيحدد المتجر، ولذلك فهو مطلوب؛ والمتجر غير الموجود يجب أن يلقى404مطلوب - 5معامل الاستعلام
qكلمة البحث مرمَّزة بترميز النسبة المئوية؛ ويفك الخادم ترميزها إلى:green tea - 6معامل الاستعلام
pageاختياري غالباً وله قيمة افتراضية، فحذفه اختبار قائم بذاتهاختياري - 7الجزء (Fragment)يعالجه المتصفح وحده، ولذلك لا يظهر في سطر الطلبلا يُرسل
النسخة النصية
https://shop.example.com/stores/riyadh-01/products?q=green%20tea&page=2#reviews
scheme https
authority shop.example.com (no port: 443, the https default)
path /stores/riyadh-01/products
query q=green%20tea&page=2
fragment reviews (stays in the browser)
What the server receives:
GET /stores/riyadh-01/products?q=green%20tea&page=2 HTTP/1.1
Host: shop.example.comمعاملات المسار: أيّ مورد
لا يعرف RFC 3986 إلا مقاطع المسار. أما «معامل المسار» فهو الاسم الذي يستخدمه تصميم الواجهات البرمجية لمقطع تتغير قيمته من طلب إلى آخر: يكتب التوثيق قالباً مثل /stores/{storeId}/products، ويملؤه كل طلب بقيمة. ويصفه OpenAPI بـin: path، وتستخدم المواقع الفكرة نفسها في المعرّفات النصية المقروءة (Slugs) مثل /products/green-tea-500g.
يحدد معامل المسار المورد، ولذلك فهو مطلوب: فالعنوان /stores//products يصل إلى مسار آخر أو لا يصل إلى شيء. والقيمة من نوع خاطئ، مثل /orders/abc لمعرّف رقمي، يجب أن تلقى خطأً من فئة 4xx لا 500؛ والمعرّف غير الموجود يجب أن يلقى 404؛ والمعرّف الذي يخص مستخدماً آخر يجب ألا يعيد بيانات ذلك المستخدم أبداً. والشرطة المائلة المرمَّزة %2F داخل القيمة تعالجها الخوادم بطرق مختلفة، فاسأل عن السلوك المقصود واختبره.
معاملات الاستعلام: كيف تقرأ المورد
يضبط الاستعلام الطلب: بحث وتصفية وترتيب وتقسيم إلى صفحات. ولا يحدد RFC 3986 إلا موضع بدايته ونهايته؛ أما أزواج key=value التي يربط بينها & فمصدرها صيغة نماذج HTML المسماة application/x-www-form-urlencoded في معيار WHATWG URL، وتتبعها أغلب أطر العمل. ومعاملات الاستعلام اختيارية غالباً ولها قيم افتراضية، فغياب المعامل سلوك يُختبر هو أيضاً. وكل مما يلي مُدخل مختلف:
?page=2: مفتاح له قيمة.?page=أو?page: المفتاح موجود لكنه فارغ، وهذا يختلف عن حذفpageتماماً.?tag=tea&tag=gifts: مفتاح مكرَّر. لا يحدد أي معيار القيمة المعتمدة؛ فبعض أطر العمل يأخذ الأولى، وبعضها الأخيرة، وبعضها القيمتين معاً.?Page=2: كثير من أطر العمل يفرّق بين الأحرف الكبيرة والصغيرة في المفاتيح، فقد يكون هذا مفتاحاً مجهولاً يُتجاهَل بصمت.
ترميز النسبة المئوية: كيف تبقى القيم سليمة
يحجز RFC 3986 المحارف التي تبني هيكل العنوان: : / ? # [ ] @ تفصل بين المكوّنات، و! $ & ' ( ) * + , ; = تفصل البيانات داخلها. أما الحروف اللاتينية من A إلى Z ومن a إلى z والأرقام من 0 إلى 9 و- . _ ~ فلا تحتاج إلى ترميز أبداً. وأي محرف آخر داخل قيمة، ومنه المسافة، يُرمَّز بترميز النسبة المئوية (Percent-encoding): يصبح كل بايت علامة % يتبعها رقمان ست عشريان (Hexadecimal).
والمحارف التي تقع خارج ASCII، وهي المجموعة الأساسية من 128 محرفاً تضم الحروف اللاتينية والأرقام والرموز ومحارف التحكم، تتحول أولاً إلى بايتات بترميز UTF-8 المعتمد على الويب: يشغل محرف ASCII بايتاً واحداً، ويشغل أي محرف آخر من بايتين إلى أربعة. فكلمة «قهوة» أربعة حروف في كل منها بايتان، ولذلك تُرسل ثمانية بايتات مرمَّزة: %D9%82%D9%87%D9%88%D8%A9. رمّز كل قيمة وحدها ولا ترمّز العنوان كله، وإلا تحولت / و? إلى %2F و%3F وتعطّل العنوان.
- المحرف
+يعني مسافةً في البيانات المرمَّزة بصيغة النماذج فقط، وهكذا تقرأ أغلب الخوادم الاستعلام؛ أما في المسار فهو علامة جمع. رمّز علامة الجمع الحقيقية بـ%2B، وإلا وصل الرقم+966…مسافةً يتبعها966…. - رمّز مرة واحدة وفُكّ الترميز مرة واحدة. فالقيمة
%2520مسافة رُمّزت مرتين، والخادم الذي يفك الترميز مرتين قد يسمح لمحرف مرمَّز بأن يتجاوز التحقق من المدخلات.
Value Encoded query value
green tea green%20tea
tea & coffee tea%20%26%20coffee
50% 50%25
#1 seller %231%20seller
+966500000000 %2B966500000000
قهوة %D9%82%D9%87%D9%88%D8%A9مثال تطبيقي: اختبار عنوان قائمة منتجات
المتطلب: GET /api/v1/stores/{storeId}/products يعرض منتجات متجر. والمعامل q كلمة بحث اختيارية؛ وsort قيمته price_asc (الافتراضية) أو price_desc؛ وpage عدد صحيح يبدأ من 1 وقيمته الافتراضية 1؛ وlimit عدد صحيح من 1 إلى 50 وقيمته الافتراضية 20. والمتجر غير الموجود يلقى 404، والقيمة غير الصالحة لأي معامل تلقى 400 مع رسالة تذكر اسم المعامل.
تتولى معظمَ العمل هنا تقنيتان من تقنيات تصميم الاختبار، ولكلٍّ منهما درس خاص يشرحها بالتفصيل: تقسيم التكافؤ (Equivalence Partitioning) يقسم قيم المعامل إلى أقسام، أي مجموعات يجب أن يعاملها النظام بالطريقة نفسها، مثل قيم limit الصالحة وغير الصالحة، ثم يختبر قيمة واحدة من كل قسم؛ وتحليل القيم الحدّية (Boundary Value Analysis) يختبر بعد ذلك القيمة الواقعة على كل طرف من أطراف القسم وأقرب قيمة إليها خلف ذلك الطرف، مثل limit=50 وlimit=51. طبّق التقنيتين على كل معامل، ثم أضف حالات العنوان: القيم المفقودة والفارغة والمكرَّرة والمجهولة والمرمَّزة. وهذه النتائج المتوقعة للطلبات أدناه:
- دون استعلام:
200، والصفحة 1، و20 منتجاً على الأكثر، والأرخص أولاً. - متجر غير موجود
riyadh-99:404، لا قائمة فارغة ولا500. limit=50مقبول، وlimit=51يلقى400مع رسالة تذكرlimit. وأضفlimit=1وlimit=0للحد الأدنى.page=0وpage=abcوsort=newest:400، لا500ولا قيمة بديلة لا يذكرها التوثيق.- يجب أن يُفك ترميز البحثين إلى
tea & coffeeو«قهوة» وأن يعيدا منتجات مطابقة. ثم كرّر البحث من واجهة المستخدم وافحص الطلب في تبويب Network في أدوات المطوّر: الصفحة التي تضمّ النصوص بعضها إلى بعض تُرسلq=tea%20&%20coffee، فيقرأ الخادمqعلى أنهاteaتليها مسافة، ومعها مفتاح شارد. pageالمكرَّر وdebugالمجهول: النتيجة المتوقعة يجب أن تأتي من التوثيق، فإن لم يُكتب شيء فاسأل.
GET /api/v1/stores/riyadh-01/products
GET /api/v1/stores/riyadh-99/products
GET /api/v1/stores/riyadh-01/products?limit=50
GET /api/v1/stores/riyadh-01/products?limit=51
GET /api/v1/stores/riyadh-01/products?page=0
GET /api/v1/stores/riyadh-01/products?page=abc
GET /api/v1/stores/riyadh-01/products?sort=newest
GET /api/v1/stores/riyadh-01/products?q=tea%20%26%20coffee
GET /api/v1/stores/riyadh-01/products?q=%D9%82%D9%87%D9%88%D8%A9
GET /api/v1/stores/riyadh-01/products?page=2&page=3
GET /api/v1/stores/riyadh-01/products?debug=trueجرّبها: منشئ معاملات استعلام API جرّبها: مولّد مجموعات الأحرف الخاصة
أخطاء شائعة
- بناء العناوين بضمّ النصوص بعضها إلى بعض، فتكسر قيمةٌ فيها
&أو#أو+أو%الطلبَ أو تغيّر معناه. - ترميز العنوان كله بدلاً من كل قيمة على حدة، أو ترميز القيمة مرتين.
- اختبار القيم الصالحة فقط، دون القيم المفقودة أو الفارغة أو ذات النوع الخاطئ أو الحدّية أو المكرَّرة أو المجهولة.
- توقّع أن يرى الخادم الجزء (Fragment): كل ما بعد
#يبقى في المتصفح. - قبول الرموز (Tokens) أو كلمات المرور أو البيانات الشخصية في سلسلة الاستعلام. فالعناوين تُحفظ في سجلات الخوادم وسجلات الوسطاء وسجل التصفح، ويحذّر RFC 9110 من وضع المعلومات الحساسة فيها.
- الثقة بمعامل المسار لأن الواجهة لا تعرض معرّفات الآخرين، بدلاً من التحقق من أن الخادم يرفض مورد مستخدم آخر.
من منظور ضمان الجودة
عندما يقدّم متطلب أو مرجع واجهة برمجية معاملاً جديداً، اسأل: هل هو مطلوب؟ وما قيمته الافتراضية؟ وما القيم والحدود المسموح بها؟ وهل يفرّق بين الأحرف الكبيرة والصغيرة؟ وماذا يحدث إذا كان مفقوداً أو فارغاً أو مكرَّراً أو مجهولاً؟ كل إجابة تصبح نتيجة متوقعة، وكل «لا أحد يعرف» تصبح سؤالاً قبل أن تبدأ البرمجة.
في حالات الاختبار وتقارير الأخطاء، سجّل عنوان URL كاملاً كما أُرسل، منسوخاً من تبويب Network أو من عميل الواجهة البرمجية لا مكتوباً من جديد، ومعه القيمة بعد فك الترميز لأي معامل مهم. وتبديل معامل المسار للوصول إلى بيانات مستخدم آخر لا يكون إلا في اختبار مصرَّح به: استخدم حسابات الاختبار الخاصة بك في بيئة لديك إذن باختبارها، وأبلغ عن أي تسريب عبر الإجراء الأمني المعتمد في الفريق.
تمرين عملي
اقرأ عنوان قائمة الطلبات واختبره
يقول مدير متجر إن قائمة الطلبات تبدو خاطئة، ويرسل العنوان أدناه. وفوقه مرجع نقطة النهاية (Endpoint): تعيد طلبات متجر واحد إلى مدير ذلك المتجر بعد تسجيل دخوله.
اعمل على الورق، ثم تحقّق من تقسيمك بأداة «محلّل عناوين URL» ومن ترميزك بأداة «مرمّز URL».
GET /stores/{storeId}/orders
storeId path required example: riyadh-01
status query optional new | paid | shipped default: all
customer query optional free text
page query optional integer >= 1 default: 1
errors 400 for an invalid parameter value, 404 for an unknown storeId
https://admin.example.com/stores/riyadh-01/orders?status=paid&page=0#list- قسّم العنوان إلى مكوّناته الخمسة. أيّها لا يصل إلى الخادم أبداً؟
- سمِّ معاملات المسار والاستعلام، وطابق كل قيمة مع المرجع.
- رمّز عامل تصفية
customerللعميلAl Noor & Sons. - اكتب ثمانية اختبارات مع نتائجها المتوقعة.
إظهار الإجابة النموذجية
المخطط https؛ والجهة admin.example.com (المنفذ الافتراضي 443)؛ والمسار /stores/riyadh-01/orders؛ والاستعلام status=paid&page=0؛ والجزء list الذي يبقى في المتصفح.
معامل المسار storeId = riyadh-01؛ ومعاملا الاستعلام status = paid وpage = 0. والقيمة page=0 أقل من الحد الأدنى 1، فالنتيجة المتوقعة 400. وإذا ظهرت القائمة رغم ذلك فقد يكون هذا هو العيب المُبلَّغ عنه.
customer=Al%20Noor%20%26%20Sons (ويقبل ترميز النماذج أيضاً Al+Noor+%26+Sons). ودون ترميز ينهي & القيمة، فيقرأ الخادم customer على أنها Al Noor تليها مسافة، ومعها مفتاح شارد هو Sons تسبقه مسافة.
الاختبارات: (1) دون استعلام: كل الحالات، والصفحة 1. (2) كل قيمة مسموح بها لـstatus: الطلبات المطابقة فقط. (3) status=cancelled: 400. (4) page=-1 وpage=abc: 400. (5) page=1 والصفحة الأخيرة تعيدان طلبات. (6) customer مرمَّز فيه & و% و+ وحروف عربية: المطابقة على الاسم كاملاً. (7) متجر غير موجود riyadh-99: 404. (8) متجر يخص حساب اختبار آخر من حساباتك: 403 أو 404 دون أي بيانات طلبات. واسأل عن السلوك المطلوب عند تكرار status.
أسئلة شائعة
ما الفرق بين URL وURI؟
معرّف الموارد الموحّد (URI) يحدد هوية المورد. ويسمّي RFC 3986 المعرّفَ URL عندما يبيّن أيضاً كيف يُوصَل إلى المورد، بمخطط مثل https واسم مضيف مثلاً. وكلاهما يتبع الصياغة العامة نفسها، ويُستخدم المصطلحان في اختبار الويب بالمعنى نفسه تقريباً.
هل + في عنوان URL مسافة أم علامة جمع؟
يتوقف ذلك على موضعه. ففي استعلام يُقرأ بصيغة النماذج (application/x-www-form-urlencoded) تعني + مسافة، وفي المسار هي علامة جمع. اكتب علامة الجمع الحقيقية %2B والمسافة %20، فيبقى معنى القيمة واحداً في كل مكان.
هل تفرّق عناوين URL بين الأحرف الكبيرة والصغيرة؟
المخطط والمضيف لا يفرّقان: HTTPS://Shop.Example.com هو نفسه https://shop.example.com. ويعامل RFC 3986 بقية المكوّنات على أنها تفرّق بينها، لكن كل خادم يقرر كيف يطابقها، فقد يعمل /Products أو يُعاد توجيهه أو يعيد 404. اختبر ذلك وسجّل السلوك الذي يختاره الفريق.
ما أقصى طول لعنوان URL؟
لا يحدد أي معيار حداً أقصى واحداً. ويوصي RFC 9110 بأن يدعم المرسلون والمستقبلون معرّفات URI بطول 8000 بايت على الأقل، وتطبّق المتصفحات والخوادم والوسطاء حدودها الخاصة. والخادم الذي يرفض عنواناً بسبب طوله يجب أن يرد بـ414 URI Too Long. اسأل عن حدّ نظامك واختبر ما دونه مباشرةً وما فوقه مباشرةً.
المراجع
- RFC 3986: Uniform Resource Identifier (URI): Generic Syntaxأجزاء URI (النظام والسلطة والمسار والاستعلام والجزء) والمحارف المحجوزة والترميز بالنسبة المئوية.
- URL Standardكيف تحلّل المتصفحات عناوين URL فعلياً وتسلسلها وترمّزها، بما فيها application/x-www-form-urlencoded.
- RFC 9110: HTTP Semanticsمعاني أساليب HTTP وحقول الترويسات ورموز الحالة والتفاوض على المحتوى، بغضّ النظر عن إصدار البروتوكول.
- OWASP API Security Top 10 (2023)أكثر عشر نقاط ضعف شيوعاً في واجهات API: خلل التفويض على مستوى الكائن، وخلل التوثيق، والاستهلاك غير المقيد للموارد، وغيرها.
تدرّب بأدوات متقن
تفكيك عنوان URL إلى البروتوكول والمضيف والمنفذ والمسار ومعاملات الاستعلام والجزء (fragment) وبيانات الاعتماد.
تحليل سلاسل الاستعلام وبناؤها، بما في ذلك المفاتيح المكرّرة وترميز المصفوفات بالأقواس المربعة.
ترميز النص بالنسبة المئوية (percent-encoding) لاستخدامه بأمان في عناوين URL وسلاسل الاستعلام وبيانات النماذج.
فكّ ترميز عناوين URL وسلاسل الاستعلام المرمَّزة بالنسبة المئوية وإعادتها إلى نص مقروء.
إنشاء سلاسل استعلام مرمّزة ترميزاً صحيحاً لاستدعاءات API مع معاملات التصفح (pagination) والتصفية والترتيب.
ولّد مدخلات مليئة بالأحرف الخاصة: علامات الاقتباس، والأحرف الوصفية لـ SQL/HTML/JSON، وأحرف التحكم، والرموز التعبيرية، وحيل المسارات.
للتعمّق أكثر
تقدّمك في هذا الموضوع
سجّل الدخول لتتابع تقدّمك
علّم الموضوعات مكتملة، وتابع من حيث توقفت، وافتح امتحانات التخصص.