بدء العمل مع Lua

API7.ai

September 23, 2022

OpenResty (NGINX + Lua)

بعد فهم عام لأساسيات NGINX، سنتعلم المزيد عن Lua. إنها لغة البرمجة المستخدمة في OpenResty، ومن الضروري إتقان قواعدها الأساسية.

Lua هي لغة برمجة صغيرة ودقيقة، ولدت في مختبر جامعي في البرازيل، ويعني اسمها "القمر الجميل" باللغة البرتغالية. NGINX ولدت في روسيا، Lua في البرازيل، وOpenResty في الصين من بلد المؤلف. ومن المثير للاهتمام أن هذه التقنيات المفتوحة المصدر الذكية الثلاثة جاءت من دول البريكس، وليس من أوروبا أو أمريكا.

تم تصميم Lua لتكون لغة لاصقة بسيطة وخفيفة الوزن وقابلة للتضمين، ولا تسلك الطريق الكبير والجريء. على الرغم من أنك قد لا تكتب كود Lua مباشرة في عملك اليومي، إلا أن Lua تستخدم على نطاق واسع. العديد من الألعاب عبر الإنترنت، مثل World of Warcraft، تستخدم Lua لكتابة الإضافات؛ قاعدة البيانات الرئيسية-القيمة Redis تحتوي على Lua مدمجة للتحكم في المنطق.

من ناحية أخرى، على الرغم من أن مكتبة Lua بسيطة نسبيًا، إلا أنه يمكن استدعاء مكتبات لغة البرمجة C بسهولة، ويمكن استخدام العديد من أكواد لغة البرمجة C الناضجة لها. على سبيل المثال، في OpenResty، ستحتاج غالبًا إلى استدعاء وظائف لغة البرمجة C الخاصة بـ NGINX وOpenSSL، وذلك بفضل قدرة Lua وLuaJIT على الوصول إلى مكتبات C بسهولة.

هنا، سأأخذك في جولة سريعة للتعرف على أنواع البيانات وتركيبات Lua حتى تتمكن من تعلم OpenResty بسلاسة لاحقًا.

البيئة وhello world

لا نحتاج إلى تثبيت بيئة Lua 5.1 القياسية بشكل خاص لأن OpenResty لم يعد يدعم Lua القياسية، بل يدعم فقط LuaJIT. لاحظ أن تركيب Lua الذي أقدمه هنا متوافق أيضًا مع LuaJIT وليس مبنيًا على أحدث إصدار من Lua 5.3.

يمكنك العثور على دليل LuaJIT والملف القابل للتنفيذ في دليل تثبيت OpenResty. أنا في بيئة Mac واستخدمت brew لتثبيت OpenResty، لذا من المحتمل أن يختلف المسار المحلي لديك عن التالي.

$ ll /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit
     lrwxr-xr-x  1 ming  admin    18B  4  2 14:54 /usr/local/Cellar/openresty/1.13.6.2/luajit/bin/luajit -> luajit-2.1.0-beta3

يمكنك أيضًا العثور عليه في دليل الملفات القابلة للتنفيذ في النظام.

 $ which luajit
 /usr/local/bin/luajit

تحقق من إصدار LuaJIT.

$ luajit -v
 LuaJIT 2.1.0-beta2 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/

بعد التحقق من هذه المعلومات، يمكنك إنشاء ملف جديد 1.lua واستخدام LuaJIT لتشغيل كود hello world.

$ cat 1.lua
print("hello world")
$ luajit 1.lua
 hello world

بالطبع، يمكنك أيضًا استخدام resty لتشغيله مباشرة، مع العلم أنه يتم تنفيذه في النهاية باستخدام LuaJIT أيضًا.

$ resty -e 'print("hello world")'
 hello world

كلتا الطريقتين لتشغيل hello world ممكنة. أنا أفضل طريقة resty لأن الكثير من كود OpenResty يتم تشغيله أيضًا بواسطة resty لاحقًا.

أنواع البيانات

لا يوجد الكثير من أنواع البيانات في Lua، ويمكنك إرجاع نوع القيمة باستخدام الدالة type، مثل التالي.

$ resty -e 'print(type("hello world"))
 print(type(print))
 print(type(true))
 print(type(360.0))
 print(type({}))
 print(type(nil))
 '

سيتم طباعة المعلومات التالية.

 string
 function
 boolean
 number
 table
 nil

هذه هي أنواع البيانات الأساسية في Lua. دعونا نقدمها باختصار.

النصوص

في Lua، النص هو قيمة غير قابلة للتغيير. إذا كنت تريد تعديل نص، يجب عليك إنشاء نص جديد. هذا النهج له مزايا وعيوب: الميزة هي أنه حتى إذا ظهر النص نفسه عدة مرات، فإنه يوجد نسخة واحدة فقط في الذاكرة، ولكن العيب واضح أيضًا: إذا كنت تريد تعديل ودمج النصوص، فإنك تنشئ الكثير من النصوص الإضافية غير الضرورية.

لنأخذ مثالاً لتوضيح هذا العيب. في Lua، نستخدم علامتي نقطة للإشارة إلى إضافة النصوص. الكود التالي يدمج الأرقام من 1 إلى 10 كنصوص.

$ resty -e 'local s  = ""
 for i = 1, 10 do
     s = s .. tostring(i)
 end
 print(s)'

هنا نقوم بالتكرار عشر مرات، والنتيجة الأخيرة فقط هي ما نحتاجه؛ النصوص الجديدة التسعة في المنتصف غير مفيدة. فهي لا تشغل مساحة إضافية فحسب، بل تستهلك أيضًا عمليات CPU غير ضرورية.

بالطبع، سيكون لدينا حل لهذا لاحقًا في قسم تحسين الأداء.

أيضًا، في Lua، لديك ثلاث طرق للتعبير عن النص: علامات الاقتباس المفردة، علامات الاقتباس المزدوجة، والأقواس الطويلة ([[]]). الأولان سهلان الفهم ويتم استخدامهما بشكل عام في اللغات الأخرى، فما فائدة الأقواس الطويلة؟

لنلقي نظرة على مثال ملموس.

$ resty -e 'print([[string has \n and \r]])'
 string has \n and \r

يمكنك أن ترى أن النصوص داخل الأقواس الطويلة لا يتم تهريبها بأي شكل.

قد تسأل سؤالاً آخر: ماذا لو كان النص أعلاه يتضمن الأقواس الطويلة؟ الجواب بسيط: أضف واحدًا أو أكثر من رموز = في منتصف الأقواس الطويلة.

$ resty -e 'print([=[ string has a [[]]. ]=])'
  string has a [[]].

القيم المنطقية

هذا بسيط، true وfalse. في Lua، فقط nil وfalse يعتبران خطأ؛ كل شيء آخر يعتبر صحيحًا، بما في ذلك 0 والنص الفارغ. يمكننا التحقق من ذلك بالكود التالي.

$ resty -e 'local a = 0
 if a then
   print("true")
 end
 a = ""
 if a then
   print("true")
 end'

هذا النوع من الحكم غير متسق مع العديد من لغات التطوير الشائعة، لذا لتجنب الأخطاء في مثل هذه الأمور، يمكنك كتابة كائن المقارنة بشكل صريح، مثل التالي.

$ resty -e 'local a = 0
 if a == false then
   print("true")
 end
 '

الأرقام

نوع الرقم في Lua يتم تنفيذه كرقم فاصلة عائمة مزدوج الدقة. ومن الجدير بالذكر أن LuaJIT يدعم وضع dual-number، مما يعني أن LuaJIT يخزن الأعداد الصحيحة كأعداد صحيحة والأعداد الفاصلة العائمة كأعداد فاصلة عائمة مزدوجة الدقة، اعتمادًا على السياق.

بالإضافة إلى ذلك، يدعم LuaJيت long-long integers للأعداد الصحيحة الكبيرة، مثل المثال التالي.

$ resty -e 'print(9223372036854775807LL - 1)'
9223372036854775806LL

الدوال

الدوال هي مواطنون من الدرجة الأولى في Lua، ويمكنك تخزين دالة في متغير أو استخدامها كمرجع وارد أو صادر لدالة أخرى.

على سبيل المثال، التصريحين التاليين للدوال متكافئان تمامًا.

function foo()
 end

و

foo = function ()
 end

الجداول

الجدول هو بنية البيانات الوحيدة في Lua وهو مهم جدًا بطبيعة الحال، لذا سأخصص قسمًا خاصًا له لاحقًا. يمكننا البدء بالنظر إلى مثال بسيط للكود.

$ resty -e 'local color = {first = "red"}
print(color["first"])'
 red

القيم الفارغة

في Lua، القيمة الفارغة هي nil. إذا قمت بتعريف متغير ولكن لم تقم بتعيين قيمة له، فإن قيمته الافتراضية هي nil.

$ resty -e 'local a
 print(type(a))'
 nil

عندما تدخل نظام OpenResty، ستجد العديد من القيم الفارغة، مثل ngx.null، وغيرها. سنتحدث أكثر عن ذلك لاحقًا.

أنواع البيانات في Lua، سأقدمها بشكل أساسي بهذا القدر، أولاً لأعطيك أساسًا. سنستمر في تعلم ما تحتاج إلى إتقانه لاحقًا في المقالة. التعلم من خلال الممارسة والاستخدام هو دائمًا الطريقة الأكثر ملاءمة لاستيعاب المعرفة الجديدة.

المكتبات القياسية الشائعة

غالبًا، تعلم لغة ما هو في الواقع تعلم مكتباتها القياسية.

Lua صغيرة نسبيًا ولا تحتوي على الكثير من المكتبات القياسية المدمجة. أيضًا، في بيئة OpenResty، تكون مكتبة Lua القياسية ذات أولوية منخفضة جدًا. بالنسبة لنفس الوظيفة، أوصي باستخدام OpenResty API أولاً، ثم وظائف مكتبة LuaJIT، وأخيرًا وظائف Lua العادية.

OpenResty's API > LuaJIT's library functions > standard Lua's functions هي أولوية سيتم ذكرها بشكل متكرر من حيث القابلية للاستخدام والأداء.

ومع ذلك، على الرغم من ذلك، سنستخدم حتمًا بعض مكتبات Lua في مشاريعنا الفعلية. هنا، قمت باختيار بعض المكتبات القياسية الأكثر استخدامًا لتقديمها، وإذا كنت ترغب في معرفة المزيد، يمكنك التحقق من الوثائق الرسمية لـ Lua.

مكتبة النصوص

معالجة النصوص هي ما نستخدمه غالبًا وحيث تكمن الفخاخ الأكبر.

قاعدة بسيطة واحدة هي أنه إذا كانت التعبيرات العادية متضمنة، يرجى استخدام ngx.re.* المقدمة من OpenResty لحلها، وليس معالجة string.* الخاصة بـ Lua. وذلك لأن التعبيرات العادية في Lua فريدة ولا تتوافق مع مواصفات PCRE، وأعتقد أن معظم المهندسين لن يتمكنوا من التعامل معها.

واحدة من أكثر وظائف مكتبة النصوص استخدامًا هي string.byte(s [, i [, j ]])، والتي تُرجع الرمز ASCII المقابل للأحرف s[i], s[i + 1], s[i + 2], ------, s[j]. القيمة الافتراضية لـ i هي 1، البايت الأول، والقيمة الافتراضية لـ j هي i.

لنلقي نظرة على نموذج كود.

$ resty -e 'print(string.byte("abc", 1, 3))
 print(string.byte("abc", 3)) -- المعلمة الثالثة مفقودة، المعلمة الثالثة هي نفسها الثانية افتراضيًا، وهي 3
 print(string.byte("abc"))    -- المعلمتان الثانية والثالثة مفقودتان، كلاهما افتراضيًا 1
 '

مخرجاتها هي:

 979899
 99
 97

مكتبة الجداول

في سياق OpenResty، لا أوصي باستخدام معظم مكتبات الجداول المدمجة في Lua، باستثناء بعض الوظائف مثل table.concat وtable.sort. أما تفاصيلها، فسنتركها لفصل LuaJIT.

هنا سأذكر باختصار table.concat. table.concat يستخدم بشكل عام في سيناريوهات دمج النصوص، مثل المثال أدناه. يمكنه تجنب إنشاء الكثير من النصوص غير الضرورية.

$ resty -e 'local a = {"A", "b", "C"}
 print(table.concat(a))'

مكتبة الرياضيات

تتكون مكتبة الرياضيات في Lua من مجموعة قياسية من الدوال الرياضية. إدخال مكتبة الرياضيات يثري لغة برمجة Lua ويجعل كتابة البرامج أسهل.

في مشاريع OpenResty، نادرًا ما نستخدم Lua لإجراء عمليات رياضية. ومع ذلك، فإن وظيفتين مرتبطتين بالأرقام العشوائية، math.random() وmath.randomseed()، تُستخدمان بشكل شائع، مثل الكود التالي، الذي يمكنه إنشاء رقمين عشوائيين في نطاق محدد.

$ resty -e 'math.randomseed (os.time())
print(math.random())
print(math.random(100))'

المتغيرات الوهمية

بعد فهم هذه المكتبات القياسية المشتركة، دعونا نتعلم مفهومًا جديدًا - المتغيرات الوهمية.

تخيل سيناريو حيث تُرجع دالة عدة قيم، بعضها لا نحتاجه، فكيف يجب أن نستقبل هذه القيم؟

لا أعرف كيف تشعر حيال هذا، ولكن بالنسبة لي على الأقل، فإن محاولة إعطاء أسماء ذات معنى لهذه المتغيرات غير المستخدمة هي تعذيب.

لحسن الحظ، لدى Lua حل مثالي لهذا، حيث توفر مفهوم المتغير الوهمي الذي يُسمى بشكل تقليدي بشرطة سفلية لتجاهل القيم غير المرغوب فيها وكمكان محدد.

لنأخذ دالة المكتبة القياسية string.find كمثال لرؤية استخدام المتغيرات الوهمية. هذه الدالة العادية تُرجع قيمتين تمثلان بداية ونهاية الفهرس الفرعي على التوالي.

إذا كنا نريد فقط الحصول على الفهرس الفرعي للبداية، فمن السهل الإعلان عن متغير لاستقبال القيمة المرجعة من string.find كما يلي.

$ resty -e 'local start = string.find("hello", "he")
 print(start)'
 1

ولكن إذا كنت تريد فقط الحصول على الفهرس الفرعي للنهاية، فيجب عليك استخدام المتغير الوهمي

$ resty -e 'local  _, end_pos = string.find("hello", "he")
 print(end_pos)'
 2

بالإضافة إلى استخدامها في القيم المرجعة، تُستخدم المتغيرات الوهمية غالبًا في الحلقات، مثل المثال التالي.

$ resty -e 'for _, v in ipairs({4,5,6}) do
     print(v)
 end'
 4
 5
 6

وعندما تكون هناك عدة قيم مرجعة لتجاهلها، يمكنك إعادة استخدام نفس المتغير الوهمي. لن أقدم مثالاً هنا. هل يمكنك محاولة كتابة نموذج كود مثل هذا بنفسك؟ أنت مرحب بنشر الكود في قسم التعليقات للمشاركة والتبادل معي.

الخلاصة

اليوم، أخذنا نظرة سريعة على هياكل البيانات وتركيبات Lua القياسية، وأنا متأكد من أنك حصلت على نظرة أولية على هذه اللغة البسيطة والمدمجة. في الدرس القادم، سأأخذك في العلاقة بين Lua وLuaJIT، مع التركيز على LuaJIT كونها المحور الرئيسي لـ OpenResty وتستحق الغوص فيها.

أخيرًا، أريد أن أتركك مع سؤال آخر يستحق التفكير.

تذكر الكود الذي تعلمته في هذا المنشور عندما تحدثنا عن مكتبة الرياضيات؟ إنه يولد رقمين عشوائيين في نطاق محدد.

$ resty -e 'math.randomseed (os.time())
print(math.random())
 print(math.random(100))'

ومع ذلك، ربما لاحظت أن الكود يتم بذر البذور باستخدام الطابع الزمني الحالي. هل هناك مشكلة في هذا النهج؟ وكيف يجب أن نولد بذورًا جيدة؟ غالبًا، الأرقام العشوائية التي نطورها ليست عشوائية ولديها مخاطر أمنية كبيرة.

مرحبًا بك في مشاركة آرائك معنا، وأيضًا في مشاركة هذا المنشور مع زملائك وأصدقائك. دعونا نتواصل ونحسن معًا.