كيفية ترميز معاملات الاستعلام بشكل صحيح في واجهات REST API
يجب ترميز معاملات الاستعلام في واجهات REST API باستخدام percent-encoding وفقًا لمعيار RFC 3986 لتضمين الأحرف الخاصة مثل &, =, والمسافات وأحرف Unicode بأمان. استخدم encodeURIComponent() في JavaScript، أو urllib.parse.quote() في Python، أو URLEncoder.encode() في Java.
لماذا تحتاج معاملات الاستعلام إلى الترميز
تستخدم سلاسل الاستعلام في واجهات REST API تنسيقًا منظمًا حيث تُفصل أزواج المفتاح والقيمة باستخدام &، وتُفصل المفاتيح عن القيم باستخدام =. إذا احتوت قيمة أحد المعاملات على أي من أحرف الفصل هذه، أو أحرف محجوزة أخرى مثل # أو + أو المسافات، فإن بنية سلسلة الاستعلام تنكسر ويستقبل الخادم بيانات غير صحيحة.
لنأخذ هذا المثال: تريد البحث عن "salt & pepper" في إحدى واجهات API. بدون ترميز، يخبر الرابط /search?q=salt & pepper الخادمَ بوجود معاملين اثنين: q=salt و pepper (بدون قيمة). ومع الترميز الصحيح، يرسل الرابط /search?q=salt%20%26%20pepper بشكل صحيح معاملًا واحدًا q بقيمة "salt & pepper".
وإلى جانب صحة البيانات، يمنع الترميز الصحيح أيضًا الثغرات الأمنية. فقد يؤدي إدخال المستخدم غير المُرمَّز في الروابط إلى هجمات الحقن (injection) وتسميم ذاكرة التخزين المؤقت (cache poisoning) واستغلالات أخرى. لذا احرص دائمًا على ترميز قيم المعاملات قبل تضمينها في الروابط.
الترميز في لغات البرمجة المختلفة
توفر كل لغة برمجة رئيسية دوالًا مدمجة لترميز الروابط. وفيما يلي كيفية ترميز معاملات الاستعلام بشكل صحيح في أكثر اللغات شيوعًا.
// JavaScript
// استخدم encodeURIComponent() لقيم المعاملات الفردية
const query = 'price >= 100 & category = books';
const url = 'https://api.example.com/search?q=' + encodeURIComponent(query);
// "https://api.example.com/search?q=price%20%3E%3D%20100%20%26%20category%20%3D%20books"
// أو استخدم URLSearchParams لعدة معاملات
const params = new URLSearchParams({
q: 'price >= 100 & category = books',
page: '1',
sort: 'price'
});
const url2 = 'https://api.example.com/search?' + params.toString();
# Python
from urllib.parse import urlencode, quote
# استخدام urlencode لعدة معاملات
params = {
'q': 'price >= 100 & category = books',
'page': 1,
'sort': 'price'
}
query_string = urlencode(params)
url = f'https://api.example.com/search?{query_string}'
# استخدام quote لقيمة واحدة
value = quote('price >= 100 & category = books', safe='')
url = f'https://api.example.com/search?q={value}'
// Java
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
String query = "price >= 100 & category = books";
String encoded = URLEncoder.encode(query, StandardCharsets.UTF_8);
// "price+%3E%3D+100+%26+category+%3D+books"
// ملاحظة: يستخدم URLEncoder الرمز + للمسافات (ترميز النماذج)
// للحصول على ترميز RFC 3986، استبدل + بـ %20
String rfc3986 = encoded.replace("+", "%20");
String url = "https://api.example.com/search?q=" + rfc3986;
// C#
using System.Net;
using System.Web;
string query = "price >= 100 & category = books";
// تتبع Uri.EscapeDataString معيار RFC 3986
string encoded = Uri.EscapeDataString(query);
// "price%20%3E%3D%20100%20%26%20category%20%3D%20books"
// يستخدم HttpUtility.UrlEncode ترميز النماذج (+ للمسافات)
string formEncoded = HttpUtility.UrlEncode(query);
// "price+%3e%3d+100+%26+category+%3d+books"
// Go
package main
import (
"fmt"
"net/url"
)
func main() {
// يرمّز url.QueryEscape لسلاسل الاستعلام (+ للمسافات)
encoded := url.QueryEscape("price >= 100 & category = books")
// "price+%3E%3D+100+%26+category+%3D+books"
// يرمّز url.PathEscape لأجزاء المسار (%20 للمسافات)
pathEncoded := url.PathEscape("my file name.pdf")
// "my%20file%20name.pdf"
// استخدام url.Values لبناء سلاسل الاستعلام
params := url.Values{}
params.Set("q", "price >= 100 & category = books")
params.Set("page", "1")
queryString := params.Encode()
fmt.Println(queryString)
}
ترميز المصفوفات والكائنات المتداخلة
كثيرًا ما تحتاج واجهات REST API إلى قبول مصفوفات أو كائنات متداخلة بوصفها معاملات استعلام. ولا يوجد معيار واحد موحّد لترميز هذه الأنواع المعقدة، لذا تستخدم كل واجهة API اصطلاحات مختلفة.
// المفاتيح المكررة (الأوسع دعمًا)
// GET /api/items?tag=javascript&tag=python&tag=go
const params = new URLSearchParams();
['javascript', 'python', 'go'].forEach(tag => params.append('tag', tag));
// صيغة الأقواس (شائعة في PHP وRails وExpress مع qs)
// GET /api/items?tags[]=javascript&tags[]=python&tags[]=go
// يجب ترميزها يدويًا:
const tags = ['javascript', 'python', 'go'];
const qs = tags.map(t => 'tags[]=' + encodeURIComponent(t)).join('&');
// مفصولة بفواصل (بسيطة لكن محدودة)
// GET /api/items?tags=javascript,python,go
const tagList = encodeURIComponent('javascript,python,go');
// الكائنات المتداخلة (صيغة الأقواس)
// GET /api/search?filter[status]=active&filter[min_price]=10
// في Python:
params = {
'filter[status]': 'active',
'filter[min_price]': '10'
}
تحقق دائمًا من توثيق واجهة API الخاصة بك لمعرفة التنسيق المتوقع. وعند تصميم واجهة API جديدة، يُعد أسلوب المفاتيح المكررة الأكثر قابلية للنقل عبر لغات البرمجة ومكتبات HTTP المختلفة.
أفضل الممارسات لفك الترميز من جهة الخادم
عند معالجة الطلبات الواردة من جهة الخادم، تفكّ معظم أطر عمل الويب ترميز معاملات الاستعلام تلقائيًا نيابةً عنك. ومع ذلك، هناك اعتبارات مهمة يجب أخذها في الحسبان لضمان معالجة موثوقة من جهة الخادم.
- تفكّ معظم أطر العمل (Express وDjango وSpring وASP.NET) ترميز معاملات الاستعلام تلقائيًا. لا تفكّ ترميزها يدويًا مرة أخرى، وإلا فستقع في فك ترميز مزدوج.
- تحقّق من صحة القيم المفكوكة مقابل الأنواع والنطاقات المتوقعة قبل استخدامها.
- اضبط حدودًا قصوى لطول الرابط على مستوى خادم الويب (عادةً 2048 أو 8192 حرفًا).
- تعامل مع الحالة التي قد يعني فيها
+إما مسافة أو علامة زائد حرفية، وفقًا لنوع المحتوى. - سجّل الرابط الأصلي (المُرمَّز) لأغراض تصحيح الأخطاء، إذ قد تكون القيم المفكوكة مضلِّلة في السجلات.
// Express.js - يُفكّ ترميز المعاملات تلقائيًا
app.get('/search', (req, res) => {
const query = req.query.q; // مفكوكة الترميز بالفعل
// لا تفعل: decodeURIComponent(req.query.q)
// تحقّق من صحة الإدخال
if (typeof query !== 'string' || query.length > 200) {
return res.status(400).json({ error: 'Invalid query parameter' });
}
// استخدم القيمة المفكوكة بأمان
const results = search(query);
res.json(results);
});
الاعتبارات الأمنية
يُعد الترميز الصحيح لمعاملات الاستعلام جزءًا أساسيًا من أمان واجهات API. وفيما يلي أهم الممارسات الأمنية التي ينبغي اتباعها.
- لا تثق أبدًا بالترميز من جهة العميل. تحقّق دائمًا من صحة المعاملات المفكوكة ونقّها على الخادم، حتى لو رمّزها العميل بشكل صحيح.
- احذر من هجمات الترميز المزدوج. قد يرمّز المهاجم الأحرف الخبيثة مرتين لتجاوز مرشّحات الإدخال التي تفكّ الترميز مرة واحدة فقط. تأكّد من أن مرشّحاتك الأمنية ترى القيم المفكوكة بالكامل.
- امنع اجتياز المسارات (path traversal). يمكن استخدام تسلسلات مُرمَّزة مثل
%2e%2e%2f(../) في معاملات المسار للوصول إلى ملفات خارج الدليل المقصود. افكك ترميز المسارات وتحقّق من صحتها قبل أي عمليات على الملفات. - استخدم الاستعلامات المُعامَلة (parameterized queries). حتى بعد فك ترميز الرابط، لا تدمج أبدًا إدخال المستخدم مباشرةً في استعلامات SQL أو NoSQL. استخدم الاستعلامات المُعامَلة أو أداة ORM.
- حدّ من طول الرابط. يمكن أن تتسبب سلاسل الاستعلام الطويلة جدًا في هجمات حجب الخدمة (denial-of-service). اضبط خادم الويب لرفض الروابط التي تتجاوز طولًا معقولًا.
- رمّز المخرجات أيضًا. عند عكس قيم معاملات الاستعلام في استجابات HTML، طبّق ترميز كيانات HTML لمنع هجمات البرمجة النصية عبر المواقع (XSS).