التوثيق وحالات الاختبار: أدوات قوية لحل مشكلات تطوير OpenResty
API7.ai
October 23, 2022
بعد تعلم مبادئ وبعض المفاهيم الأساسية لـ OpenResty، سنبدأ أخيرًا في تعلم واجهة برمجة التطبيقات (API).
من تجربتي الشخصية، تعلم واجهة برمجة التطبيقات لـ OpenResty يعتبر سهلًا نسبيًا، لذا لن يتطلب الأمر العديد من المقالات لتقديمه. قد تتساءل: أليست واجهة برمجة التطبيقات هي الجزء الأكثر شيوعًا وأهمية؟ لماذا لا نقضي الكثير من الوقت عليها؟ هناك اعتباران رئيسيان.
أولاً، توفر OpenResty وثائق مفصلة جدًا. مقارنة بالعديد من لغات البرمجة أو المنصات الأخرى، توفر OpenResty ليس فقط تعريفات معاملات واجهة برمجة التطبيقات وقيم الإرجاع، ولكن أيضًا أمثلة كود قابلة للتنفيذ بشكل كامل، مما يوضح لك بوضوح كيفية تعامل واجهة برمجة التطبيقات مع مختلف الظروف الحدودية.
اتباع تعريف واجهة برمجة التطبيقات مع أمثلة الكود والتحذيرات هو أسلوب متسق في وثائق OpenResty. لذلك، بعد قراءة وصف واجهة برمجة التطبيقات، يمكنك على الفور تشغيل الكود النموذجي في بيئتك وتعديل المعلمات والوثائق للتحقق منها وتعزيز فهمك.
ثانيًا، توفر OpenResty حالات اختبار شاملة. كما ذكرت، توضح وثائق OpenResty أمثلة كود لواجهات برمجة التطبيقات. ومع ذلك، بسبب قيود المساحة، لا تعرض الوثائق الإبلاغ عن الأخطاء ومعالجتها في مختلف الحالات غير الطبيعية وطريقة استخدام واجهات برمجة التطبيقات المتعددة.
لكن لا تقلق. يمكنك العثور على معظم هذه المحتويات في مجموعة حالات الاختبار.
بالنسبة لمطوري OpenResty، أفضل مواد تعلم واجهة برمجة التطبيقات هي الوثائق الرسمية وحالات الاختبار، وهي مهنية وصديقة للقارئ.
"أعطِ رجلاً سمكة، تطعمه ليوم واحد؛ علم رجلاً صيد السمك، تطعمه مدى الحياة." دعونا نستخدم مثالًا حقيقيًا لتجربة كيفية استغلال قوة الوثائق ومجموعة حالات الاختبار في تطوير OpenResty.
لنأخذ واجهة برمجة التطبيقات get
لـ shdict كمثال
بناءً على منطقة الذاكرة المشتركة لـ NGINX، فإن القاموس المشترك (shared dictionary) هو كائن قاموس Lua، يمكنه الوصول إلى البيانات عبر عدة عمال وتخزين بيانات مثل تحديد المعدل، التخزين المؤقت، إلخ. هناك أكثر من 20 واجهة برمجة تطبيقات متعلقة بالقواميس المشتركة - وهي أكثر واجهات برمجة التطبيقات استخدامًا وأهمية في OpenResty.
لنأخذ أبسط عملية get
كمثال؛ يمكنك النقر على رابط الوثائق للمقارنة. مثال الكود التالي هو نسخة مصغرة مأخوذة من الوثائق الرسمية.
http {
lua_shared_dict dogs 10m;
server {
location /demo {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
}
}
}
}
كملاحظة سريعة، قبل أن نتمكن من استخدام القاموس المشترك في كود Lua، نحتاج إلى إضافة كتلة ذاكرة في nginx.conf
باستخدام توجيه lua_shared_dict
، والذي يتم تسميته "dogs" وحجمه 10 ميجابايت. بعد تعديل nginx.conf
، تحتاج إلى إعادة تشغيل العملية والوصول إليها باستخدام متصفح أو أمر curl
لرؤية النتائج.
ألا يبدو هذا مرهقًا بعض الشيء؟ دعونا نعدله بشكل أكثر بساطة. كما ترى، استخدام واجهة سطر الأوامر resty بهذه الطريقة له نفس تأثير تضمين الكود في nginx.conf
.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
'
أنت الآن تعرف كيف يعمل nginx.conf
وكود Lua معًا، وقد نجحت في تشغيل طرق set و get للقاموس المشترك. بشكل عام، يتوقف معظم المطورين عند هذا الحد. هناك بعض الأشياء التي تستحق الملاحظة هنا.
- أي مراحل لا يمكن استخدام واجهات برمجة التطبيقات المتعلقة بالذاكرة المشتركة؟
- نرى في كود المثال أن دالة get لها قيمة إرجاع واحدة فقط. متى سيكون هناك أكثر من قيمة إرجاع؟
- ما هو نوع الإدخال لدالة get؟ هل هناك حد للطول؟
لا تقلل من شأن هذه الأسئلة؛ يمكن أن تساعدنا على فهم OpenResty بشكل أفضل، وسأأخذك خلالها واحدة تلو الأخرى.
السؤال 1: أي مراحل لا يمكن استخدام واجهات برمجة التطبيقات المتعلقة بالذاكرة المشتركة؟
لننظر إلى السؤال الأول. الإجابة بسيطة؛ الوثائق لديها قسم context
(أي قسم السياق) يوضح البيئات التي يمكن استخدام واجهة برمجة التطبيقات فيها.
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
كما ترى، لا يتم تضمين مراحل init
و init_worker
، مما يعني أن واجهة برمجة التطبيقات get
للذاكرة المشتركة لا يمكن استخدامها في هاتين المرحلتين. يرجى ملاحظة أن كل واجهة برمجة تطبيقات للذاكرة المشتركة يمكن استخدامها في مراحل مختلفة. على سبيل المثال، يمكن استخدام واجهة برمجة التطبيقات set
في مرحلة init
.
دائمًا، اقرأ الوثائق عند استخدامها. بالطبع، قد تحتوي وثائق OpenResty أحيانًا على أخطاء ونقائص، لذا تحتاج إلى التحقق منها باختبارات فعلية.
بعد ذلك، دعونا نعدل مجموعة الاختبار للتأكد من أن مرحلة init
يمكنها تشغيل واجهة برمجة التطبيقات get
للقاموس المشترك.
كيف يمكننا العثور على مجموعة حالات الاختبار المتعلقة بالذاكرة المشتركة؟ حالات اختبار OpenResty كلها موجودة في الدليل /t
ويتم تسميتها بشكل منتظم، أي self-incremented-number-function-name.t
. ابحث عن shdict
، وستجد 043-shdict.t
، وهي مجموعة حالات اختبار الذاكرة المشتركة، والتي تحتوي على ما يقرب من 100 حالة اختبار، بما في ذلك اختبارات لظروف مختلفة طبيعية وغير طبيعية.
لنجرب تعديل أول حالة اختبار.
يمكنك استبدال مرحلة content
بمرحلة init
وإزالة الكود الزائد لمعرفة ما إذا كانت واجهة get
تعمل. ليس عليك فهم كيفية كتابة وتنظيم وتشغيل حالة الاختبار في هذه المرحلة. تحتاج فقط إلى معرفة أنها تختبر واجهة get
.
=== TEST 1: string key, int value
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
init_by_lua '
local dogs = ngx.shared.dogs
local val = dogs:get("foo")
ngx.say(val)
';
}
--- request
GET /test
--- response_body
32
--- no_error_log
[error]
--- ONLY
يجب أن تكون قد لاحظت أنه في نهاية حالة الاختبار، أضفت علامة --ONLY
، مما يعني تجاهل جميع حالات الاختبار الأخرى، وتشغيل هذه الحالة فقط، وبالتالي تحسين سرعة التشغيل. لاحقًا في قسم الاختبار، سأشرح العلامات المختلفة.
بعد التعديل، يمكننا تشغيل حالة الاختبار باستخدام أمر prove
.
prove t/043-shdict.t
ثم، ستظهر لك رسالة خطأ تؤكد قيود المرحلة الموضحة في الوثائق.
nginx: [emerg] "init_by_lua" directive is not allowed here
السؤال 2: متى يكون لدالة get
أكثر من قيمة إرجاع؟
لننظر إلى السؤال الثاني، والذي يمكن تلخيصه من الوثائق الرسمية. تبدأ الوثائق بوصف syntax
لهذه الواجهة.
value, flags = ngx.shared.DICT:get(key)
في الظروف العادية.
- المعلمة الأولى
value
تُرجع القيمة المقابلة للمفتاحkey
في القاموس؛ ومع ذلك، عندما لا يكون المفتاح موجودًا أو انتهت صلاحيته، تكون قيمةvalue
هيnil
. - المعلمة الثانية،
flags
، أكثر تعقيدًا بعض الشيء؛ إذا كانت واجهة set قد حددت flags، فإنها تُرجعها. وإلا، فلا.
إذا حدث خطأ في استدعاء واجهة برمجة التطبيقات، فإن value
تُرجع nil
، و flags
تُرجع رسالة خطأ محددة.
من المعلومات الموجزة في الوثائق، يمكننا أن نرى أن local v = dogs:get("Jim")
مكتوبة بمعلمة استقبال واحدة فقط. هذا النوع من الكتابة غير مكتمل لأنه يغطي فقط سيناريو الاستخدام النموذجي دون استقبال معلمة ثانية أو إجراء معالجة الاستثناءات. يمكننا تعديلها إلى ما يلي.
local data, err = dogs:get("Jim")
if data == nil and err then
ngx.say("get not ok: ", err)
return
end
كما هو الحال مع السؤال الأول، يمكننا البحث في مجموعة حالات الاختبار لتأكيد فهمنا للوثائق.
=== TEST 65: get nil key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(nil)
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: nil key
--- no_error_log
[error]
في حالة الاختبار هذه، يكون إدخال واجهة get
هو nil
، ورسالة الخطأ المرتجعة هي nil key
. هذا يؤكد أن تحليلنا للوثائق صحيح ويقدم إجابة جزئية للسؤال الثالث. على الأقل، لا يمكن أن يكون الإدخال لـ get هو nil.
السؤال 3: ما هو نوع الإدخال لدالة get
؟
أما بالنسبة للسؤال الثالث، ما هي أنواع معاملات الإدخال التي يمكن أن تكون لـ get
؟ دعونا نتحقق من الوثائق أولاً، ولكن للأسف، ستجد أن الوثائق لا تحدد أنواع المفاتيح القانونية. ماذا نفعل؟
لا تقلق. على الأقل نعرف أن key
يمكن أن يكون من نوع سلسلة ولا يمكن أن يكون nil. هل تتذكر أنواع البيانات في Lua؟ بالإضافة إلى السلاسل و nil، هناك الأرقام، المصفوفات، الأنواع المنطقية، والوظائف. النوعان الأخيران غير ضروريين كمفاتيح، لذا نحتاج فقط إلى التحقق من الأولين: الأرقام والمصفوفات. يمكننا البدء بالبحث في ملف الاختبار عن الحالات التي يتم فيها استخدام الأرقام كـ key
.
=== TEST 4: number keys, string values
مع حالة الاختبار هذه، يمكنك أن ترى أن الأرقام يمكن أيضًا استخدامها كمفاتيح، وسيتم تحويلها داخليًا إلى سلاسل. ماذا عن المصفوفات؟ للأسف، حالة الاختبار لا تغطي ذلك، لذا نحتاج إلى تجربتها بأنفسنا.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:get({})
'
كما هو متوقع، ظهر الخطأ التالي.
ERROR: (command line -e):2: bad argument #1 to 'get' (string expected, got table)
باختصار، يمكننا أن نستنتج أن أنواع key
المقبولة من قبل واجهة برمجة التطبيقات get
هي السلاسل والأرقام.
هل هناك حد لطول المفتاح الذي يتم تمريره؟ هناك حالة اختبار مقابلة هنا.
=== TEST 67: get a too-long key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(string.rep("a", 65536))
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: key too long
--- no_error_log
[error]
عندما يكون طول السلسلة 65536، سيتم إعلامك بأن المفتاح طويل جدًا. يمكنك محاولة تغيير الطول إلى 65535، على الرغم من أنه أقل ببايت واحد فقط، ولكن لن تظهر أخطاء بعد الآن. هذا يعني أن الحد الأقصى لطول المفتاح هو بالضبط 65535.
الخلاصة
أخيرًا، أود أن أذكرك أنه في واجهة برمجة التطبيقات لـ OpenResty، أي قيمة إرجاع تحتوي على رسالة خطأ يجب أن يكون لها متغير لاستقبالها وإجراء معالجة الأخطاء، وإلا ستحدث أخطاء. على سبيل المثال، إذا تم وضع اتصال خاطئ في مجموعة الاتصالات، أو إذا فشل استدعاء واجهة برمجة التطبيقات في متابعة المنطق الذي يليه، فإن ذلك سيسبب شكاوى مستمرة.
لذا، إذا واجهت مشكلة عند كتابة كود OpenResty، ما هي الطريقة المعتادة لحلها؟ هل هي الوثائق، قوائم البريد، أو قنوات أخرى؟
مرحبًا بمشاركة هذه المقالة مع زملائك وأصدقائك حتى نتمكن من التواصل والتحسين.