أراهن أنك واجهت خطأ برمجياً حيث تسبب رابط يحتوي على مسافات أو أحرف خاصة في تعطل شيء ما. ربما تسبب & في استعلام بحث بسلوك غريب. أو رأيت %20 في رابط وتساءلت عما يعنيه. دعنا نوضح كل هذا.
لماذا تحتاج الروابط إلى ترميز
لا يمكن أن تحتوي الروابط إلا على مجموعة محدودة من الأحرف. تحدد مواصفة RFC 3986 الأحرف "غير المحجوزة" الآمنة دائماً: A-Z، a-z، 0-9، -، _، . و ~. كل شيء آخر — المسافات، &، =، ?، الأحرف غير ASCII مثل ñ أو 日本語 — يجب ترميزه بالنسبة المئوية.
كيف يعمل الترميز بالنسبة المئوية
الأمر بسيط: خذ قيمة البايت UTF-8 للحرف، ومثّل كل بايت كـ % متبوعاً برقمين ست عشريين.
أمثلة:
- المسافة →
%20 &→%26=→%3Dé→%C3%A9(بايتان في UTF-8)日→%E6%97%A5(ثلاثة بايتات في UTF-8)
إذن Hello World تصبح Hello%20World في مسار الرابط.
الخطأ الأول: الترميز المزدوج
هذا هو أكثر أخطاء ترميز URL شيوعاً التي أراها، وهو خفي. ترمّز سلسلة نصية، ثم تمررها لدالة ترمّزها مرة أخرى. الآن %20 (مسافة مرمّزة بالفعل) تصبح %2520 لأن % نفسه يتم ترميزه.
مثال على المشكلة:
الحل: رمّز القيم مرة واحدة، في المستوى الصحيح. لا ترمّز ما هو مرمّز بالفعل.
علامة الزائد مقابل %20 — نعم، إنه مربك
في سلاسل الاستعلام، يمكن تمثيل المسافات كـ + أو %20. يأتي + من ترميز نماذج HTML (application/x-www-form-urlencoded). في مسارات URL، فقط %20 صالح.
إذن https://example.com/hello world → المسار يُرمّز إلى https://example.com/hello%20world
لكن https://example.com/search?q=hello world → الاستعلام يمكن أن يكون ?q=hello+world أو ?q=hello%20world
encodeURI مقابل encodeURIComponent
يوفر لك JavaScript دالتين، واستخدام الخاطئة خطأ كلاسيكي:
encodeURI()— يرمّز رابطاً كاملاً. يترك:و/و?و#و&و=كما هي لأنها أجزاء هيكلية من الرابط.encodeURIComponent()— يرمّز مكوّناً من مكونات الرابط (مثل قيمة معامل استعلام). يرمّز أيضاً:و/و?و#و&و=لأنها قد تكون جزءاً من البيانات.
القاعدة العامة: استخدم encodeURIComponent() للقيم، ولا تستخدمه أبداً لروابط كاملة. وثائق MDN تشرح الفرق بشكل ممتاز.
لغات برمجة أخرى
- Python:
urllib.parse.quote()للمسارات،urllib.parse.urlencode()لسلاسل الاستعلام — الوثائق هنا - Java:
URLEncoder.encode()(يستخدم+للمسافات — انتبه!) - PHP:
urlencode()لسلاسل الاستعلام،rawurlencode()للمسارات
نصائح سريعة
- رمّز دائماً قيم المعاملات، وليس الروابط بالكامل
- فك الترميز مرة واحدة في جهة الاستقبال — لا تمرر قيماً مرمّزة عبر طبقات متعددة
- اختبر بحالات حدية: المسافات،
&،=،#، أحرف غير ASCII والرموز التعبيرية
بناء الروابط بأمان في JavaScript
الطريقة الصحيحة لبناء روابط مع معاملات الاستعلام هي استخدام واجهات URL و URLSearchParams المدمجة. تتعامل مع كل الترميز نيابة عنك:
لاحظ كيف يستخدم URLSearchParams علامة + للمسافات (اصطلاح ترميز نماذج HTML) ويرمّز & في القيمة بشكل صحيح حتى لا يُخلط مع & الذي يفصل المعاملات. هذا أكثر أماناً بكثير من ربط النصوص يدوياً.
مزالق ترميز URL الشائعة
1. ترميز الرابط بالكامل بدلاً من القيم فقط. إذا شغّلت encodeURIComponent() على رابط كامل، سيرمّز أحرف :// و / و ? و & — مما يجعل الرابط غير قابل للاستخدام تماماً. رمّز فقط قيم المعاملات الفردية.
2. نسيان ترميز أجزاء الهاش. حرف # في الرابط يبدأ معرّف الجزء. إذا كانت بياناتك تحتوي على # ولم ترمّزه، كل شيء بعده يختفي من منظور الخادم. الخادم لا يرى معرّفات الأجزاء أبداً — هي من جانب العميل فقط.
3. عدم التعامل مع أسماء النطاقات الدولية (IDN). أسماء النطاقات ذات الأحرف غير ASCII مثل example.日本 تحتاج معالجة خاصة تسمى ترميز Punycode. هذا منفصل عن الترميز بالنسبة المئوية ويطبق فقط على جزء اسم المضيف في الرابط.
4. ترميز المسافات بشكل غير متسق. بعض أجزاء نظامك قد ترمّز المسافات كـ + وأخرى كـ %20. هذا يعمل عادة بشكل جيد لفك الترميز، لكنه قد يسبب مشاكل في مقارنة الروابط والتخزين المؤقت. اختر اصطلاحاً واحداً والتزم به.
مرجع سريع للترميز بالنسبة المئوية
| الحرف | المُرمّز | لماذا يحتاج ترميزاً |
| المسافة | %20 أو + | غير مسموح به في الروابط |
& | %26 | يفصل معاملات الاستعلام |
= | %3D | يفصل المفتاح عن القيمة |
? | %3F | يبدأ سلسلة الاستعلام |
# | %23 | يبدأ معرّف الجزء |
% | %25 | حرف الهروب نفسه |
/ | %2F | فاصل المسار |
@ | %40 | يُستخدم في قسم معلومات المستخدم |
ترميز URL في سياقات مختلفة
ترميز URL ليس فقط لروابط المتصفح. ستواجهه في هذه المواقف الشائعة:
- طلبات API: معاملات الاستعلام في استدعاءات REST API تحتاج ترميزاً صحيحاً، خاصة إذا كانت تحتوي على مدخلات المستخدم
- روابط إعادة التوجيه: عند تمرير رابط عودة كمعامل (مثل
?redirect=https://...)، يجب ترميز رابط إعادة التوجيه بالكامل كقيمة - تدفقات OAuth: روابط رد الاتصال والمعاملات الحالة في OAuth صعبة بشكل سيء لأنها تتضمن طبقات متعددة من ترميز URL
- الروابط العميقة: الروابط العميقة للهاتف المحمول تتبع نفس قواعد ترميز URL، لكن بعض المنصات لها متطلبات إضافية
تصحيح مشاكل ترميز URL
عندما يحدث خطأ في ترميز URL، إليك قائمة التحقق الخاصة بي:
1. تحقق من الترميز المزدوج — ابحث عن %25 في الرابط، مما يعني أن % تم ترميزه مرتين
2. تحقق من الطلب الخام — استخدم تبويب الشبكة في أدوات المطور في متصفحك لرؤية الرابط المُرسل بالضبط، قبل أن يعرض المتصفح النسخة المفكوكة
3. قارن المُرمّز مع المفكوك — الصق الرابط المُشكل في فاك ترميز لرؤية ما يحتويه فعلاً
4. تحقق من فك الترميز على الخادم — بعض أطر العمل تفك ترميز معاملات URL تلقائياً، وعمل ذلك يدوياً فوقها يسبب مشاكل
تشريح الرابط
حسناً، لنرجع خطوة للخلف. قبل أن نتعمق أكثر في الترميز، تحتاج فعلاً لفهم مما يتكون الرابط. أعرف، أعرف — كنت تستخدم الروابط طوال حياتك المهنية. لكنني أراهن أنك لا تعرف كل الأجزاء بأسمائها الرسمية. معظم المطورين لا يعرفون، وهنا يبدأ الارتباك في الترميز.
وفقاً لـ RFC 3986، للرابط هذا الهيكل:
دعني أشرح لك كل جزء:
- Scheme (
https،ftp،mailto) — البروتوكول. لا يحتاج ترميزاً هنا، هو دائماً أحرف ASCII. - Authority — يتضمن
user:password@الاختياري (الذي لا يجب استخدامه عملياً في 2026)، و المضيف (اسم النطاق أو IP)، ورقم المنفذ الاختياري. - Path (
/search/results) — الجزء الهرمي. الخطوط المائلة/تفصل الأجزاء. داخل كل جزء، تحتاج لترميز الأحرف الخاصة، لكنك لا ترمّز الخطوط المائلة نفسها. - Query (
?q=hello&lang=en) — أزواج مفتاح-قيمة بعد?. يفصل&بين الأزواج، و=يفصل المفاتيح عن القيم. ترمّز المفاتيح والقيم، لكن ليس&و=الهيكلية. - Fragment (
#section-2) — الجزء بعد#. هذا مثير للاهتمام — لا يُرسل أبداً للخادم. هو من جانب العميل فقط. لكنك لا تزال تحتاج لترميز الأحرف الخاصة بداخله.
هذا ما يربك الناس: أجزاء مختلفة من الرابط لها قواعد ترميز مختلفة. / مقبول تماماً في المسار (هو فاصل!) لكن يحتاج ترميزاً كـ %2F إذا ظهر داخل قيمة معامل استعلام. علامة @ مقبولة في قسم authority لكن يجب ترميزها في المسار. لهذا السبب تسبب دوال الترميز الموحدة كثيراً من الأخطاء.
فكّر فيه هكذا: الرابط جملة لها قواعد نحوية. الأحرف الخاصة هي علامات ترقيم. لن ترمّز فاصلة تُستخدم فعلاً كفاصلة — ترمّز فقط فاصلة هي جزء من البيانات وقد تُخلط مع علامات الترقيم.
encodeURI مقابل encodeURIComponent: حقل ألغام JavaScript
حسناً، هذا الموضوع يُجنّني لأنني أرى المطورين يخطئون فيه طوال الوقت. JavaScript يعطيك دالتي ترميز، وتبدوان متطابقتين تقريباً، لكن استخدام الخاطئة سيُفسد يومك.
دعني أوضحه بجلاء:
encodeURI() مصممة لترميز رابط كامل. ترمّز الأحرف غير الآمنة لكن تترك الأحرف الهيكلية — أشياء مثل : و / و ? و # و & و = و @. لأنك إذا كنت ترمّز رابطاً كاملاً، لا تريد بالطبع تكسير هيكل الرابط.
encodeURIComponent() مصممة لترميز قيمة واحدة تدخل في الرابط. ترمّز كل شيء ما عدا الأحرف والأرقام و - _ . ~. هذا يشمل : و / و ? و # و & و = — لأنها عندما تظهر في قيمة، تكون بيانات وليست هيكلاً.
إليك مقارنة توضح الأمر بجلاء:
| الحرف | encodeURI() | encodeURIComponent() |
: | : (بدون تغيير) | %3A |
/ | / (بدون تغيير) | %2F |
? | ? (بدون تغيير) | %3F |
# | # (بدون تغيير) | %23 |
& | & (بدون تغيير) | %26 |
= | = (بدون تغيير) | %3D |
@ | @ (بدون تغيير) | %40 |
| المسافة | %20 | %20 |
é | %C3%A9 | %C3%A9 |
الآن يصبح الأمر مخيفاً. شاهد ماذا يحدث عند استخدام الدالة الخاطئة:
والخطأ المعاكس سيء بنفس القدر:
القاعدة الذهبية: encodeURIComponent للقيم، encodeURI للروابط الكاملة. أو الأفضل من ذلك، استخدم ببساطة واجهة URL ودع المتصفح يتعامل معها. بجدية، فئتا URL و URLSearchParams موجودتان لسبب. راجع وثائق MDN لـ encodeURIComponent و وثائق MDN لـ encodeURI للتفاصيل الكاملة.
ترميز URL في لغات مختلفة
JavaScript ليست اللغة الوحيدة التي لها دوال ترميز URL مربكة. كل لغة لها خصوصياتها، ولا أمزح، بعضها أكثر إرباكاً من زوج JavaScript.
Python — معقولة فعلاً، بمجرد إيجاد الوحدة الصحيحة:
Java — هنا يصبح الأمر غريباً. URLEncoder صُمم لترميز نماذج HTML، وليس ترميز URL العام. لذا المسافات تصبح + بدلاً من %20:
C# — .NET يعطيك فعلاً الأدوات الصحيحة، لكن هناك حوالي خمس طرق مختلفة وكلها تفعل أشياء مختلفة قليلاً:
PHP — آه PHP. بالطبع لديك دالتان بأسماء متطابقة تقريباً تفعلان أشياء مختلفة قليلاً:
Go — نظيفة ومعقولة، كعادة Go:
الخلاصة؟ كل لغة تتعامل مع موضوع + مقابل %20 بشكل مختلف، وكل لغة لديها دالة واحدة على الأقل ستفاجئك. تحقق دائماً من وثائق اللغة التي تعمل بها. لا تفترض أنها تعمل مثل JavaScript.
Unicode في الروابط: الغرب المتوحش
حسناً، هنا يصبح الأمر مثيراً حقاً. ماذا يحدث عندما تضع أحرفاً غير ASCII في رابط؟ مثلاً، ماذا لو أردت رابطاً بأحرف يابانية أو عربية أو — لا أمزح — إيموجي؟
الإجابة تتضمن نظامين مختلفين تماماً، والخلط بينهما خطأ كلاسيكي.
لأجزاء المسار والاستعلام: تستخدم الترميز بالنسبة المئوية. خذ بايتات UTF-8 للحرف ورمّز كل واحد. إذن café تصبح caf%C3%A9. متصفحك يفعل هذا تلقائياً ويعرض لك عادة النسخة الجميلة في شريط العنوان، لكن خلف الكواليس يرسل النسخة المرمّزة.
لأسماء النطاقات: لا يُستخدم الترميز بالنسبة المئوية. بدلاً من ذلك، هناك نظام يسمى Punycode. يحوّل أسماء النطاقات Unicode إلى سلاسل متوافقة مع ASCII تبدأ بـ xn--.
انظر إلى هذا:
café.com→xn--caf-dma.commünchen.de→xn--mnchen-3ya.de例え.jp→xn--r8jz45g.jp
لماذا نظامان مختلفان؟ لأن DNS (النظام الذي يحوّل أسماء النطاقات إلى عناوين IP) بُني في الثمانينيات ويدعم ASCII فقط. لذا كان عليهم اختراع طريقة لحشو Unicode في سلاسل ASCII — وPunycode هو ذلك الاختراع. أما أجزاء المسار والاستعلام من الرابط، فتتعامل معها خوادم الويب التي يمكنها التعامل مع البايتات المرمّزة.
هناك فعلاً مواصفة كاملة لـ Unicode في الروابط تسمى IRI (Internationalized Resource Identifiers) محددة في RFC 3987. IRI هو أساساً رابط يسمح بأحرف Unicode مباشرة. المتصفحات تحوّل IRIs إلى URIs خلف الكواليس.
ونعم، نطاقات الإيموجي موجودة. 💩.la هو نطاق حقيقي (أو كان في وقت ما). يُرمّز بـ Punycode إلى xn--ls8h.la. لا أنصح باستخدام نطاقات الإيموجي لأي شيء جدي، لكنه دليل ممتع على أن النظام يعمل.
مطب واحد مع Unicode في الروابط: تمثيلات Unicode المختلفة لنفس الحرف. مثلاً، é يمكن تمثيله كنقطة رمز واحدة (U+00E9) أو كـ e + علامة تشكيل مدمجة (U+0065 + U+0301). هذه تنتج سلاسل مرمّزة مختلفة! يوصي معيار WHATWG URL بتطبيع NFC، لكن ليست كل الأنظمة تتبع هذا بشكل متسق.
الترميز المزدوج: الخطأ الذي يطارد أحلامك
ذكرت الترميز المزدوج سابقاً، لكنه يستحق الغوص العميق لأن هذا الخطأ ربما أضاع ساعات مطورين جماعية أكثر من أي مشكلة أخرى متعلقة بالروابط. مررت بهذا — مرات عديدة.
إليك السيناريو الأساسي:
ماذا حدث؟ عندما ترمّز hello%20world مرة ثانية، يتم ترميز حرف % إلى %25. إذن %20 تصبح %2520. الخادم يرى النص الحرفي %20 بدلاً من المسافة.
يبدو هذا واضحاً عندما أكتبه هكذا، لكن في قواعد الأكواد الحقيقية هو خبيث للغاية. إليك السيناريوهات التي يلدغك فيها الترميز المزدوج:
سلاسل البروكسي. ترسل طلباً إلى الخادم A، الذي يمرره إلى الخادم B. إذا رمّز كلا الخادمين الرابط، بووم — ترميز مزدوج. بوابات API مثل Kong و AWS API Gateway أو بروكسيات nginx العكسية هم مذنبون شائعون.
سلاسل إعادة التوجيه. المستخدم يذهب للصفحة A، يُعاد توجيهه للصفحة B مع معامل ?returnUrl=...، والصفحة B تعيد التوجيه مرة أخرى مع رابط العودة كمعامل. كل إعادة توجيه قد تعيد ترميز الرابط. بعد ثلاث إعادات توجيه، رابطك مرمّز ثلاث مرات ومدمّر تماماً.
"مساعدات" أطر العمل. بعض أطر عمل الويب ترمّز معاملات URL تلقائياً. إذا رمّزت يدوياً قبل التمرير لإطار العمل، تحصل على ترميز مزدوج. رأيت هذا يحدث مع Spring Boot و middleware في Express.js وعكس الروابط في Django.
كيف تكتشف الترميز المزدوج؟ ابحث عن %25 في الرابط. هذا علامة نسبة تم ترميزها، مما يعني عادة أن شيئاً تم ترميزه مرتين. إذا رأيت %2520، فهذه مسافة مرمّزة مرتين. إذا رأيت %253D، فهذه علامة = مرمّزة مرتين.
كيف تصلحه:
أفضل دفاع هو إنشاء حدود واضحة في كودك: رمّز على الحواف (قبل إرسال طلب HTTP مباشرة، أو قبل بناء رابط للعرض مباشرة)، ومرر سلاسل نصية خام غير مرمّزة في كل مكان داخلياً.
حدود طول الرابط وماذا تفعل حيالها
هناك شيء سيفاجئك: مواصفة HTTP نفسها لا تحدد طولاً أقصى للرابط. تقول RFC 3986 أن الروابط يجب أن تكون "بطول غير محدود". لكن العالم الحقيقي لا يوافق.
مكونات مختلفة في السلسلة لها حدودها الخاصة، والأقصر يفوز:
| المكوّن | أقصى طول للرابط |
| Internet Explorer (RIP) | 2,083 حرفاً |
| Chrome، Firefox، Safari | ~65,000+ حرفاً |
| Apache (افتراضي) | 8,190 حرفاً |
| Nginx (افتراضي) | 8,192 حرفاً |
| IIS (افتراضي) | 16,384 حرفاً |
| AWS ALB | 8,192 حرفاً |
| Cloudflare | 32,768 حرفاً |
حد الـ 2,083 حرفاً القديم من IE كان يحكم كل شيء. رغم أن IE ميت عملياً الآن، بعض المطورين والأدوات لا يزالون يعاملونه كحقيقة مقدسة. لكن عملياً، معظم المكدسات الحديثة يمكنها التعامل مع روابط أطول بكثير.
مع ذلك، لمجرد أنك تستطيع عمل رابط من 65,000 حرف لا يعني أنك يجب أن تفعل. إليك بعض الأسباب الحقيقية لإبقاء الروابط قصيرة:
- سجلات الخادم. كثير من أنظمة التسجيل تقطع الروابط الطويلة، مما يجعل تصحيح الأخطاء كابوساً.
- النسخ واللصق. المستخدمون ينسخون ويشاركون الروابط. الروابط الطويلة جداً تتكسر في البريد الإلكتروني والرسائل والمستندات.
- SEO. محركات البحث توصي عموماً بإبقاء الروابط أقل من 2,000 حرف.
- التخزين المؤقت. بعض حدود مفاتيح التخزين المؤقت في CDN والبروكسي مبنية على الروابط. روابط أطول = أخطاء تخزين مؤقت أكثر.
إذن ماذا تفعل عندما يصبح رابطك طويلاً جداً؟ سعيد أنك سألت:
1. استخدم POST بدلاً من GET. إذا كنت ترسل كثيراً من البيانات، ضعها في جسم الطلب. جسم الطلب ليس له حد عملي للحجم. هذا الحل الأكثر شيوعاً لنماذج البحث المعقدة ذات الفلاتر الكثيرة.
2. استخدم مُقصّرات الروابط أو معرّفات مرجعية. أنشئ رمزاً قصيراً يشير إلى مجموعة المعاملات الكاملة المخزنة على الخادم. إذن بدلاً من /search?filter1=abc&filter2=def&filter3=... تحصل على /search/saved/abc123.
3. اضغط معاملاتك. بعض التطبيقات تُرمّز كتلة JSON مضغوطة بـ base64 وتضعها في الرابط. ليس جميلاً، لكنه يعمل. سترى هذا في أدوات مثل روابط لوحات Grafana.
ترميز النماذج: application/x-www-form-urlencoded
لنتحدث عن الفيل في الغرفة الذي يجعل ترميز URL أكثر إرباكاً: ترميز نماذج HTML. عندما ترسل نموذج HTML بـ method="POST"، يرمّز المتصفح بيانات النموذج بتنسيق يسمى application/x-www-form-urlencoded. وهذا التنسيق مشابه تقريباً للترميز بالنسبة المئوية القياسي، لكن بفرق رئيسي واحد يُجنّن الجميع.
المسافات تصبح + بدلاً من %20.
هذا كل شيء. هذا الفرق الرئيسي. لكن يا للهول، كم يسبب من ارتباك.
لماذا هذا الفرق موجود؟ أسباب تاريخية بالطبع. مواصفة ترميز نماذج HTML أقدم من مواصفة ترميز URL، واستخدمت + للمسافات لأن... حسناً، شخص ما ظن أنها تبدو أجمل في التسعينيات. والآن نحن عالقون بها للأبد.
عندما ترسل نموذج HTML، يرسل المتصفح ترويسة Content-Type: application/x-www-form-urlencoded، والخادم يعرف أن + تعني مسافات. لكن إذا كنت تبني روابط يدوياً في JavaScript وتستخدم + للمسافات في جزء المسار، سيرى الخادم علامات زائد حرفية. أوقات ممتعة.
هنا يهم في الممارسة العملية:
ثم هناك multipart/form-data، وهو وحش مختلف تماماً. عندما يتضمن نموذجك رفع ملفات ()، يتحول المتصفح إلى ترميز multipart/form-data، الذي يستخدم حدوداً لفصل الحقول بدلاً من علامات &. لا يستخدم ترميز URL إطلاقاً — كل جزء له ترويساته وجسمه الخاص. إذا حاولت يوماً تحليل طلب multipart يدوياً، فأنت تعرف الألم.
النصيحة العملية: استخدم URLSearchParams لسلاسل الاستعلام وبيانات النماذج. استخدم FormData لرفع الملفات. لا تحاول ترميز هذه الأشياء يدوياً إلا إذا كنت مضطراً فعلاً.
جرّبها بنفسك
تعمل مع روابط تبدو خاطئة؟ الصقها في فاك ترميز URL لرؤية الأحرف الفعلية. تحتاج ترميز قيمة قبل وضعها في رابط؟ مُرمّز URL يتعامل معها فوراً. ولتفكيك الروابط المعقدة إلى مكوناتها، استخدم مُحلّل URL لرؤية كل جزء بوضوح.