يوحّد RFC 9457 problem+json استجاباتِ الأخطاء ككائن JSON بحقول type وtitle وstatus وdetail وinstance، يُقدَّم بنوع application/problem+json — فيحلّل العملاء الأخطاء بانتظام. ويقسّم ترقيمُ الصفحات المجموعاتِ الكبيرة إلى صفحات باستخدام cursor (ثابت) أو offset (بسيط)، مع روابط next/prev محمولة في ترويسة RFC 8288 Link.
problem+json: كائن خطأ قياسي
الأشكال المخصّصة للأخطاء تجبر كل عميل على معالجة خاصة لكل واجهة. ويُثبِّت RFC 9457 (الذي يُبطِل RFC 7807) صيغةَ خطأ JSON واحدة قابلة للتمديد، تُقدَّم بنوع الوسائط application/problem+json بحيث يعرف العميل أن يحلّلها كمشكلة. وللكائن خمسة أعضاء قياسية: type، عنوان URI يعرّف صنف المشكلة ويعمل رابطًا لوثائق بشرية؛ وtitle، ملخّص قصير ثابت مقروء للبشر لذلك الصنف؛ وstatus، رمز حالة HTTP مردَّد في الجسم؛ وdetail، شرح مقروء للبشر خاصّ بهذه الحالة؛ وinstance، عنوان URI يعرّف هذه الحالة بعينها. وأي أعضاء إضافية (مصفوفة أخطاء تحقّق أو معرّف طلب أو تلميح إعادة إرسال) تمدّد الكائن دون كسر القياسية، فيقرأ عميلٌ عام الحقولَ المشتركة ويقرأ عميلٌ محدّد الامتدادات أيضًا.
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json
{
"type": "https://example/problems/invalid-destination",
"title": "Invalid destination number",
"status": 422,
"detail": "The field 'to' is not a valid E.164 number.",
"instance": "/v1/messages/req-01HZY9",
"errors": [ { "field": "to", "issue": "not_e164" } ]
}الـ type والـ title والـ instance
الانضباط الذي يجعل problem+json نافعًا هو إبقاء type وtitle ثابتين وموجّهين للآلة بينما يتغيّر detail وinstance لكل حالة. وينبغي أن يتفرّع العميل على type (عنوان URI ثابت)، لا على نثر detail أو title أبدًا، لأن detail حرّ في التغيّر وقد يُترجَم. وعنوان URI في type يُفضَّل أن يحلّ إلى وثائق ذلك الصنف من الأخطاء، بحيث يهبط المطوّر الذي يتبعه على شرح وعلاج. ويعرّف instance الطلب الفاشل بعينه — مفيد للاقتباس في تذكرة دعم — وكثيرًا ما يعكس معرّف طلب تعيده الواجهة في ترويسة أيضًا. ولأن type وtitle يعرّفان الصنف وstatus/detail/instance يصفان الحالة، يمكن إعادة استخدام نوع مشكلة واحد عبر إخفاقات كثيرة دون لبس.
تصنيف الأخطاء: قابل للإعادة مقابل نهائي
إلى جانب الشكل، يحتاج العميل معرفة ما يفعل، وصنفُ رمز الحالة يجيب عن ذلك. فالـ 4xx خطأ عميل: الطلب كما أُرسِل لن ينجح أبدًا، فإعادة إرساله دون تغيير عبث — والعلاج تصحيح الطلب (400/422 للمُدخَل السيّئ، و401/403 للمصادقة، و404 لمورد مفقود، و409 لتعارض). والـ 5xx خطأ خادم أو طبقة عليا: قد ينجح الطلب لاحقًا، فهو قابل للإعادة بتباطؤ تدريجي — يُفضَّل تحت الـ Idempotency-Key نفسه بحيث لا تُرسِل الإعادةُ إرسالًا مزدوجًا. والـ 429 هو الحالة العابرة الخاصة: قابل للإعادة، لكن بعد تأخير Retry-After فقط. وترميز هذا في رمز الحالة (وتعزيزه بنوع مشكلة) يتيح للعميل أن يقرّر آليًا بين أصلِح-وأعِد، وتراجَع-وأعِد، واستسلم.
| الفئة | المعنى | قابل لإعادة المحاولة؟ | إجراء العميل |
|---|---|---|---|
| 400 / 422 | إدخال غير صحيح / غير صالح | No | أصلح الطلب، ثم أعد إرساله |
| 401 / 403 | غير موثق / ممنوع | No | إصلاح بيانات الاعتماد أو النطاق |
| 404 | لم يتم العثور على المورد | No | صحح المعرف |
| 409 | تعارض الحالة (مثل إعادة استخدام المفتاح) | No | حل النزاع |
| 429 | تم تجاوز حد المعدل | نعم (مؤجل) | انتظر مدة Retry-After، ثم أعد المحاولة |
| 500 / 502 / 503 | فشل الخادم / الطرف السابق (upstream) | نعم | التراجع (backoff) + إعادة المحاولة بنفس مفتاح عدم التكرار (idempotency key) |
ترقيم الصفحات: cursor مقابل offset
نقطة نهاية لمجموعة قد تعيد ملايين الرسائل يجب أن تُرقِّم الصفحات. والنموذجان الرائجان يوازنان بين البساطة والصحة. فترقيم offset (?limit=50&offset=100) تافه ويتيح للعميل القفز إلى صفحة اعتباطية، لكنه غير مستقرّ على مجموعة متغيّرة: فإن أُدرِجت صفوف أو حُذِفت بين الصفحات، تنزاح العناصر فيتخطّى العميل سجلّات أو يكرّرها — وتبطؤ الـ offsets العميقة لأن على المخزن مسح كل ما قبلها ونبذه. وترقيم cursor (keyset) يسلّم العميل مؤشّرًا معتِمًا يرمّز موضع آخر عنصر رآه (مثلًا معرّفًا وطابعًا زمنيًا)؛ والطلب التالي يطلب الصفوف بعد ذلك المؤشّر. وهو مستقرّ تحت الكتابات المتزامنة ويبقى سريعًا عند أي عمق لأن الاستعلام يقفز مباشرةً إلى المؤشّر، على حساب عدم دعم القفز الاعتباطي بين الصفحات. وللموارد عالية الحجم كثيفة الإلحاق كسجلّات الرسائل، ترقيمُ cursor هو الافتراض الصائب.
| الخاصية | الإزاحة | المؤشر (keyset) |
|---|---|---|
| الطلب | ?limit=50&offset=100 | ?limit=50&after=<cursor> |
| الانتقال المباشر إلى أي صفحة | نعم | لا (متسلسل فقط) |
| مستقر في ظل عمليات الإضافة/الحذف | لا — تتزحزح العناصر | نعم |
| أداء الصفحات العميقة | يتراجع الأداء (مسح + تجاهل) | ثابت (الانتقال إلى المؤشر) |
| الأفضل لـ | مجموعات صغيرة ومستقرة | سجلات كبيرة يغلب عليها الإلحاق |
ترويسة Link (RFC 8288)
بدل دفن عناوين next/previous في الجسم، تستطيع الواجهة حملها في ترويسة Link القياسية المُعرَّفة في RFC 8288 (Web Linking)، باستخدام أنواع علاقة مسجّلة — next وprev وfirst وlast. ويتبع العميل رابط rel=«next» للتقدّم دون بناء العناوين بنفسه أو معرفة مخطّط الـ cursor، مما يُبقي آليّة الترقيم مضبوطة بالخادم بالكامل ويتيح لها التطوّر (offset اليوم، cursor غدًا) دون كسر العملاء. ومن المقاربات الشائعة على سبيل الاحتياط أيضًا عكسُ الروابط في جسم JSON للعملاء الذين لا يقرؤون الترويسات، لكن ترويسة Link هي الموطن المُعيَّر عند طبقة النقل لها وهي الآليّة نفسها التي يفهمها متصفّح أو عميل واجهة حَسَن السلوك أصلًا.
HTTP/1.1 200 OK
Content-Type: application/json
Link: </v1/messages?after=01HZY8Q3M6&limit=50>; rel="next",
</v1/messages?limit=50>; rel="first"
{ "data": [ /* up to 50 messages */ ],
"page": { "count": 50, "has_more": true } }حدود حجم الصفحة وقيمها الافتراضية
يجب تحديد حجم الصفحة عند الخادم لا تركه للعميل، وإلّا صار طلبٌ واحدٌ لصفحة ضخمة ناقلًا لهجوم حرمان من الخدمة ومصدرًا لزمن استجابة غير متوقَّع. والنمط قيمة افتراضية معقولة (تُستخدَم حين يُغفِل العميل limit) وحدٌّ أقصى صلب يقصره الخادم بصمت — فالعميل الذي يطلب أكثر يتلقّى الحدّ الأقصى ببساطة. والإعلان عن حجم الصفحة الفعّال وعمّا إذا بقيت صفحات (رايةُ has_more أو وجودُ رابط rel=«next») يتيح للعميل التكرار بأمان: يظلّ يتبع next حتى لا يبقى next. وبالاقتران مع ترقيم cursor وأخطاء problem+json، يعطي هذا واجهةَ مجموعة متوقَّعةَ الاستهلاك عند أي مقياس. وتوثّق BroadNet رموز أخطائها وترقيم صفحاتها الفعلي للمطوّرين.
الأسئلة المتكررة
ما هو problem+json ولماذا يُستخدَم؟
problem+json (RFC 9457، الذي يُبطِل RFC 7807) صيغةُ خطأ JSON قياسية تُقدَّم بنوع application/problem+json، بأعضاء type وtitle وstatus وdetail وinstance. وتتيح لكل عميل تحليل الأخطاء بالطريقة نفسها بدل معالجة خاصة لشكل مخصّص لكل واجهة، وهي قابلة للتمديد بحيث تُضاف أعضاء إضافية دون كسر القياسية.
على أي حقل من problem+json ينبغي أن يتفرّع العميل؟
على type — عنوان URI ثابت يعرّف صنف الخطأ. أما title وdetail فمقروءان للبشر وقد يتغيّران أو يُترجَمان، لذا يجب ألّا تتفرّع الشفرة على نثرهما أبدًا. وعنوان URI في type يُفضَّل أن يحلّ إلى وثائق ذلك الخطأ، بينما يصف status وdetail وinstance الحالةَ بعينها.
كيف يعرف العميل ما إذا كان يُعيد إرسال خطأ؟
من صنف رمز الحالة. فالـ 4xx خطأ عميل لن ينجح دون تغيير، فأصلِح الطلب بدل الإعادة. والـ 5xx خطأ خادم/طبقة عليا قد ينجح لاحقًا، فأعِد المحاولة بتباطؤ تدريجي تحت الـ Idempotency-Key نفسه. والـ 429 قابل للإعادة لكن بعد تأخير Retry-After فقط.
ما الفرق بين ترقيم cursor وoffset؟
offset (?limit&offset) بسيط ويدعم القفز إلى أي صفحة لكنه غير مستقرّ حين تُدرَج صفوف أو تُحذَف بين الطلبات ويبطؤ عند الـ offsets العميقة. وترقيم cursor (keyset) يمرّر علامة معتِمة لآخر عنصر رآه ويطلب الصفوف بعده — مستقرّ تحت الكتابات المتزامنة وسريع عند أي عمق، لكن تسلسليًا فقط. وcursor هو الافتراض الصائب لسجلّات الرسائل الكبيرة.
كيف تساعد ترويسة RFC 8288 Link في ترقيم الصفحات؟
تحمل عناوين صفحات next وprev وfirst وlast في ترويسة قياسية باستخدام أنواع علاقة مسجّلة، فيتبع العميل rel=«next» للتقدّم دون بناء عناوين أو معرفة مخطّط cursor. وهذا يُبقي آليّة الترقيم مضبوطة بالخادم وحرّة في التطوّر، وهي آليّة يفهمها عملاء HTTP العامّون أصلًا.
لماذا يُحدَّد حجم الصفحة عند الخادم؟
لأن حجم صفحة غير محدود يتيح لطلب واحد سحب مجموعة نتائج هائلة، وهو خطر حرمان من الخدمة ويعطي زمن استجابة غير متوقَّع. ويطبّق الخادم قيمة افتراضية حين يُغفَل limit ويقصره بصمت إلى حدّ أقصى صلب، ويعلن عمّا إذا بقيت صفحات بحيث يستطيع العملاء التكرار باتّباع next حتى يغيب.
مواصفات المصدر
تستند هذه الصفحة إلى المعايير المنشورة. يفتح في علامة تبويب جديدة.