Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

<none>

Hello our valued visitor, We present you the best web solutions and high quality graphic designs with a lot of features. just login to your account and enjoy ...

CAPTCHA
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.
10 + 10 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.

أخبار تكنلوجيا

رقم الخبر عنوان الخبر التفاصيل
77,407 أبل تواجه فيسبوك.. تضخ استثمارات للتوسع في "الميتافيرس"

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

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

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

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

ساعدت تعليقات كوك يوم الخميس فضلا عن أرباح ومبيعات فصلية فاقت التوقعات على ارتفاع أسهم أبل نحو 5% في تعاملات ما بعد الإغلاق.

وقفزت إيرادات خدمات أبل 24% إلى 19.5 مليار دولار في الربع الرابع من 2021 والذي تخلله موسم العطلات، متجاوزة تقديرات المحللين البالغة 18.6 مليار دولار.

السحر يكمن في التداخل

وبلغ عدد مشتركي مجموعة خدمات الشركة المدفوعة مثل بث الموسيقى والألعاب عبر الإنترنت 785 مليونا في الربع الرابع من 2021، ارتفاعا من 620 مليونا قبل عام و745 مليونا في الربع الثالث.

ومن ناحية أخرى، قال كوك إن جهود البحث والتطوير التي تبذلها الشركة تركز على تداخل الأجهزة والبرمجيات والخدمات.

وأضاف "هذا هو المكان الذي يمكن فيه السحر حقا... هناك قدر كبير من الاستثمار في أشياء ليست موجودة في السوق في هذه المرحلة".

ومن شأن التوسع في مجال الواقع المعزز أن يفتح الباب أمام زيادة الاشتراكات. ويمكن أن تصبح العروض الحالية لتدريبات اللياقة البدنية والمحتوى المصور أكثر جاذبية من خلال تجارب الواقع المعزز.

ومع نمو الخدمات والمشتركين، قفزت هوامش الربح الإجمالية لأبل أكثر من 40%.

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

وأضاف "ثمة زيادة كبيرة في نشاط خدمات أبل التي من المحتمل أن تتجاوز جهاز آيفون الخاص بها في السنوات الخمس المقبلة".

وقطاع الخدمات والتطبيقات وهو ‬ثاني أكبر قطاع في أبل حاليا بعد آيفون. وتجاوزت قيمة أبل السوقة ثلاثة تريليونات دولار في مطلع هذا العام.

77,406 قريبا.. ويندوز 11 يحصل على تطبيقات أندرويد

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

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

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

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

وتمثل تطبيقات أندرويد الإضافة الجديدة الكبيرة. ويقول باناي إن التجربة تأتي على شكل معاينة عامة، مشيرًا إلى أن الميزة تظل في مرحلة تجريبية عندما تكون متاحة على نطاق واسع في الشهر المقبل.

وبدأت مايكروسوفت لأول مرة باختبار تطبيقات أندرويد عبر النظام التشغيلي مع المختبرين في شهر أكتوبر. وتتيح لك الميزة تثبيت عدد محدود من التطبيقات من Appstore التابع لشركة أمازون.

وهناك مجموعة متنوعة من الحلول لتشغيل متجر جوجل بلاي عبر ويندوز 11. ولكن مايكروسوفت لا تدعم ذلك رسميًا.
هناك مجموعة من الحلول لتشغيل متجر جوجل بلاي عبر ويندوز 11

شارك باناي أيضًا مجموعة متنوعة من الإحصائيات حول مدى أهمية ويندوز على مدار العامين الماضيين. ويعمل Windows 10 و Windows 11 الآن على 1.4 مليار جهاز كل شهر. وشهد سوق أجهزة الحاسب نموًا قويًا طوال الوباء.

ويقول باناي: نرى ثلاث اتجاهات دائمة وراء هذا التحول في الطلب على الحواسيب الشخصية واستخدامها:
1. الارتفاع في العمل الهجين والتعلم.
2. التحولات في عادات الترفيه ونماذج التوزيع.
3. تغيير عادات المستهلك للمهام اليومية

كما تقول مايكروسوفت إنها شهدت زيادة قدرها 6 أضعاف في عدد الأشخاص الذين يستخدمون تطبيقات مثل Cisco WebEx وسلاك ومايكروسوفت تيمز و Zoom.

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

ويوضح باناي: زاد عدد الأشخاص الذين بثوا المحتوى عبر Hulu ونتفليكس ويوتيوب عبر ويندوز منذ بداية الوباء بنسبة 70 في المئة. ونمت دقائق اللعب الشهرية بأكثر من 35 في المئة.

77,383 مشروع بناء موقع لمشاركة المهارات باستعمال Node.js

اقتباس

لا أعلم بعد النبوة أفضل من بث العلم.

ـــ عبد الله بن المبارك.

يوجد ما يسمى بفعاليات مشاركة المهارات، حيث يتحدث الناس في كلمات موجزة غير رسمية عما يفعلونه لينفعوا غيرهم به، فإذا كانت الفعالية حول مشاركة مهارات الزراعة مثلًا فربما يتحدث أحدهم عن زراعة الكرفس، أو إذا كنا في مجموعة برمجية فربما تخبر الناس عن Node.js، كما تسمى مثل تلك الاجتماعات بمجموعات المستخدِمين إذا كانت تتعلق بالحوسبة والتقنية، وهي طريقة فعالة لتوسيع الأفق ومعرفة جديد التطورات، أو التعرف على أشخاص جدد لهم الاهتمامات نفسها، وسيكون هدفنا في هذا المقال الأخير إعداد موقع لإدارة الكلمات المقدمة في اجتماع لمشاركة المهارات.

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

التصميم

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

يهيَّأ التطبيق ليعرض الكلمات المقترحة وتعليقاتها عرضًا حيًا، وكلما أرسل أحد كلمةً جديدةً في مكان ما أو أضاف تعليقًا فيجب على كل من تكون الصفحة مفتوحة عنده رؤية ذلك الحدث، وهنا محل التحدي إذ لا توجد طريقة يفتح بها الخادم اتصالًا مع عميل ولا توجد طريقة مناسبة لنعرف مَن من العملاء ينظرون الآن إلى الموقع، ويسمى حل تلك المشكلة بالاستطلاع المفتوح long polling وهو أحد بواعث تصميم بيئة Node من البداية.

الاستطلاع المفتوح

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

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

المعتاد لمثل تلك الطلبات والاتصالات أنها تنقطع بعد مهلة محددة تسمى timeout إذا لم يكن ثمة نشاط أو رد، ولكي نمنع حدوث ذلك هنا فإنّ تقنيات الاستطلاع المفتوح تعيّن وقتًا أقصى لكل طلب، حيث يستجيب الخادم بعده ولا بد حتى لو لم يكن ثمة شيء يبلِّغه، ثم ينشئ العميل بعد ذلك طلبًا جديدًا، وإعادة التشغيل الدورية تلك تجعل التقنية أكثر ثباتًا لتسمح للعملاء بالعودة للاتصال بعد فشل مؤقت في الشبكة أو مشاكل في الخادم، وإذا كان لدينا خادمًا يستخدِم الاستطلاع المفتوح فقد يكون لديه آلاف الطلبات التي تنتظره، مما يعني أنّ اتصالات TCP مفتوحة، وهنا تأتي ميزة Node، إذ تسهِّل إدارة عدة اتصالات دون إنشاء خيط تحكم منفصل لكل اتصال منها.

واجهة HTTP

ينبغي النظر أولًا قبل تصميم الخادم أو العميل إلى النقطة التي يتلاقى فيها كل منهما، وهي واجهة HTTP التي يتواصلان من خلالها، حيث سنستخدم JSON على أساس صيغة لطلبنا وعلى أساس متن للاستجابة أيضًا، كما سنستفيد من توابع HTTP وترويساته كما في خادم الملفات من المقال السابق المشار إليه سلفًا، وبما أنّ الواجهة تتمحور حول مسار ‎/talks، فستُستخدَم المسارات التي لا تبدأ بـ ‎/talks لخدمة الملفات الساكنة، وهي شيفرة HTML وجافاسكربت لنظام جانب العميل، فإذا أرسلنا طلب GET إلى /talks فسيعيد مستند JSON يشبه ما يلي:

[{"title": "Unituning", "presenter": "Jamal", "summary": "Modifying your cycle for extra style", "comments": []}]

تُنشأ الكلمة الجديدة بإنشاء طلب PUT إلى رابط مثل ‎/talks/Unituning، حيث يكون الجزء الذي بعد الشرطة الثانية هو عنوان الكلمة، ويجب احتواء متن طلب PUT على كائن JSON يستخدِم الخاصيتين presenter وsummary، وبما أنّ عناوين الكلمات تحتوي على مسافات ومحارف قد لا تظهر كما يجب لها في الرابط، فيجب ترميز سلاسل العناوين النصية بدالة encodeURIComponent عند بناء مثل تلك الروابط.

console.log("/talks/" + encodeURIComponent("How to Idle")); // → /talks/How%20to%20Idle

قد يبدو طلب إنشاء كلمة عن الوقوف بالدراجة كما يلي:

PUT /talks/How%20to%20Idle HTTP/1.1 Content-Type: application/json Content-Length: 92 {"presenter": "Hasan", "summary": "Standing still on a unicycle"}

تدعم مثل تلك الروابط طلبات GET لجلب تمثيل JSON لكلمة ما وطلبات DELETE لحذف الكلمة، كما تضاف التعليقات إلى الكلمة باستخدام طلب POST إلى رابط مثل ‎/talks/Unituning/comments مع متن JSON يحتوي على الخاصيتين author وmessage.

POST /talks/Unituning/comments HTTP/1.1 Content-Type: application/json Content-Length: 72 {"author": "Iman", "message": "Will you talk about raising a cycle?"}

قد تحتوي طلبات GET إلى ‎/talks على ترويسات إضافية تخبر الخادم بتأخير الإجابة إذا لم تتوفر معلومات جديدة، وذلك من أجل دعم الاستطلاع المفتوح، كما سنستخدِم زوجًا من الترويسات صُممتا أساسًا من أجل إدارة التخزين المؤقت وهما ETag وIf-None-Match، وقد تدرِج الخوادم ترويسة ETag -التي تشير إلى وسم الكتلة Entity Tag- في الاستجابة، بحيث تكون قيمتها سلسلةً نصيةً تعرِّف الإصدار الحالي للمورِد، وقد تنشئ العملاء طلبًا إضافيًا عندما تطلب هذا المورد مرةً ثانيةً من خلال إدراج ترويسة If-None-Match التي تحمل قيمتها السلسلة نفسها؛ أما إذا لم يتغير المورد، فسيستجيب الخادم برمز الحالة 304 والذي يعني "غير معدَّل not modified"، ليخبر العميل أنّ إصداره المخزَّن لا زال هو الإصدار الحالي؛ أما إذا لم يطابق الوسم، فسيستجيب الاستجابة العادية.

نحتاج إلى مثل ذلك النظام لأننا نريد تمكين العميل من إخبار الخادم بإصدار قائمة الكلمات التي لديه، وألا يستجيب الخادم إلا عند تغير تلك القائمة، لكن ينبغي على الخادم تأخير الإجابة وعدم الإعادة نهائيًا إلا عند توفر شيء جديد أو مرور مهلة زمنية محددة بدلًا من إعادة 304 مباشرةً، وعليه فمن أجل تمييز طلبات الاستطلاع المفتوح عن الطلبات الشرطية العادية، فإننا نعطيها ترويسةً أخرى هي Prefer: wait=90 التي تخبر الخادم باستعداد العميل لانتظار الاستجابة مدةً قدرها 90 ثانية، كما سيحتفظ الخادم برقم إصدار version number يحدِّثه في كل مرة تتغير فيها كلمة ما، وسيستخدم ذلك على أساس قيمة لوسم ETag، ويمكن للعملاء إنشاء طلبات مثل هذا ليتم إشعارها عند حدوث تغيير في الكلمة:

GET /talks HTTP/1.1 If-None-Match: "4" Prefer: wait=90 (time passes) HTTP/1.1 200 OK Content-Type: application/json ETag: "5" Content-Length: 295 [....]

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

الخادم

لنبدأ ببناء جانب الخادم من البرنامج، حيث ستعمل الشيفرة في هذا القسم على Node.js.

التوجيه Routing

سيستخدم خادمنا createServer من أجل بدء خادم HTTP، ويجب علينا التفريق في الدالة التي تعالج طلبًا جديدًا بين أنواع الطلبات المختلفة التي ندعمها وفقًا للتابع والمسار، وصحيح أنه يمكن تنفيذ ذلك بسلسلة طويلة من تعليمات if، إلا أنّ طريقة التوجيه أفضل، فالموجّه هو مكون يساعد في إرسال طلب إلى الدالة التي تستطيع معالجته، فنستطيع إخباره أنّ طلبات PUT مثلًا التي يطابق مسارها التعبير النمطي ‎/^\/talks\/([^\/]+)$/‎ -يشير إلى ‎/talks/‎ متبوعًا بعنوان الكلمة-، يمكن معالجتها بدالة ما، كما يساعد على استخراج أجزاء مفيدة من المسار -عنوان الكلمة في حالتنا- مغلَّفًا بين أقواس في التعبير النمطي ثم يمررها إلى الدالة المعالجة.

هناك عدة حزم موجهات جيدة على NPM، لكننا سنكتب واحدةً بأنفسنا لتوضيح الفكرة، وتوضِّح الشيفرة التالية router.js الذي سنطلبه من وحدة الخادم الخاص بنا عن طريق require لاحقًا:

const {parse} = require("url"); module.exports = class Router { constructor() { this.routes = []; } add(method, url, handler) { this.routes.push({method, url, handler}); } resolve(context, request) { let path = parse(request.url).pathname; for (let {method, url, handler} of this.routes) { let match = url.exec(path); if (!match || request.method != method) continue; let urlParts = match.slice(1).map(decodeURIComponent); return handler(context, ...urlParts, request); } return null; } };

تصدِّر الوحدة صنف Router، كما يسمح كائن الموجّه بتسجيل معالِجات جديدة باستخدام التابع add، ويمكن حل الطلبات باستخدام التابع resolve الخاص به، حيث سيعيد هذا التابع استجابةً عند العثور على معالج، وإذا لم يعثر فسيعيد قيمةً غير معرَّفة null، ويجرب طريقًا واحدًا في كل مرة بالترتيب الذي عرِّفَت به تلك الطرق إلى أن يعثر على تطابق، كما تُستدعَى الدوال المعالجة بقيمة context التي ستكون نسخة الخادم في حالتنا وسلاسل المطابقة لأيّ مجموعة تعرّفها في تعبيرنا النمطي وكائن الطلب، كما يجب فك تشفير روابط السلاسل النصية بما أنّ الرابط الخام قد يحتوي على رموز من تنسيق ‎%20.

تقديم الملفات

إذا لم يطابق الطلب أي نوع معرّف في موجهنا فيجب على الخادم تفسير ذلك على أنه طلب لملف في مجلد public، ومن الممكن هنا استخدام خادم الملفات المعرَّف في المقال السابق لتقديم مثل تلك الملفات، لكننا لا نحتاج ولا نريد دعم طلبات PUT أو DELETE على الملفات، ونرغب أن يكون لدينا ميزات مثل دعم التخزين، وعليه فسنستخدم خادم ملفات ساكنة مجرَّبًا من NPM وليكن ecstatic مثلًا، رغم أنه ليس الوحيد على NPM ولكنه يعمل جيدًا ومناسب لأغراضنا.

تصدِّر حزمة ecstatic دالةً يمكن استدعاؤها مع كائن تهيئة configuration object لإنتاج دالة معالجة طلبات، وسنستخدِم الخيار root لنخبر الخادم بالمكان الذي يجب أعليه البحث فيه عن الملفات، كما تقبل الدالة المعالِجة المعاملَين request وresponse ويمكن تمريرهما مباشرةً إلى createServer لإنشاء خادم لا يقدم لنا إلا الملفات فقط، كما نريد التحقق أولًا من الطلبات التي يجب معالجتها معالجةً خاصةً، لذا نغلفها في دالة أخرى.

const {createServer} = require("http"); const Router = require("./router"); const ecstatic = require("ecstatic"); const router = new Router(); const defaultHeaders = {"Content-Type": "text/plain"}; class SkillShareServer { constructor(talks) { this.talks = talks; this.version = 0; this.waiting = []; let fileServer = ecstatic({root: "./public"}); this.server = createServer((request, response) => { let resolved = router.resolve(this, request); if (resolved) { resolved.catch(error => { if (error.status != null) return error; return {body: String(error), status: 500}; }).then(({body, status = 200, headers = defaultHeaders}) => { response.writeHead(status, headers); response.end(body); }); } else { fileServer(request, response); } }); } start(port) { this.server.listen(port); } stop() { this.server.close(); } }

نستخدِم هنا طريقةً للاستجابات تشبه خادم الملفات الذي رأيناه في المقال السابق، إذ تعيد المعالِجات وعودًا تُحل إلى كائنات تصف الاستجابة، وتغلِّف الخادم في كائن يحمل حالته كذلك.

الكلمات على أساس موارد

تخزَّن الكلمات المقترحة في الخاصية talks للخادم، وهو كائن تكون أسماء خصائصه عناوين الكلمات، كما ستُكشف على أساس موارد HTTP تحت ‎/talks/[title]‎، لذا نحتاج إلى إضافة معالجات إلى الموجه الخاص بنا تستخدِم التوابع المختلفة التي تستطيع العملاء استخدامها كي تعمل معها، كما يجب على معالج طلبات GET التي تطلب كلمة بعينها البحث عن تلك الكلمة، ويستجيب ببيانات JSON لها أو باستجابة خطأ 404.

const talkPath = /^\/talks\/([^\/]+)$/; router.add("GET", talkPath, async (server, title) => { if (title in server.talks) { return {body: JSON.stringify(server.talks[title]), headers: {"Content-Type": "application/json"}}; } else { return {status: 404, body: `No talk '${title}' found`}; } });

تُحذَف الكلمة بحذفها من الكائن talks.

router.add("DELETE", talkPath, async (server, title) => { if (title in server.talks) { delete server.talks[title]; server.updated(); } return {status: 204}; });

يرسل التابع updated -الذي سنعرِّفه لاحقًا- إشعارات إلى طلبات الاستطلاع المفتوح المنتظرة بشأن التغيير، ولجلب محتوى متن الطلب فإننا نعرِّف دالةً تدعى readStream تقرأ كل المحتوى من بث قابل للقراءة وتعيد وعدًا يُحل إلى سلسلة نصية.

function readStream(stream) { return new Promise((resolve, reject) => { let data = ""; stream.on("error", reject); stream.on("data", chunk => data += chunk.toString()); stream.on("end", () => resolve(data)); }); }

أحد المعالِجات التي تحتاج إلى قراءة متون الطلبات هو PUT المستخدَم في إنشاء كلمات جديدة، ويجب عليه التحقق من إذا كانت البيانات المعطاة لها الخصائص presenter وsummary والتي تكون سلاسل نصية، فقد تكون أيّ بيانات قادمة من خارج النظام غير منطقية، ولا نريد إفساد نموذج بياناتنا الداخلية أو تعطيله إذا أتت طلبات سيئة bad requests، وإذا بدت البيانات صالحةً، فسيخزِّن المعالِج كائنًا يمثِّل الكلمة الجديدة في كائن talks، وهذا سيكتب فوق كلمة موجودة سلفًا في العنوان نفسه ويستدعي updated مرةً أخرى.

router.add("PUT", talkPath, async (server, title, request) => { let requestBody = await readStream(request); let talk; try { talk = JSON.parse(requestBody); } catch (_) { return {status: 400, body: "Invalid JSON"}; } if (!talk || typeof talk.presenter != "string" || typeof talk.summary != "string") { return {status: 400, body: "Bad talk data"}; } server.talks[title] = {title, presenter: talk.presenter, summary: talk.summary, comments: []}; server.updated(); return {status: 204}; });

تعمل إضافة تعليق إلى كلمة ما بصورة مشابهة، إذ نستخدِم readStream لنحصل على محتوى الطلب ونتحقق من البيانات الناتجة ونخزِّنها على هيئة تعليق إذا كانت صالحةً.

router.add("POST", /^\/talks\/([^\/]+)\/comments$/, async (server, title, request) => { let requestBody = await readStream(request); let comment; try { comment = JSON.parse(requestBody); } catch (_) { return {status: 400, body: "Invalid JSON"}; } if (!comment || typeof comment.author != "string" || typeof comment.message != "string") { return {status: 400, body: "Bad comment data"}; } else if (title in server.talks) { server.talks[title].comments.push(comment); server.updated(); return {status: 204}; } else { return {status: 404, body: `No talk '${title}' found`}; } });

إذا حاولنا إضافة تعليق إلى كلمة غير موجودة فسنحصل على الخطأ 404.

دعم الاستطلاع المفتوح

يُعَدّ الجزء المتعلق بمعالجة الاستطلاع المفتوح في هذا الخادم أمرًا مثيرًا، فقد يكون الطلب GET الآتي إلى ‎/talks طلبًا عاديًا أو طلب استطلاع مفتوح، وسيكون لدينا أماكن عدة يجب علينا فيها إرسال مصفوفة من الكلمات talks إلى العميل، لذا سنعرِّف تابعًا مساعدًا يبني مثل تلك المصفوفة ويدرِج ترويسة ETag في الاستجابة.

SkillShareServer.prototype.talkResponse = function() { let talks = []; for (let title of Object.keys(this.talks)) { talks.push(this.talks[title]); } return { body: JSON.stringify(talks), headers: {"Content-Type": "application/json", "ETag": `"${this.version}"`, "Cache-Control": "no-store"} }; };

يجب على المعالج النظر في ترويسات الطلب ليرى إذا كانت الترويستان If-None-Match وPrefer موجودتين أم لا، كما تخزِّن Node الترويسات التي تكون أسماؤها حساسةً لحالة الأحرف بأسماء ذات أحرف صغيرة.

router.add("GET", /^\/talks$/, async (server, request) => { let tag = /"(.*)"/.exec(request.headers["if-none-match"]); let wait = /\bwait=(\d+)/.exec(request.headers["prefer"]); if (!tag || tag[1] != server.version) { return server.talkResponse(); } else if (!wait) { return {status: 304}; } else { return server.waitForChanges(Number(wait[1])); } });

إذا لم يُعط أيّ وسم أو كان الوسم المعطى لا يطابق إصدار الخادم الحالي، فسيستجيب المعالج بقائمة من الكلمات، وإذا كان الطلب شرطيًا ولم تتغير الكلمات، فسننظر في الترويسة Prefer لنرى إذا كان يجب علينا تأخير الاستجابة أم نستجيب فورًا، كما تخزَّن دوال رد النداء للطلبات المؤجلة في مصفوفة waiting الخاصة بالخادم كي يستطيع إشعارها عند حدوث شيء ما، ويضبط التابع waitForChanges مؤقتًا على الفور للاستجابة برمز الحالة 304 إذا انتظر الطلب لفترة طويلة.

SkillShareServer.prototype.waitForChanges = function(time) { return new Promise(resolve => { this.waiting.push(resolve); setTimeout(() => { if (!this.waiting.includes(resolve)) return; this.waiting = this.waiting.filter(r => r != resolve); resolve({status: 304}); }, time * 1000); }); };

يزيد تسجيل التغيير بالتابع updated قيمة الإصدار التي هي قيمة الخاصية version ويوقظ جميع الطلبات المنتظِرة.

SkillShareServer.prototype.updated = function() { this.version++; let response = this.talkResponse(); this.waiting.forEach(resolve => resolve(response)); this.waiting = []; };

هكذا تكون شيفرة الخادم قد تمت، فإذا أنشأنا نسخةً من SkillShareServer وبدأناها عند المنفَذ 8000، فسيخدم خادم HTTP الناتج الملفات من المجلد الفرعي public مع واجهة لإدارة الكلمات تحت رابط ‎/talks.

new SkillShareServer(Object.create(null)).start(8000); العميل

يتكون جانب العميل من موقع لمشاركة المهارات من ثلاثة ملفات هي صفحة HTML صغيرة وورقة تنسيقات style sheet وملف جافاسكربت.

HTML

يُعَدّ تقديم ملف اسمه index.html إحدى الطرق المستخدَمة بكثرة في خوادم الويب عند إنشاء طلب مباشرة إلى مسار موافق لمجلد ما، وتدعم وحدة خادم الملفات التي نستخدمها exstatic تلك الطريقة، فإذا أنشئ طلب إلى المسار / فسيبحث الخادم عن الملف ‎./public/index.html، حيث يكون ‎./public الجذر الذي أعطيناه إليه، ثم يعيد ذلك الملف إذا وجده، وعلى ذلك فإذا أردنا لصفحة أن تظهر عندما يوجَّه متصفح ما إلى خادمنا، فيجب علينا وضعها في public/index.html، حيث يكون ملف index الخاص بنا كما يلي:

<!doctype html> <meta charset="utf-8"> <title>Skill Sharing</title> <link rel="stylesheet" href="skillsharing.css"> <h1>Skill Sharing</h1> <script src="skillsharing_client.js"></script>

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

الإجراءات

تتكون حالة التطبيق من قائمة من الكلمات واسم المستخدم، كما سنخزِّن ذلك في الكائن {talks,user}، ولا نريد السماح لواجهة المستخدِم بتعديل الحالة أو إرسال طلبات HTTP، بل قد تطلق إجراءات تصف ما الذي يحاول المستخدِم فعله، في حين تأخذ دالة handleAction مثل هذا الإجراء وتجعله يحدُث، كما تعالَج تغيرات الحالة في الدالة نفسها بما أنّ تحديثات حالتنا بسيطة جدًا.

function handleAction(state, action) { if (action.type == "setUser") { localStorage.setItem("userName", action.user); return Object.assign({}, state, {user: action.user}); } else if (action.type == "setTalks") { return Object.assign({}, state, {talks: action.talks}); } else if (action.type == "newTalk") { fetchOK(talkURL(action.title), { method: "PUT", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ presenter: state.user, summary: action.summary }) }).catch(reportError); } else if (action.type == "deleteTalk") { fetchOK(talkURL(action.talk), {method: "DELETE"}) .catch(reportError); } else if (action.type == "newComment") { fetchOK(talkURL(action.talk) + "/comments", { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({ author: state.user, message: action.message }) }).catch(reportError); } return state; }

سنخزن اسم المستخدِم في localStorage كي يمكن استعادتها عند تحميل الصفحة؛ أما الإجراءات التي تحتاج إلى إنشاء الخادم طلبات شبكية باستخدام fetch إلى واجهة HTTP التي وصفناها من قبل فسنستخدِم دالةً مغلِّفةً هي fetchOk تتأكد من أنّ الوعد المعاد مرفوض إذا أعاد الخادم رمز خطأ.

function fetchOK(url, options) { return fetch(url, options).then(response => { if (response.status < 400) return response; else throw new Error(response.statusText); }); }

تُستخدَم الدالة المساعدة التالية لبناء رابط لكلمة لها عنوان محدَّد.

function talkURL(title) { return "talks/" + encodeURIComponent(title); }

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

function reportError(error) { alert(String(error)); } إخراج المكونات Rendering Components

سنستخدِم منظورًا يشبه الذي رأيناه في مقال إنجاز مشروع محرر رسوم نقطية باستخدام جافاسكربت والذي يقسِّم التطبيق إلى مكونات، لكن بما أن بعض تلك المكونات قد لا تحتاج إلى تحديث أبدًا أو تُرسم من جديد في كل مرة تُحدَّث فيها، فسنعرِّف أولئك على أساس دوال تعيد عقدة DOM مباشرةً وليس على أساس أصناف، ويوضِّح المثال التالي مكونًا يعرض حقلًا يمكن للمستخدِم إدخال اسمه فيه.

function renderUserField(name, dispatch) { return elt("label", {}, "Your name: ", elt("input", { type: "text", value: name, onchange(event) { dispatch({type: "setUser", user: event.target.value}); } })); }

الدالة elt المستخدَمة لبناء عناصر DOM هي نفسها التي استخدمناها في مقال إنجاز مشروع محرر رسوم نقطية باستخدام جافاسكربت المشار إليه أعلاه، وتُستخدَم دالة شبيهة بها لإخراج الكلمات، حيث تتضمن قائمةً من التعليقات واستمارةً من أجل إضافة تعليق جديد.

function renderTalk(talk, dispatch) { return elt( "section", {className: "talk"}, elt("h2", null, talk.title, " ", elt("button", { type: "button", onclick() { dispatch({type: "deleteTalk", talk: talk.title}); } }, "Delete")), elt("div", null, "by ", elt("strong", null, talk.presenter)), elt("p", null, talk.summary), ...talk.comments.map(renderComment), elt("form", { onsubmit(event) { event.preventDefault(); let form = event.target; dispatch({type: "newComment", talk: talk.title, message: form.elements.comment.value}); form.reset(); } }, elt("input", {type: "text", name: "comment"}), " ", elt("button", {type: "submit"}, "Add comment"))); }

معالِج الحدث "submit" يستدعي form.reset لمسح محتوى الاستمارة بعد إنشاء الإجراء "newcomment"، وعند إنشاء أجزاء متوسطة التعقيد من DOM، فسيبدو هذا التنسيق من البرمجة فوضويًا، وهناك امتداد جافاسكربت واسع الاستخدام رغم أنه ليس قياسيًا ويسمى JSX، حيث يسمح لنا بكتابة HTML في السكربتات الخاصة بك مباشرةً مما يحسِّن من مظهر الشيفرة، لكن يجب علينا تشغيل برنامج ما قبل تشغيل الشيفرة نفسها ليحوّل شيفرة HTML الوهمية تلك إلى استدعاءات لدوال جافاسكربت مثل تلك التي نستخدمها ها هنا؛ أما التعليقات فستكون أبسط في الإخراج.

function renderComment(comment) { return elt("p", {className: "comment"}, elt("strong", null, comment.author), ": ", comment.message); }

أخيرًا، تُخرَج الاستمارة التي يستطيع المستخدِم استخدامها في إنشاء الكلمة كما يلي:

function renderTalkForm(dispatch) { let title = elt("input", {type: "text"}); let summary = elt("input", {type: "text"}); return elt("form", { onsubmit(event) { event.preventDefault(); dispatch({type: "newTalk", title: title.value, summary: summary.value}); event.target.reset(); } }, elt("h3", null, "Submit a Talk"), elt("label", null, "Title: ", title), elt("label", null, "Summary: ", summary), elt("button", {type: "submit"}, "Submit")); } الاستطلاع

نحتاج إلى قائمة الكلمات الحالية إذا أردنا بدء التطبيق، وبما أن التحميل الابتدائي متعلق للغاية بعملية الاستطلاع المفتوح إذ يجب استخدام ETag من الحمل عند الاستطلاع، فسنكتب دالةً تظل تستطلع الخادم لـ ‎/talks وتستدعي دالة رد نداء عند توفر مجموعة كلمات جديدة.

async function pollTalks(update) { let tag = undefined; for (;;) { let response; try { response = await fetchOK("/talks", { headers: tag && {"If-None-Match": tag, "Prefer": "wait=90"} }); } catch (e) { console.log("Request failed: " + e); await new Promise(resolve => setTimeout(resolve, 500)); continue; } if (response.status == 304) continue; tag = response.headers.get("ETag"); update(await response.json()); } }

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

إذا أعاد الخادم استجابة 304 فهذا يعني انتهاء المهلة الزمنية المحددة لطلب استطلاع مفتوح، لذا يجب أن تبدأ الدالة الطلب التالي، فإذا كانت الاستجابة هي 200 العادية، فسيُقرأ متنها على أنه JSON ويمرَّر إلى رد النداء، كما تخزَّن قيمة الترويسة ETag من أجل التكرار التالي.

التطبيق

يربط المكون التالي واجهة المستخدِم كلها بعضها ببعض:

class SkillShareApp { constructor(state, dispatch) { this.dispatch = dispatch; this.talkDOM = elt("div", {className: "talks"}); this.dom = elt("div", null, renderUserField(state.user, dispatch), this.talkDOM, renderTalkForm(dispatch)); this.syncState(state); } syncState(state) { if (state.talks != this.talks) { this.talkDOM.textContent = ""; for (let talk of state.talks) { this.talkDOM.appendChild( renderTalk(talk, this.dispatch)); } this.talks = state.talks; } } }

إذا تغيرت الكلمات فسيُعيد هذا المكون رسمها جميعًا، وهذا أمر بسيط حقًا لكنه مضيعة للوقت وسنعود إليه في التدريبات، إذ نستطيع بدء التطبيق كما يلي:

function runApp() { let user = localStorage.getItem("userName") || "Anon"; let state, app; function dispatch(action) { state = handleAction(state, action); app.syncState(state); } pollTalks(talks => { if (!app) { state = {user, talks}; app = new SkillShareApp(state, dispatch); document.body.appendChild(app.dom); } else { dispatch({type: "setTalks", talks}); } }).catch(reportError); } runApp();

إذا شغلنا الخادم وفتحنا نافذتَي متصفح لـ http://localhost:8000 جنبًا إلى جنب، فسيمكنك رؤية كيف أنّ الإجراءات الذي تحدِثه في إحدى النافذتين تظهر مباشرةً في الأخرى.

تدريبات

ستتضمن التدريبات التالية تعديل النظام المعرّف في هذا المقال، ولكي تعمل عليها تأكد من تحميل الشيفرة أولًا من هذا الرابط وتكون قد ثبّتَّ Node لديك من موقعها الرسمي، وكذلك اعتماديات المشروع باستخدام الأمر npm install.

ثبات القرص

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

إرشادات الحل

أبسط حل لهذا هو ترميز كائن talks كله على أنه JSON وإلقائه في ملف بواسطة writeFile، وهناك تابع update بالفعل يُستدعى في كل مرة تتغير فيها بيانات الخادم، حيث يمكن توسيعه لكتابة البيانات الجديدة على القرص.

اختر اسم ملف وليكن ‎./talks,json، ويمكن للخادم أن يحاول في قراءة هذا الملف باستخدام readFile عند بدء عمله، وإذا نجح فيمكن للخادم أن يستخدِم محتويات الملف على أساس تاريخ بدء له؛ لكن احذر، فكائن talks بدأ على أساس كائن ليس له نموذج أولي كي يمكن استخدام العامل in بصورة موثوقة.

ستعيد JSON.parse كائنات عادية يكون نموذجها الأولي هو Object.prototype، فإذا استخدمت JSON على أساس صيغة ملفات لك، فيجب عليك نسخ خصائص الكائن المعاد بواسطة JSON.parse في كائن جديد ليس له نموذج أولي.

إعادة ضبط حقول التعليقات

تعمل إعادة رسم الكلمات كلها لأنك لا تستطيع عادةً معرفة الفرق بين عقدة DOM وبديلها التوأم، لكن هناك استثناءات لهذا، فإذا بدأت كتابة شيء ما في حقل التعليق لكلمة ما في نافذة متصفح ثم أضفت تعليقًا إلى الكلمة نفسها من متصفح آخر، فسيعاد رسم الحقل في النافذة الأولى ليحذف محتواه وتركيزه focus معًا، وسيكون هذا مزعجًا للغاية إذا كان لدينا نقاشًا بين عدة مستخدِمِين من حواسيب مختلفة ومتصفحات عدة يضيفون تعليقات في الوقت نفسه، فهل تستطيع إيجاد طريقة لحل هذه المشكلة؟

إرشادات الحل

إنّ أفضل حل لهذا هو جعل مكونات الكلمات كائنات لها التابع syncState كي يمكن تحديثها لتعرض نسخةً معدلةً من الكلمة، وتكون الطريقة الوحيدة التي يمكن بها تغير كلمة ما هي بإضافة تعليقات أكثر، وعليه يكون التابع syncState بسيطًا نسبيًا هنا؛ أما الجزء الصعب فهو عند تغير قائمة الكلمات، إذ يجب إصلاح قائمة مكونات DOM الموجودة بكلمات من القائمة الجديدة، مما يعني حذف المكونات التي حُذفت كلماتها وتحديث المكونات التي تغيرت كلماتها.

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

ترجمة -بتصرف- للفصل الحادي والعشرين من كتاب Elequent Javascript لصاحبه Marijn Haverbeke.

اقرأ أيضًا
77,325 اكتشاف أكثر من مئتي نوع حيواني ونباتي في جنوب شرق آسيا اعتدنا على أخبار تعرض حيوانات ونباتات للانقراض. الجديد اليوم هو اكتشاف 224 نوعا نباتيا وحيوانيا جديدة يطالب خبراء بحمايتها والمحافظة على التنوع الحيوي على الكوكب.
77,324 طائرات "الحوثيين" المسيرة تهديد منخفض التكلفة للإمارات تشكّل الطائرات المسيرة الصغيرة الحجم والقادرة على الطيران لمئات الكيلومترات تهديدا لدولة الإمارات التي استهدفها المتمردون الحوثيون خلال الأسبوعين الماضيين مرتين، وأودى الهجوم الأول بحياة ثلاثة أشخاص.
77,323 نظرة على نافذة صحة موقع ووردبريس

يوفر ووردبريس فحصًا لصحة موقعك حيث يوجد قسمين في صفحة صحة الموقع Site Health هما:

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

سوف تجد المزيد من المعلومات في الأقسام التالية من المقال.

الحالة

يعرض فحص صحة الموقع المشاكل الحرجة عن إعدادات ووردبريس والأجزاء التي تتطلب انتباهك. تُجمَع هذه المعلومات حسب أهميتها إلى مشاكل حرجة وتحسينات موصى بها والاختبارات المجتازة.

المشاكل الحرجة

تشير المشاكل الحرجة لأجزاء الموقع التي يمكن أن تشكل خطرًا أمنيًا عليه أو لمشاكل الأداء الخطيرة وتقترح كيفية إصلاحها. وفيما يلي بعض المشاكل الحرجة المحتملة:

  • التحديثات التلقائية للإضافات والقوالب معطلة ولكن لا تزال الإعدادات مضبوطة للعرض: لن تعمل التحديثات التلقائية كما هو متوقع، ونوع هذه المشكلة أمني.
  • التحديثات في الخلفية لا تعمل كما هو متوقع: تضمن تحديثات الخلفية أن ووردبريس يستطيع تفعيل التحديث التلقائي عند إصدار تحديث أمني للنسخة الحالية التي تعمل عليها. انظر مقال ضبط التحديثات التلقائية.
  • لا يمكن لموقعك تبادل الاتصال مع WordPress.org: إن الاتصال مع خوادم ووردبريس مطلوب لأجل التحقق من وجود إصدارات أحدث وتثبيت وتحديث نواة ووردبريس والقوالب والإضافات. تعني هذه الرسالة أن موقعك غير قادر على الوصول لموقع wordpress.org باستخدام الرابط api.wordpress.org.
  • رفع ملف: هذا يعني أن file_upload مضبوط على القيمة 0 ولن تتمكن من رفع ملفات لموقعك. عُطلت الدالة ini_get()‎ وبسبب ذلك بعض إعدادات الوسائط لن تكون متوفرةً.
  • طلبات HTTP محظورة: حجبت طلبات HTTP من قبل الثابت WP_HTTP_BLOCK_EXTERNAL دون السماح لأي مضيف بالاتصال.

يمكن لمن ينفذ صيانةً للموقع حجب جميع أو بعض الاتصالات مع المواقع الأخرى والخدمات وإذا ضُبط الأمر بطريقة خاطئة يمكن أن يمنع هذا الإضافات والقوالب من العمل كما هو متوقع.

  • عد موقعك ليعرض الأخطاء لزوار الموقع: يفعل نمط إصلاح المشاكل لجمع المزيد عن المعلومات عن خطأ ما أو مشكلة في الموقع لكن بعض هذه المعلومات يمكن أن تكون حساسةً لذا يجب ألا يسمح الوصول لها من قبل الزوار. ويمكن أن تكون القيمة WP_DEBUG_DISPLAY مفعّلة من قبل WP_DEBUG أو أضيفت لملف wp-config.php حيث تسمح هذه القيمة بعرض الأخطاء على الواجهة الأمامية للموقع.

تعرف أكثر على إصلاح أخطاء ووردبريس.

  • موقعك لم يتمكن من إكمال طلب loopback: تستخدم طلبات Loopback لتشغيل الأحداث المجدولة كما تستخدم من قبل المحرر المبني ضمن ووردبريس للقوالب والإضافات للتأكد من استقرار الشيفرة.

  • التوقيت الزمني الافتراضي غير صالح: غُيرت القيمة الافتراضية للمنطقة الزمنية بعد تحميل ووردبريس، وهذا يتداخل مع حسابات ووردبريس للوقت والتاريخ.
  • واحد أو أكثر من الوحدات الموصى بها مفقودة: إن الوحدة المطلوبة json_last_error غير مثبتة أو معطلة.
  • كشف عن جلسة PHP نشطة: أنشئت جلسة PHP باستخدام الدالة session_start()‎، وهذا يتداخل مع طلبات REST API و Loopback. يجب أن تُغلق الجلسة باستخدام session_write_close()‎ قبل إنشاء أي طلبات HTTP.

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

  • لديك إضافات بانتظار التحديث: تساعد الإضافات على إضافة المزيد من الوظائف لموقعك مثل إضافة Contact Forms و eCommerce وغيرها، ويعني هذا أن عليهم الوصول عميقًا ضمن موقعك وهنا تأتي أهمية تحديث هذه الإضافات واستخدامها بأحدث إصدار.

التحسينات الموصى بها

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

الاختبارات المجتازة

يتضمن هذا القسم كل العناصر التي اختبرت من قبل أداة صحة الموقع ولم تظهر أي مشاكل.

معلومات عن صحة الموقع

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

اضغط على السهم المشير للأسفل على يسار القسم لتوسيعه. سوف تجد المعلومات التالية:

ووردبريس

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

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

القالب النشط

يعرض هذا القسم معلومات عن قالب ووردبريس النشط حاليًا وتستطيع الاطلاع على المعلومات التالية منه:

  • الاسم: اسم القالب النشط حاليًا.
  • النسخة: الإصدار الحالي من القالب.
  • الكاتب: مؤلف هذا القالب.
  • موقع المؤلف: الموقع الخاص بمؤلف القالب.
  • القالب الأب (الأساسي): يعرض لك القالب الأب إن كنت تستخدم قالب ابن وسوف يعرض لك "بدون" إن كنت لا تستخدم قالب ابن.
  • مميزات القالب: قائمة بجميع مميزات القالب النشط مثل قوالب الكتل وتصاميم المحرر وغيرها العديد من الميزات المتقدمة.
  • موقع دليل القالب Theme: المسار الذي ثبت فيه القالب النشط الحالي.
  • التحديثات التلقائية: يخبرك إن كنت فعلت التحديثات التلقائية أم لا. تستطيع الوصول لهذه الإعدادات من تبويب "قوالب" ضمن ابحث عن القالب النشط واضغط على "تفاصيل القالب" عند تحريك مؤشر الفأرة فوق عنوان القالب. سوف يعرض لك الآن خيار "تفعيل التحديثات التلقائية" أسفل عنوان القالب.

القوالب غير النشطة

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

إن كنت لا تخطط لاستخدام هذه القوالب فمن الأفضل إزالة القوالب غير النشطة.

الإضافات النشطة

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

الإضافات غير النشطة

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

معالجة الوسائط

يعرض هذا القسم كيف تعالج الوسائط ضمن موقعك ويمكنك إيجاد المعلومات التالية ضمنه:

  • المحرر النشط: يظهر لك اسم المحرر النشط لتعديل وتحسين ملفات الصور. حيث يكون المحرر الافتراضي هو WP Image Editor GD حيث يستعمل مكتبة معالجة الصور GD PHP. سوف يعرض WP Image Editor Imagick إذا كان الموقع يستخدم مكتبة Imagick، تتوفر مكتبة GD PHP افتراضيًا ضمن PHP 4.3 لذلك فهي متوفرة في معظم الاستضافات بينما توافر مكتبة Imagick يعتمد على شركة الاستضافة.
  • رقم إصدار ImageMagick: يعرض رقم إصدار ImageMagick بهيئة رقم صحيح حيث أن ImageMagick هو برمجية تستخدم لإنشاء وتعديل صور الموقع.
  • سلسلة إصدار ImageMagick: يعرض سلسلة إصدار ImageMagick بهيئة محرف في صيغة x.y.z مع تاريخ الإصدار ورابط موقع ImageMagick.
  • إصدار Imagick: يعرض إصدار Imagick المثبت على الموقع حيث يُساعد هذا في عملية إصلاح المشاكل الناتجة عن إنشاء الصور من جهة PHP.
  • رفع الملفات: يظهر لك ما إذا كنت قادرًا على رفع ملفات لموقعك. عليك الاتصال بالاستضافة إذا كان معطلًا.
  • الحجم الأقصى لبيانات الـ post: يعرض لك الحجم الأعظمي لطلب http POST الذي يرسل البيانات من المتصفح إلى الخادم.
  • الحجم الأقصى للملف المرفوع: يعرض الحجم الأعظمي للملف الذي تستطيع رفعه لمكتبة الوسائط.
  • الحدّ الأقصى لحجم الملف الفعلي: يعرض القيمة الأصغر بين الحجم الأعظمي لبيانات post والحجم الأعظمي للملف المرفوع.
  • أقصى عدد مسموح به من الملفات: العدد الأعظمي لعدد الملفات المسموح بها في طلب http POST واحد.
  • إصدار GD: يعرض إصدار GD المثبت لديك وGD هي مكتبة تحسين صور في PHP.
  • تنسيقات ملفات GD المدعومة: يعرض صيغة الملفات التي تدعمها مكتبة GD. الصيغ المدعومة حاليًا هي GIF وJPEG وPNG وWebP وBMP وXPM.
  • إصدار Ghostscript: يعرض إصدار Ghostscript المثبت ضمن الموقع إن كان متوفرًا وGhostscript عبارة عن محرك لمعالجة ملفات PDF.

الخادم

يعرض هذا القسم معلومات مرتبطة بالخادم مثل معمارية الخادم وخادم الويب ومتغيرات PHP المتنوعة وزمن الإدخال الأقصى وحجم الملف الأقصى للرفع وإصدار cURL وSUHOSIN ومكتبة Imagick والروابط الدائمة الجميلة وقواعد htaccess. وإن كنت تريد تغيير أي من إعدادات العناصر السابقة عليك التواصل مع الاستضافة.

ثوابت ووردبريس

تستطيع هنا معرفة أي الأجزاء من ووردبريس تحمّل.

أذونات ملفات النظام

يعرض هذا القسم ما إذا كان ووردبريس قادرًا على الكتابة على المسارات التي يحتاج الكتابة فيها. يجب أن يكون قادرًا على الكتابة على المسار الرئيسي لووردبريس ومسار wp-content ومسار رفع الملفات ومسار الإضافات ومسار القوالب لكن إن كان أي من هذه المسارات غير قابل للكتابة من قبل ووردبريس يجب عليك التواصل مع الاستضافة.

ترجمة -وبتصرف- للمقال Site Health Screen من موقع ووردبريس.

اقرأ أيضًا
77,302 قنوات الدخل والخرج وعمليتي القراءة والكتابة في جافا

تُصبِح البرامج عديمة الفائدة إذا لم تكن قادرةً على التعامل مع العالم الخارجي بشكلٍ أو بآخر، حيث يُشار إلى تعامل البرامج مع العالم الخارجي باسم "الدْخَل والخرج أو I/O". يُعدّ توفير إمكانياتٍ جيدةٍ لعمليات الدْخَل والخرج واحدًا من أصعب التحديات التي تواجه مُصمِّمي اللغات البرمجية، حيث يَستطيِع الحاسوب الاتصال مع أنواعٍ مختلفةٍ كثيرة من أجهزة الدخل والخرج.

إذا اضطّرت لغة البرمجة للتعامل مع كل نوعٍ منها على حدة، لكان الأمر غايةً في التعقيد، ولهذا يُعدّ التمثيل المُجرّد لأجهزة الدخل والخرج واحدًا من أعظم الإنجازات بتاريخ البرمجة، ويُطلَق على ذلك التمثيل المُجرّد بلغة جافا اسم مجاري تدفق الدْخَل والخرج I/O streams. تتوفَّر تجريداتٌ أخرى، مثل الملفات والقنوات، ولكننا سنناقش مجاري التدفق فقط، حيث يُمثِّل كل مجرًى مصدرًا يُقرَأ منه الدْخَل أو مقصدًا يُرسَل إليه الخرج.

مجاري تدفق البايتات Byte Streams ومجاري تدفق المحارف Character Streams

عندما تتعامل مع المُدْخَلات والمخرجات، تذكَّر أن هناك نوعان من البيانات في العموم؛ بياناتٌ مُهيأةٌ للآلة؛ وبياناتٌ مهيأةٌ لنا بمعنى أنها قابلةٌ للقراءة. تُكتَب الأولى بالصيغة الثنائية binary بنفس الطريقة التي تُخزَّن بها البيانات داخل الحاسوب، أي بهيئة سلاسلٍ نصيةٍ مُكوَّنةٍ من "0" و "1"؛ بينما تُكتَب الثانية بهيئة محارف. فعندما تقرأ عددًا، مثل "3.141592654"، فأنت في الواقع تقرأ متتاليةً من المحارف، ولكنك تُفسِّرها عددًا؛ بينما يُمثِّل الحاسوب نفس ذلك العدد بهيئة سلسلةٍ نصيةٍ من البتات أي أنك لن تتمكَّن من تمييزها.

تُوفِّر جافا نوعين من مجاري التدفق streams للتعامل مع البيانات المُمثَلة بالصيغتين السابقتين: مجرى بايتات byte streams للبيانات المُهيأة للآلة، ومجرى محارف character streams للبيانات القابلة للقراءة. ستَجِد أصنافًا مُعرَّفةً مُسبقًا تُمثِّل المجاري من كلا النوعين.

تنتمي الكائنات المُرسِلة للبيانات إلى مجرى بايت إلى أحد الأصناف الفرعية subclasses المُشتقَّة من الصنف المُجرَّد OutputStream؛ بينما تنتمي الكائنات القارئة للبيانات من هذا النوع من المجاري إلى أحد الأصناف الفرعية المُشتقَّة من الصنف المُجرَّد InputStream. إذا أرسلت أعدادًا إلى كائنٍ من الصنف OutputStream، لن تتمكَّن من قراءة البيانات الناتجة بنفسك. في المقابل، ما يزال بإمكان الحاسوب قرائتها مُجدَّدًا عبر كائنٍ من الصنف InputStream. تعمَل عمليتي قراءة البيانات وكتابتها في تلك الحالة بكفاءة لعدم استخدامهما أي ترجمة؛ حيث تُنسَخ فقط البتات المُمثِلة للبيانات بالحاسوب من مجاري التدفق وإليها.

في المقابل، يتولَّى الصنفان المجرَّدان Reader وWriter قراءة البيانات القابلة للقراءة وكتابتها على الترتيب، فجميع أصناف مجارى المحرف هي مجرد أصنافٍ فرعيةٍ مُشتقَّةٍ من هذين الصنفين. إذا أرسلت عددًا إلى مجرًى من النوع Writer، فيجب أن يُترجمها الحاسوب إلى متتاليةٍ من المحارف المُمثِلة لذلك العدد والقابلة للقراءة؛ بينما تنطوي عملية قراءة عددٍ من مجرًى من النوع Reader، وتخزينها بمُتغيِّرٍ عددي على عملية ترجمةٍ من متتالية محارف إلى سلسلة بتاتٍ مناسبة. حتى لو كانت البيانات التي تتعامل معها مُكوَّنةً من محارفٍ بالأساس، مثل بعض الكلمات من برنامج معدِّل نصوص، من الممكن أن يتضمَّن الأمر بعضًا من الترجمة أيضًا.

يُخزِّن الحاسوب المحارف على انها قيم يونيكود Unicode من 16 بت، وتُخزّن حروف الأبجدية الإنجليزية عمومًا بملفات بشيفرة ASCII، التي تَستخدِم 8 بتات للمحرف الواحد. يتولى الصنفان Reader وWriter أمر تلك الترجمة، كما يمكنهما معالجة الحروف الأبجدية الأخرى، وكذلك المحارف من غير الحروف الأبجدية المكتوبة بلغاتٍ، مثل الصينية.

تُستَخدَم مجاري تدفق البايتات للاتصال المباشر بين الحواسيب، كما أنها تكون مفيدةً أحيانًا لتخزين البيانات ضمن ملفات، بالأخص عندما نحتاج إلى تخزين أحجامٍ هائلةٍ من البيانات بطريقةٍ فعالة؛ كما هو الحال مع قواعد البيانات الضخمة. ومع ذلك، تُعدّ البيانات الثنائية هشة نوعًا ما، فهي لا تعبُر بذاتها عن معناها.

عندما تتعامل مع سلسلةٍ طويلةٍ من العددين صفر وواحد، ينبغي أن تُعرِّف أولًا نوعية المعلومات المُفترَض لتلك السلسلة أن تُمثِّلها، وكذلك أن تَعرِّف الكيفية التي رُمزَّت بها المعلومات قبل أن تتمكَّن من تفسيرها. ينطبق الأمر نفسه بالطبع على البيانات المحرفية نوعًا ما؛ فالمحارف بالنهاية مثلها مثل أي نوعٍ من البيانات، وينبغي أن تُرمَّز مثل أعدادٍ ثنائية حتى يتمكَّن الحاسوب من تخزينها ومعالجتها، ولكن الترميز الثنائي للبيانات المحرفية على الأقل مُوحدٌ ومفهوم، بل حتى يُمكِننا أن نجعل البيانات بصيغتها المحرفية ذات معنًى للقارئ. يتجه التيار العام إلى استخدام البيانات المحرفية، وتمثيلها بطريقةٍ تجعلها مُفسَّرةً ذاتيًا قدر الإمكان، وسنناقش إحدى تلك الطرائق في مقال مقدمة مختصرة للغة XML.

لا يدعم الإصدار الأصلي من جافا مجاري المحارف، حيث يُمكِن لمجاري البايتات أن تحلّ محل مجاري المحارف عند التعامل مع البيانات المُرمزَّة بشيفرة ASCII. يُعدُّ مجريا الدخل القياسي System.in والخرج القياسي System.out مجاري بايتات، وليس مجاري محارف؛ ومع ذلك يُحبَّذ استخدام الصنفين Reader وWriter على الصنفين InputStream وOutputStream عند التعامل مع البيانات المحرفية، وحتى عند التعامل مع مجموعة محارف ASCII القياسية.

تقع أصناف مجاري الدخل والخرج القياسية -والتي سنناقشها ضمن هذا المقال - بحزمة java.io بالإضافة إلى عددٍ من الأصناف الأخرى. يجب أن تستورد import أصناف تلك الحزمة إذا أردت اِستخدَامها ضمن البرنامج؛ أي إما أن تستورد الأصناف المطلوبة بصورةٍ فردية؛ أو أن تْكْتُب المُوجِّه import java.io.*;‎ في بداية الملف المصدري.

تُستخدَم مجاري الدخل والخرج عند التعامل مع الملفات، وعند الاتصال الشبكي، وكذلك للاتصال بين الخيوط المُتزامنة concurrent threads. تتوفَّر أيضًا أصناف مجاري لقراءة البيانات وكتابتها من وإلى ذاكرة الحاسوب.

اقتباس

ملاحظة: تُوفِّر واجهة برمجة تطبيقات جافا دعمًا إضافيًا لعمليات الدخل والخرج عبر حزمة java.nio وحزمها الفرعية subpackages، ولكننا لن نناقشها هنا. تُوفِّر تلك الحزمة بعضًا من التقنيات المُتقدِّمة لعمليات الدخل والخرج في العموم.

تَكْمُن فعالية المجاري وأناقتها بكونها تُجرِّد عملية كتابة البيانات؛ حيث تُصبِح عملياتٍ مثل كتابة بياناتٍ إلى ملف أو إرسالها عبر شبكةٍ بنفس سهولة طباعة تلك البيانات على الشاشة.

تُوفِّر أصناف الدخل والخرج Reader وWriter وInputStream وOutputStream العمليات الأساسية فقط، حيث يُصرِّح الصنف InputStream مثلًا عن تابع النسخة instance method المُجرَّد التالي:

public int read() throws IOException

يقرأ هذا التابع بايتًا واحدًا من مجرى دْخَلٍ بهيئة عددٍ يقع بنطاقٍ يتراوح بين "0" و "255"، ويُعيد القيمة "-1" عند وصوله إلى نهاية المجرى. إذا حدث خطأٌ أثناء عملية الدخل، يقع استثناء exception من النوع IOException، ونظرًا لكونه من الاستثناءات المُتحقَّق منها checked exceptions، لا بُدّ من استخدام التابع read()‎ ضمن تعليمة try أو ببرنامجٍ فرعي subroutine يتضمَّن تصريحه عبارة throws IOException. انظر مقال الاستثناءات exceptions وتعليمة try..catch في جافا للمزيد من المعلومات عن الاستثناءات المُتحقَّق منها والمعالجة الاجبارية للاستثناءات.

يُعرِّف الصنف InputStream أيضًا توابعًا لقراءة عدة بايتات من البيانات ضمن خطوةٍ واحدة، وتخزينها بمصفوفة بايتات، وهو ما يُعدّ أكثر كفاءة بكثير من قرائتها بصورةٍ إفرادية؛ ولكنه -أي الصنف InputStream- مع ذلك لا يُوفِّر أي توابعٍ لقراءة أنواعٍ أخرى من البيانات، مثل int وdouble من مجرى. لا يُمثِل ذلك مشكلة؛ حيث من النادر أن تستخدِم كائناتٍ من النوع InputStream، وإنما ستعتمد على أصنافٍ فرعية منه. تُعرِّف تلك الأصناف توابع دْخَلٍ إضافية إلى جانب الإمكانيات الأساسية للصنف InputStream، كما يُعرِّف بالمثل الصنف OutputStream تابع الخرج التالي لكتابة بايت واحد إلى مجرى خرج:

public void write(int b) throws IOException

لاحِظ أن المعامل parameter من النوع int، وليس من النوع byte، ولكنه يُحوَّل type-cast إلى النوع byte قبل كتابته، وهو ما يؤدي إلى إهمال جميع بتات المعامل b باستثناء البتات الثمانية الأقل رتبة. عمليًا، ستَستخدِم دائمًا أصنافًا فرعية مُشتقَّة من الصنف OutputStream، والتي تُعرِّف عمليات خرجٍ إضافية عالية المستوى.

يُوفِّر الصنفان Reader وWriter توابعًا منخفضة المستوى مشابهة لعمليتي read وwrite. وكما هو الحال مع أصناف مجاري البايتات، ينتمي كلٌ من معامل التابع write(c)‎ المُعرَّف بالصنف Writer، والقيمة المعادة من التابع read()‎ المُعرَّف بالصنف Reader إلى النوع int، ولكن ما يزال هناك اختلاف؛ حيث تُجرَى بتلك الأصناف المُخصَّصة بالأساس للمحارف عمليتي الدخل والخرج على المحارف، وليس على البايتات. يعيد التابع read()‎ القيمة "-1" عند وصوله إلى نهاية المجرى، أما قبل ذلك، فيجب أن نُحوَّل القيمة المعادة منه إلى النوع char لنَحصُل على المحرف المقروء. عمليًا، ستَستخدِم عادةً أصنافًا فرعيةً مُشتقَّةً من الصنفين Reader وWriter، والتي تُعرِّف عمليات دْخَل وخَرْج إضافية عالية المستوى، كما سنناقش فيما يلي.

الصنف PrintWriter

تُمكِّنك حزمة جافا للدخل والخرج من إضافة إمكانياتٍ جديدة إلى مجاري التدفق من خلال تغليفها wrapping ضمن كائنات مجاري تدفقٍ أخرى تُوفِّر تلك الإمكانيات. يكون الكائن المُغلِّف مجرًى أيضًا؛ أي يُمكِنك أن تقرأ منه أو تكتب به، ولكن عبر عملياتٍ أكثر فعالية من تلك المتاحة بمجاري التدفق الأصلية.

يُعدّ الصنف PrintWriter على سبيل المثال صنفًا فرعيًا من الصنف Writer، ويُوفِّر توابعًا لإخراج جميع أنواع البيانات الأساسية بلغة جافا بصيغة محارف مقروءة. إذا كان لديك كائنٌ منتميٌ إلى الصنف Writer أو أيٍّ من أصنافه الفرعية، وأردت استخدام توابع الصنف PrintWriter لعمليات الخرج الخاصة بذلك الكائن؛ فكل ما عليك فعله هو تغليف كائن الصنف Writer بكائن الصنف PrintWriter، وذلك بتمريره إلى باني الكائن constructor المُعرَّف بالصنف PrintWriter. بفرض أن charSink من النوع Writer، يُمكِنك كتابة ما يَلِي:

PrintWriter printableCharSink = new PrintWriter(charSink);

يُمكِن للمعامل المُمَّرر إلى الباني أن يكون من النوع OutputStream أو النوع File، وهذا ما سنناقشه في المقال التالي؛ حيث يُنشِئ الباني في العموم كائنًا من النوع PrintWriter، والذي يكون بإمكانه الكتابة إلى مقصد الخرج الخاص بالكائن المُمرَّر إليه. عندما تُرسِل بيانات خرجٍ إلى printableCharSink عبر إحدى توابع الخرج عالية المستوى المُعرَّفة بالصنف PrintWriter، فستُرسَل تلك البيانات إلى نفس المقصد الذي يُرسِل charSink البيانات إليه؛ فكل ما فعلناه هو توفير واجهة أفضل لنفس مقصد الخرج، وهذا يَسمَح لنا باستخدام توابع الصنف PrintWriter لإرسال البيانات إلى ملفٍ أو عبر اتصالٍ شبكي مثلًا.

إذا كان out مُتغيّرًا من النوع PrintWriter، فإنه إذًا يُعرِّف التوابع التالية:

  • out.print(x)‎: يُرسِل قيمة المعامل x بهيئة سلسلةٍ نصيةٍ من المحارف إلى مجرى الخرج، ويُمكِن للمعامل x أن يكون تعبيرًا expression من أي نوع، بما في ذلك الأنواع الأساسية primitive types والأنواع الكائنية؛ حيث يُحوِّل التابع أي كائنٍ إلى سلسلةٍ نصيةٍ عبر تابعه toString()‎. تُمثَّل القيمة الفارغة null بالسلسلة النصية "null".
  • out.println()‎: يُرسِل مِحرف سطرٍ جديد إلى مجرى الخرج.
  • out.println(x)‎: يُرسِل قيمة x متبوعةً بسطرٍ جديد، وهو ما يُكافِئ استدعاء التابعين out.print(x)‎ وout.println()‎ على التوالي.
  • out.printf(formatString, x1, x2, ...)‎: يُرسِل خرجًا مُنسَّقًا للمعاملات المُمرَّرة x1 وx2 و .. وهكذا إلى مجرى الخرج. يمثِّل المعامل الأول سلسلةً نصيةً تُخصِّص صيغة الخرج المطلوبة. إلى جانب ذلك، يَستقبِل التابع أي عددٍ من المعاملات الإضافية التي يُمكِنها أن تنتمي لأي نوع، بشرط أن تتوافق مع صيغة الخرج المُخصَّصة بالمعامل الأول. ألقِ نظرةً على قسم الخرج البسيط والخرج المنسق من مقال المدخلات والمخرجات النصية في جافا للمزيد من المعلومات عن الخرج المُنسَّق فيما يتعلَّق بمجرى الخرج القياسي System.out، ويُوفِّر التابع out.printf نفس الوظيفة.
  • out.flush()‎: يتأكَّد من كتابة المحارف المُرسلة عبر أيٍّ من التوابع السابقة إلى مقصدها بصورةٍ فعليّة. يكون استدعاء هذا التابع ضروريًا في بعض الحالات بالأخص عند إرسال الخرج إلى ملفٍ أو عبر شبكة، وذلك لضمان ظهور الخرج بالمقصد المُحدَّد.

لا تُبلِّغ أيٌ من التوابع السابقة عن استثناءٍ من النوع IOException نهائيًا. بدلًا من ذلك، يتضمَّن الصنف PrintWriter التابع التالي:

public boolean checkError()

يعيد هذا التابع القيمة true في حالة حدوث خطأٍ أثناء عملية الكتابة بمجرى؛ حيث يلتقط الصنف PrintWriter أي استثناءات من النوع IOException، ثم يَضبُط قيمة رايةٍ flag داخليةٍ معينةٍ للإشارة إلى وجود خطأ. يُمكِنك إذًا استخدام التابع checkError()‎ لفحص قيمة تلك الراية، وذلك من خلال استخدام توابع الصنف PrintWriter دون الحاجة لالتقاط أي استثناءات؛ ومع ذلك، إذا كنت تريد كتابة برنامج متين تمامًا، فيجب أن تستدعي التابع checkError()‎ عند استخدام أيٍّ من توابع الصنف PrintWriter لتَتأكَّد من عدم وقوع أي أخطاءٍ مُحتمَلة.

مجاري تدفق البيانات Data Streams

عندما نَستخدِم الصنف PrintWriter لإرسال بياناتٍ إلى مجرًى معيّن، فسيُحوِّل البيانات إلى متتاليةٍ مقروءةٍ من المحارف المُمثِّلة لتلك البيانات. ماذا لو أردنا إرسال البيانات بصيغةٍ ثنائيةٍ مهيأةٍ للآلة؟ في الواقع، تتضمَّن حزمة java.io الصنف DataOutputStream المُمثِّل لمجرى بايتات، والذي يُمكِننا استخدامه لإرسال البيانات إلى المجاري بهيئةٍ ثنائية.

تُعدّ العلاقة بين الصنفين DataOutputStream وOutputStream مشابهةً لتلك الموجودة بين الصنفين PrintWriter وWriter؛ فبينما يَملُك الصنف OutputStream توابع الخرج المُخصَّصة للبايتات فقط؛ يملك الصنف DataOutputStream التابع writeDouble(double x)‎ لقيم الخرج من النوع double، والتابع writeInt(int x)‎ لقيم الخرج من النوع int، وهكذا.

علاوةً على ذلك، من الممكن أيضًا تغليف أي كائنٍ من النوع OutputStream ضمن كائنٍ من النوع DataOutputStream؛ لنتمكَّن من استخدام توابع الخرج عالية المستوى المُعرَّفة به. إذا كان byteSink من النوع OutputStream مثلًا، يُمكِن كتابة ما يَلي لتغليفِه ضمن كائنٍ من النوع DataOutputStream:

DataOutputStream dataSink = new DataOutputStream(byteSink);

تُوفِّر حزمة java.io الصنف DataInputStream بالنسبة للمُدْخلات المُهيأة للآلة، مثل تلك التي يُنشئها DataOutputStream عند اِستخدَامه للكتابة. يُمكِنك تغليف كائنٍ من النوع InputStream ضمن كائنٍ من النوع DataInputStream؛ لتُمكِّنه من قراءة أي نوعٍ من البيانات من مجرى بايتات. أسماء توابع الصنف DataInputStream المسؤولة عن قراءة البيانات الثنائية هي: readDouble()‎ وreadInt()‎ وهكذا.

يكْتُب الصنف DataOutputStream البيانات بصيغةٍ يُمكِن للصنف DataInputStream أن يقرأها بالضرورة، حتى لو أنشأ حاسوبٌ من نوعٍ معين المجرى، وكان المطلوب أن يقرأه حاسوبٌ من نوعٍ آخر. تُوفِّر البيانات الثنائية توافقًا compatibility عبر المنصات، ويُعدُّ هذا أحد الجوانب الأساسية لاستقلالية منصة جافا.

قد ترغب في بعض الحالات بقراءة محارفٍ من مجرًى من النوع InputStream، أو كتابة محارفٍ إلى مجرًى من النوع OutputStream، ولا يُمثِل ذلك مشكلةً لأن المحارف مثلها مثل جميع البيانات؛ فهي تُمثَّل بهيئة أعدادٍ ثنائيةٍ، على الرغم أنه من الأفضل في تلك الحالة استخدام الصنفين Reader وWriter، بدلًا من InputStream وOutputStream. مع ذلك، تستطيع فعل ذلك بتغليف مجرى البايتات ضمن مجرى محارف.

إذا كان byteSource متغيرًا من النوع InputStream وكان byteSink مُتغيرًا من النوع OutputStream، تُنشِئ التعليمات التالية مجاري محارف بإمكانها قراءة المحارف وكتابتها من وإلى مجاري بايتات.

Reader charSource = new InputStreamReader( byteSource ); Writer charSink = new OutputStreamWriter( byteSink );

يُمكِننا تحديدًا تغليف مجرى الدخل القياسي System.in، المُنتمي إلى الصنف InputStream لأسبابٍ تاريخية، ضمن كائنٍ من النوع Reader، لتسهيل قراءة المحارف من الدخل القياسي كما يلي:

Reader charIn = new InputStreamReader( System.in );

لنأخذ مثالًا آخر؛ حيث تُعدّ مجاري الدخل والخرج المُرتبطِة باتصالٍ شبكي مجاري بايتات لا مجاري محارف، ويُمكننا مع ذلك تغليف مجاري البايتات بمجاري محارف للتسهيل من إرسال البيانات المحرفية واستقبالها عبر الشبكة. سنناقش عمليات الدخل والخرج عبر الشبكة لاحقًا.

تتوفَّر طرائقٌ مختلفة لترميز المحارف بهيئة بياناتٍ ثنائية، حيث يُطلَق مُصطلح "طقم محارف charset" على أي ترميز محارف، ويَملُك اسمًا قياسيًا، مثل "UTF-16" و "UTF-8" و "ISO-8859-1"؛ حيث يُرمِّز "UTF-16" المحارف بهيئة قيم يونيكود Unicode مُكوَّنةٍ من "16 بت"، وهو الترميز المُستخدَم داخليًا بجافا؛ بينما يُعدّ "UTF-8" أسلوبًا لترميز محارف اليونيكود بتخصيص "8 بت" لمحارف ASCII الشائعة في مقابل عدد بتاتٍ أكثر للمحارف الأخرى؛ أما ترميز "ISO-8859-1" المعروف أيضًا باسم "Latin-1"، فهو مكوَّنٌ من "8 بت"، ويتضمَّن محارف ASCII إلى جانب محارفٍ أخرى مُستخدَمةٍ ضمن عدة لغاتٍ أوروبية.

يَعتمِد الصنفان Reader وWriter على طقم المحارف الافتراضي ضمن الحاسوب المُشّغلان عليه، إلا إذا خصَّصت طقم محارفٍ معين بتمريره عبر الباني على النحو التالي:

Writer charSink = new OutputStreamWriter( byteSink, "ISO-8859-1" );

يؤدي اختلاف ترميزات أطقم المحارف وكثرتها إلى تعقيد عملية معالجة النصوص، وهو ما يُعدّ أمرًا سيئًا للمتحدثين بالإنجليزية، ولكنه ضروري لغيرهم ممن يَستخدِمون أطقم محارفٍ مختلفة. لا حاجة للقلق عمومًا بشأن أيٍّ من ذلك، إنما عليك فقط أن تتذكَّر أن هناك أطقم محارفٍ مختلفة إذا واجهت بياناتٍ نصيةٍ مُرمزَّة بطريقةٍ غير اعتيادية.

قراءة النصوص

تُجرَى كثيرٌ من عمليات الدخل والخرج على محارفٍ مقروءة، ومع ذلك، لا توفِّر جافا صنفًا قياسيًا يُمكِنه قراءة المحارف بإمكانياتٍ متكافئة مع ما يُوفِّره الصنف PrintWriter لإخراج المحارف. قد يَكون الصنف Scanner -الذي تعرَّضنا له في مقال المدخلات والمخرجات النصية في جافا، والذي سنناقشه تفصيليًا فيما يلي مكافئًا نوعًا ما، ولكنه ليس صنفًا فرعيًا من أي صنف مجرى؛ ما يعني أنه لا يتناسب مع إطار عمل مجاري التدفق. هناك مع ذلك حالةٌ بسيطةٌ بإمكان الصنف القياسي BufferedReader معالجتها بسهولة. يتضمَّن هذا الصنف التابع التالي:

public String readLine() throws IOException

يقرأ هذا التابع سطرًا نصيًا واحدًا من المُدْخلات، ويقرأ خلال ذلك مؤشر نهاية السطر أيضًا، ولكن لا يكون هذا المؤشر جزءًا من السلسلة النصية التي يعيدها التابع؛ بينما يُعيد التابع القيمة null عند وصوله إلى نهاية المجرى. تَستخدِم الأنواع المختلفة من مجاري الدْخَل محارفًا مختلفةً للإشارة إلى نهاية السطر، ولكن يُمكِن للتابع readLine التعامُل مع أغلب الحالات الشائعة. تَستخدِم حواسيب Unix، بما في ذلك Linux و Mac OS X عادةً محرف سطرٍ جديد '‎\n' للإشارة إلى نهاية السطر؛ بينما يستخدم Macintosh محرف العودة إلى بداية السطر '‎\r'؛ أما Windows فيَستخدِم المحرفين "‎\r\n". تستطيع الحواسيب العصرية عمومًا التعامل مع كل تلك الاحتمالات.

يُعرِّف الصنف BufferedReader إضافةً إلى ذلك تابع النسخة lines()‎، والذي يعيد قيمةً من النوع Stream<String>‎ يُمكِن استخدامها مع واجهة برمجة تطبيقات stream API -انظر مقال مقدمة إلى واجهة برمجة التطبيقات Stream API في جافا-. بفرض أن reader متغيرٌ من النوع BufferedReader، ستكون الطريقة الأمثل لمعالجة جميع الأسطر التي قرأها بتطبيق العامل forEach()‎ على مجرى الأسطر على النحو التالي:

reader.lines().forEachOrdered(action)‎

حيث تمثِّل action مُستهلِك سلاسلٍ نصية، والذي يُكتَب عادةً بصيغة تعبيرات لامدا lambda expression.

تشيع معالجة الأسطر واحدًا تلو الآخر، لذلك يُمكِننا تغليف wrap أي كائنٍ من النوع Reader ضمن كائنٍ من النوع BufferedReader لتسهيل قراءة الأسطر النصية بالكامل. بفرض أن reader من النوع Reader، يُمكِننا تغليفه باستخدام كائنٍ من النوع BufferedReader على النحو التالي:

BufferedReader in = new BufferedReader( reader );

كما يُمكِننا مثلًا استخدامه مع الصنف InputStreamReader المذكور بالأعلى لقراءة أسطرٍ نصيةٍ من كائنٍ من النوع InputStream، أو قد نُطبقه على System.in على النحو التالي:

BufferedReader in; // BufferedReader for reading from standard input. in = new BufferedReader( new InputStreamReader( System.in ) ); try { String line = in.readLine(); while ( line != null ) { processOneLineOfInput( line ); line = in.readLine(); } } catch (IOException e) { }

تقرأ الشيفرة السابقة أسطرًا من الدخل القياسي، وتعالجها حتى الوصول إلى نهاية المجرى. تَعمَل مؤشرات نهاية المجرى حتى مع المُْدْخَلات التفاعلية، حيث يُولِّد النقر على زر Control-D ببعض الحواسيب على الأقل مثلًا مؤشر نهاية مجرى بمجرى الدخل القياسي. تُعدُّ معالجة الاستثناءات إلزامية نظرًا لإمكانية تبليغ التابع readLine عن استثناءاتٍ exception من النوع IOException، ولهذا كان من الضروري إحاطة التابع بتعليمة try..catch. يُمكِننا بدلًا من ذلك كتابة عبارة throws IOException بتصريح التابع المُتضمِّن للشيفرة بالأعلى. يجب أن تُستورد الأصناف الآتية من حزمة java.io:

  •  BufferedReader.
  • InputStreamReader.
  • IOException.

على الرغم من تسهيل الصنف BufferedReader عملية قراءة الأسطر النصية، فإن هذا ليس الغرض الأساسي من وجوده، حيث تعمَل بعض أجهزة الدخل والخرج بأعلى كفائتها عند قراءة أو كتابة قدرٍ كبيرٍ من البيانات دفعةً واحدةً بدلًا من مجرد قراءة بايتاتٍ أو محارفٍ مفردة. يُوفِّر الصنف BufferedReader تلك الإمكانية، حيث يُمكِنه قراءة دفعةٍ من البيانات، وتخزينها ضمن ذاكرةٍ داخلية، تُعرَف باسم المخزن المؤقت buffer.

عندما تقرأ من كائنٍ من الصنف BufferedReader، فإنه في الواقع يستعيد البيانات من المخزن المؤقت إذا كان ذلك ممكنًا، أي إذا لم يَكن المخزن فارغًا؛ حيث يضطّر تلك الحالة فقط من التعامل مع مصدر الدْخل مرةً أخرى لجلب المزيد من البيانات. يتوفَّر أيضًا الصنف المكافئ BufferedWriter، بالإضافة إلى وجود أصناف مجاري تدفق في مخزنٍ مؤقت للعمل مع مجاري البايتات.

اِستخدمنا الصنف غير القياسي TextIO سابقًا لقراءة المُدْخَلات من المُستخدِمين والملفات؛ حيث يتميز ذلك الصنف بسهولة قراءة البيانات المنتمية لأي نوعٍ من الأنواع الأساسية primitive types، ولكنه لا يستطيع مع ذلك القراءة من أكثر من مصدر دخلٍ واحد بنفس الوقت، وهو بذلك لا يَتّبِع نفس نمط أصناف جافا القياسية المبنية مُسبقًا للدْخَل والخَرْج.

إذا أعجبك أسلوب الصنف TextIO في التعامل مع المُدْخَلات، يُمكِنك إلقاء نظرةٍ على الصنف TextReader.java، الذي يُنفِّذ implement أسلوبًا مشابهًا بطريقةٍ أكثر كائنية object-oriented. لم نَستخدِم الصنف TextReader ضمن هذا الإصدار من الكتاب، ولكننا أشرنا إليه ضمن بعض الإصدارات السابقة.

الصنف Scanner

لم تُوفِّر جافا بإصداراتها الأولى دعمًا مبنيًا مسبقًا للمُدْخلات البسيطة، حيث اعتمد الدعم الذي وفِّرته على بعض التقنيات المتقدمة نوعًا ما، ووفَّرت بعد ذلك الصنف Scanner المُعرَّف بحزمة java.util لتسهيل قراءة المُدْخَلات من الأنواع البسيطة، وهو ما يُعدّ تَحسُنًا كبيرًا، ولكنه لم يَحِل المشكلة بالكامل. تعرَّضنا للصنف Scanner في المقال مقدمة إلى واجهة برمجة التطبيقات Stream API في جافا، ولكننا لم نَستخدِمه بعدها، ولهذا سنعتمد بغالبية الأمثلة التالية على الصنف Scanner بدلًا من TextIO.

يُعرِّف الصنف البرامج المسؤولة عن عمليات الدْخَل على هيئة توابع نسخ instance methods؛ أي ينبغي أن نُنشِئ كائنًا منه إذا أردنا أن نَستخدِمها. يَستقبِل باني الصنف constructor المصدر الذي ينبغي أن تُقرَأ منه المحارف؛ أي أنه يَعمَل مثل مُغلِّف لذلك المصدر. يُمكِن للمصدر أن يكون من الصنف Reader، أوInputStream، أوString، أوFile، أو غيرها من الاحتمالات الأخرى.

إذا اِستخدَمنا النوع String مصدرًا للمُدْخَلات، فسيقرأ الصنف Scanner محارف السلسلة النصية ببساطة من بدايتها إلى نهايتها بنفس الكيفية التي كان سيتعامل بها مع متتالية محارفٍ مصدرها مجرى، حيث يُمكِننا مثلًا استخدام كائنٍ من النوع Scanner للقراءة من الدْخَل القياسي بكتابة ما يلي:

Scanner standardInputScanner = new Scanner( System.in );

وبفرض أن charSource من النوع Reader، يُمكِننا بالمثل أن نُنشِئ كائنًا من الصنف Scanner للقراءة منه بكتابة ما يَلي:

Scanner scanner = new Scanner( charSource );

يُعالِج الصنف Scanner المُدَْخَلات عادةً وحدةً token تلو الأخرى؛ حيث يُقصَد بالوحدة سلسلةً نصيةً من المحارف لا يُمكِن تقسيمها إلى وحداتٍ أصغر، وإلا ستفقد معناها وفقًا للمهمة المعنية بها. يُمكِن للوحدة أن تكون كلمةً مفردةً مثلًا أو سلسلةً نصيةً مُمثِّلةً لقيمةٍ من النوع double.

يحتاج الصنف Scanner أيضًا لوجود "فاصلٍ delimiter" بين تلك الوحدات، والذي يُمثَّل عادةً ببضعة فراغات، مثل محارف الفراغ، أو محارف tab، أو مؤشرات نهاية السطر. يُهمِل الصنف Scanner تلك الفراغات، حيث يقتصر الهدف من وجودها على الفصل بين الوحدات. يتضمَّن الصنف توابع نسخٍ لقراءة مختلف أنواع الوحدات. لنفترض أن scanner كائنٌ من النوع Scanner، يكون لدينا التوابع التالية:

  • scanner.next()‎: يقرأ الوحدة التالية من مصدر المُدْخَلات، ويعيد قيمةً من النوع String.
  • scanner.nextInt()‎ وscanner.nextDouble()‎ وغيرها: يقرأون الوحدة التالية من مصدر المُدْخَلات، ويحاولون تحويلها إلى قيمةٍ من النوع int وdouble وغيرها. تتوفَّر توابعٌ لقراءة جميع الأنواع الأساسية.
  • scanner.nextLine()‎: يقرأ سطرًا كاملًا من المُدْخَلات حتى يَصِل إلى مؤشر نهاية السطر، ثم يعيد السطر على أنه قيمةٌ من النوع String. في حين يقرأ التابع مؤشر نهاية السطر، فإنه لا يُضمُّنه بالقيمة التي يُعيدها، كما أنه لا يعتمد على مفهوم الوحدات؛ فهو يعيد سطرًا كاملًا بما قد يحتويه من أية فراغات. يُمكِن أن تكون القيمة المُعادة من التابع مجرد سلسلةٍ نصيةٍ فارغة.

يُمكِن للتوابع السابقة أن تُبلِّغ عن بعض أنواع الاستثناءات، حيث يمكنها على سبيل المثال التبليغ عن استثناءٍ من النوع NoSuchElementException عند محاولتها القراءة من مصدرٍ تجاوزت نهايته بالفعل. كما تُبلِّغ توابعٌ، مثل scanner.getInt()‎ عن حدوث استثناءٍ من النوع InputMismatchException، إذا لم تكُن الوحدة token التالية من النوع المطلوب. لا تُعدّ معالجة الاستثناءات التي تُبلِّغ عنها تلك التوابع إلزامية.

يتمتع الصنف Scanner بإمكانياتٍ جيدة لفحص المُدْخَلات دون قرائتها؛ حيث يُمكِنه مثلًا أن يُحدِّد فيما إذا كان هناك المزيد من الوحدات للقراءة، أو إذا كانت الوحدة التالية من نوعٍ معين. إذا كان scanner كائنًا من النوع Scanner، يُمكِننا استخدام التوابع التالية:

  • scanner.hasNext()‎: يُعيد القيمة المنطقية true في حالة وجود وحدةٍ واحدةٍ على الأقل بمصدر المُدْخَلات.
  • scanner.hasNextInt()‎ وscanner.hasNextDouble()‎، وهكذا: يعيدون القيمة المنطقية true إذا كان هناك وحدةً واحدةً على الأقل بمصدر المُدْخَلات، وكانت تلك الوحدة قيمةً من النوع المطلوب.
  • scanner.hasNextLine()‎: يعيد القيمة المنطقية true في حالة وجود سطرٍ واحدٍ على الأقل بمصدر المُدْخَلات.

تَحِد ضرورة اِستخدَام فاصلٍ بين الوحدات من فعالية الصنف Scanner نوعًا ما، لكنه رغم ذلك سهل الاستخدام، ومناسبٌ للعديد من التطبيقات المختلفة. نظرًا لوجود الكثير من الأصناف المسؤولة عن عمليات الدْخَل، مثل BufferedReader وTextIO وScanner، قد تُصيبك الحيرة لإختيار الأنسب للاستخدام. يُفضَّل عمومًا اِستخدام الصنف Scanner إلا إذا كان هناك سببٌ واضحٌ يدفعك لتفضيل أسلوب الصنف TextIO. في المقابل، يُعدّ الصنف BufferedReader بديلًا بسيطًا، إذا كان كل ما تحتاجه هو مجرد قراءة أسطرٍ نصيةٍ كاملةٍ من مصدر المُدْخَلات.

لاحِظ أنه من الممكن تغيير الفاصل الذي يَعتمِد عليه الصنف Scanner للفصل بين الوحدات tokens، ولكن يتطلَّب ذلك التعامل مع ما يُعرَف باسم التعبيرات النمطية regular expression، والتي قد تكون معقدةً بعض الشيء، وهي عمومًا ليست ضمن أهداف هذا الكتاب، ولكن سنأخذ مثالًا بسيطًا عنها؛ ولنفترض مثلًا أننا نريد وحداتٍ مؤلفةً من كلماتٍ مُكوَّنةٍ فقط من أحرف الأبجدية الإنجليزية. يُمكِن في تلك الحالة للفاصل أن يَكون أي محرفٍ من غير تلك الأحرف؛ فإذا كان لدينا كائنٌ من الصنف Scanner اسمه scnr، فإننا نستطيع كتابة scnr.useDelimiter("[^a-zA-Z]+")‎ لجعله يَستخدِم هذا النوع من الفواصل، وستكون بذلك الوحدات المعادة من scnr.next()‎ مُكوَّنةً بالكامل من أحرف الأبجدية الإنجليزية. تُعدّ السلسلة النصية ‎[^a-zA-Z]+‎ تعبيرًا نمطيًا، وهي في الواقع أداةً مهمةً لأي مبرمج، وعليك أن تشرُع بتعلُّمها إذا واتتك الفرصة لذلك.

إدخال وإخراج الكائنات المسلسلة Serialized

تَسمَح لنا الأصناف الآتية:

  • PrintWriter.
  • Scanner.
  • DataInputStream.
  • DataOutputStream.

بمعالجة الدْخَل والخَرْج من جميع أنواع جافا الأساسية، ولكن ماذا لو أردنا أن نقرأ أو نكتب كائنات؟ سنحتاج بالضرورة إلى العثور على طريقةٍ ما لترميز الكائنات، وتحويلها إلى متتاليةٍ من القيم المنتمية لأي من الأنواع الأساسية، والتي يُمكِن بعد ذلك إرسالها على أنها خَرْجٌ بهيئة بايتات أو محارف. يُطلَق على تلك العملية اسم سَلسَلة serialize الكائن. سنضطّر من الناحية الأخرى لقراءة البيانات المُسَلسَلة، ثم اِستخدَامها لإعادة بناء الكائن الأصلي.

إذا كان الكائن معقدًا بعض الشيء، فسيضطّرنا ذلك إلى الكثير من العمل الذي هو في أساسه مجرد عمل روتيني. تُوفِّر جافا لحسن الحظ الصنفين ObjectInputStream وObjectOutputStream؛ لتحمُّل عبء غالبية ذلك العمل. لاحِظ أنهما صنفان فرعيان من الصنفين InputStream وOutputStream، ويُمكِنهما العمل مع الكائنات المُسَلسَلة.

يُعدّ الصنفان ObjectInputStream وObjectOutputStream أصنافًا مغلِّفة؛ أي يُمكِنها أن تُغلِّف مجارٍ streams من النوعين InputStream وOutputStream على الترتيب، وهو ما يَسمَح بإدْخال وإخراج الكائنات عبر أي مجرى بايتات؛ حيث يتضمَّن الصنف ObjectInputStream التابع readObject()‎؛ بينما يتضمَّن الصنف ObjectOutputStream التابع writeObject(Object obj)‎، ويُمكِنهما التبليغ عن استثناءاتٍ من النوع IOException.

اقتباس

ملاحظة: يُعيد التابع readObject()‎ قيمةً من النوع Object، والتي يجب أن نُحوِّلها type-cast إلى نوع الكائن الفعلي المُفترَض قراءته.

يتضمَّن الصنف ObjectOutputStream التوابع writeInt()‎ وwriteDouble()‎ وما يُشبهها، لإرسال قيمٍ منتميةٍ لأي من الأنواع الأساسية إلى مجرى الخرج، كما يتضمَّن الصنف ObjectInputStream توابعًا مكافئةً لقراءة قيمٍ منتميةٍ لأي من الأنواع الأساسية. لاحِظ أنه من الممكن إرسال كائناتٍ أثناء إرسال قيم تنتمي لأي من الأنواع الأساسية، حيث تُمثَّل القيم المنتمية للأنواع الأساسية بصيغتها الثنائية binary الداخلية عند تخزينها بملف.

تُعدّ مجاري الكائنات بمثابة مجاري بايتات؛ حيث تُمثَّل الكائنات بصيغةٍ ثنائيةٍ مهيأة للآلة. في حين يُعزز ذلك من كفائتها، فإنه يتسبَّب بنفس الهشاشة التي تُعاني منها البيانات الثنائية في العموم. ونظرًا لأن الصيغة الثنائية للكائنات مُهيأةٌ للغة جافا، لا يكون من السهل إتاحة بيانات مجاري الكائنات للبرامج المكتوبة بلغاتٍ برمجيةٍ مختلفة. بناءً على ذلك، يُفضَّل اِستخدَام مجاري الكائنات فقط عند الحاجة إلى تخزينها تخزينًا مؤقتًا، أو إلى نَقْلها عبر اتصالٍ شبكي بين برنامجي جافا؛ أما بالنسبة للتخزين طويل الأمد أو الاتصال مع برامج مكتوبة بلغات آخرى، فهناك طرائقٌ بديلةٌ أفضل لسَلسَلة الكائنات (ألقِ نظرةً على المقال مقدمة مختصرة للغة XML لطريقةٍ معتمدةٍ على المحارف).

يَعمَل الصنفان ObjectInputStream وObjectOutputStream مع الكائنات التي تُنفِّذ الواجهة Serializable فقط، كما يجب أن تكون جميع متغيرات النسخ المُضمَّنة بتلك الكائنات قابلةً للسَلسَلة. لا تنطوي عملية جعل كائنٍ معينٍ قابلًا للسَلسَلة على أي عملٍ تقريبًا؛ حيث لا تُصرّح الواجهة Serializable حقيقةً عن أي توابع، وإنما هي موجودةٌ فقط مثل إشارةٍ للمُصرِّف على أن الكائن المُنفِّذ لها قابل للكتابة والقراءة. يَعنِي ذلك أن كل ما علينا فعله هو إضافة الكلمات implements Serializable إلى تعريف الصنف. لاحِظ أن الكثير من أصناف جافا القياسية مُصرَّح عنها بحيث تكون قابلة للسَلسَلة بالفعل.

تنبيه عن استخدام الصنف ObjectOutputStream: أُعدَّت مجاري ذلك الصنف لتجنَّبنا إعادة كتابة نفس الكائن أكثر من مرة، ولهذا، إذا واجه المجرى كائنًا معينًا للمرة الثانية، فإنه في الواقع يَستخدِم مرجعًا reference إلى كائن المرة الأولى أثناء الكتابة. يَعنِي ذلك أنه في حالة كان الكائن قد عُدّل بين المرتين الأولى والثانية، فإننا لن نَحصُل على البيانات الجديدة؛ لأن القيمة المُعدَّلة لا تُرسَل بصورةٍ صحيحة إلى المجرى.

يَرجِع ذلك إلى أن مجاري الصنف ObjectOutputStream قد أُعدّت بالأساس للعمل مع الكائنات الثابتة immutable التي لا يُمكِن تعديلها بعد إنشائها، مثل السلاسل النصية من النوع String. ومع ذلك، إذا أردت حقًا أن تُرسِل كائنًا مُتغيرًا mutable إلى هذا النوع من المجاري، وكان من المحتمل أن تُرسِل نفس الكائن أكثر من مرة، فيُمكِنك في تلك الحالة أن تضمَن إرسال النسخة الصحيحة من الكائن باستدعاء تابع المجرى reset()‎ قبل إرسال الكائن إليه.

ترجمة -بتصرّف- للقسم Section 1: I/O Streams, Readers, and Writers من فصل Chapter 11: Input/Output Streams, Files, and Networking من كتاب Introduction to Programming Using Java.

اقرأ أيضًا
77,301 كيفية تحديد المسارات وأنواع طلبات HTTP في إطار العمل Express

سنشرح في هذا المقال كيفية التعامل مع المسارات routes وأنواع طلبات HTTP في إطار العمل Express بتطبيق مشروع عملي، وسنتعلم كيفية تحديد المسارات واستخدام طلبات HTTP من نوع GET و POST و PUT و DELETE لمعالجة البيانات.

تتعامل المسارات مع انتقال المستخدم إلى عناوين URL مختلفة، ويسهل بروتوكول HTTP عمليات التواصل ونقل البيانات من خادم Express إلى متصفح الويب.

يُفضل أن يكون لديك معرفة مسبقة ببيئة Node.js، لكن ذلك ليس ضروريًا، انظر صفحة التعريف Node.js على موسوعة حسوب وننصح بقراءة صفحة طريقة الاستعمال وتشغيل الأمثلة أيضًا من التوثيق نفسه.

إعداد المشروع

احرص على أن تكون لديك بيئة Node.js جاهزة ومثبتة على حاسوبك حتى تتمكن من استخدام إطار العمل Express.

نفذ الخطوات التالية في الطرفية Terminal:

أولًا، أنشئ مجلدًا جديدًا خاص بالمشروع باسم node-express-routing:

mkdir node-express-routing

ثم انتقل إلى المجلد الجديد:

cd node-express-routing

ثانيًا، أنشئ مشروعك الجديد معتمدًا الإعدادات الافتراضية، ومن ضمنها ملف package.json حتى تتمكن من الوصول إلى الاعتماديات dependencies:

npm init -y

ثم أنشئ ملف index.js حيث ستتعامل مع المسارات وتوابع طلبات HTTP:

touch index.js

ثبت الحزمتين Express و nodemon، إذ سنحتاج إلى الحزمة الأخيرة لإعادة تشغيل المشروع باستمرار عند كل تغيير في ملف index.js:

npm install express --save npm install nodemon --save-dev

افتح الملف package.json باستخدام إحدى محررات النصوص وأضف سكربت البدء start لتشغيل ملف index.js عبر الأمر nodemon:

{ "name": "node-express-routing", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "start": "nodemon index.js" }, "keywords": [], "author": "Paul Halliday", "license": "MIT" }

يتيح لك ذلك استخدام الأمر npm start في الطرفية لتشغيل خادم Express وحفظ التعديلات. تهانينا! أصبحت جاهزًا لإنشاء خادم Express بعد أن أعددت الحزمة nodemon لتعيد تشغيل الخادم عند التعديل على الملف index.js.

تشغيل خادم Express

ستتعامل مع منطق دمج المسارات ومختلف أنواع طلبيات HTTP في خادم Express الخاص بك، وستعمل على إعداد وتشغيل خادم خاص بك لتتصور كيف سيبدو مشروعك في المتصفح.

اطلب وحدة Express عبر التابع require، في ملف index.js ثم خزن نسخة instance في المتغير app، وبعد ذلك أعلن عن متغير PORT واضبطه إلى القيمة 3000.

const express = require('express'); const app = express(); const PORT = 3000; app.use(express.json()); app.listen(PORT, () => console.log(`Express server currently running on port ${PORT}`));

ثم استدعِ التابع ()listen مع التابع ()app ومرر المتغير PORT كوسيط أول، ودالة رد نداء callback كوسيط ثاني، إذ تقوم الدالة الوسيطة ()listen بإنشاء خادم محلي على المنفذ المحدد بالمتغير PORT لمعاينة التعديلات.

مرر الوسيط ()express.json إلى التابع ()app.use لتحليل البيانات الواردة من خلال طلبات HTTP الخاصة بك. اعتمدت الإصدارات السابقة على تبعية المحلل body-parser، أما في الإصدارات الأحدث ضمنت Express برمجيات وسيطة معدة مسبقًا لتحليل البيانات خصوصًا التي بصيغة JOSN.

اكتب الأمر التالي في الطرفية لتبدأ تشغيل المشروع:

npm start

سيُخدَّم المشروع على العنوان http://localhost:3000، وستلاحظ رسالة الخطأ التالية عند الانتقال إلى المتصفح:

تعد هذه خطوة البداية، الآن علينا تعريف طلبيات HTTP للتخاطب الصحيح بين المتصفح والخادم.

استقبال الخادم طلبية GET من طلبيات HTTP

يمكنك إرسال البيانات من خادم Express الخاص بك إلى المتصفح لمعاينة مشروعك بناءً على إرساله طلبية GET محددة الوجهة، ولفعل ذلك استدعِ التابع ()get. مع المتغير app ولا تحدد الوجهة حاليًا، ومرر له دالة تأخذ الوسطين request و response كما يلي:

app.get('/', (request, response) => { response.send('Hello'); });

لاحظ أن الوسيط request يحتوي على معلومات عن الطلبية GET التي أرسلها المتصفح، بينما يرسل التابع ()response.send بيانات إلى المتصفح، إما على شكل سلسلة، أو كائن، أو مصفوفة كرد أو إجابة على الطلب المرسل.

لنتعرف على المسارات وطلبات HTTP الأخرى، بعد أن طبقنا طلبية GET.

التعامل مع المسارات Routes

أنشئ طلبية GET مع تحديد المسار '‎/accounts' والمسار '‎/accounts/:id'، ثم صَرّح عن مصفوفة حسابات باسم accounts:

let accounts = [ { "id": 1, "username": "paulhal", "role": "admin" }, { "id": 2, "username": "johndoe", "role": "guest" }, { "id": 3, "username": "sarahjane", "role": "guest" } ]; app.get('/accounts', (request, response) => { response.json(accounts); }); app.get('/accounts/:id', (request, response) => { const accountId = Number(request.params.id); const getAccount = accounts.find((account) => account.id === accountId); if (!getAccount) { response.status(500).send('Account not found.') } else { response.json(getAccount); } });

ستحصل على جميع معلومات الحسابات الموجودة في المصفوفة عند الانتقال إلى العنوان الآتي:

 http://localhost:3000/accounts

 حيث ستكون نتيجة الخرج كما يلي:

[ { "id": 1, "username": "paulhal", "role": "admin" }, { "id": 2, "username": "johndoe", "role": "guest" }, { "id": 3, "username": "sarahjane", "role": "guest" } ]

يمكن طلب معلومات حساب محدد بعينه من خلال إرسال مُعرِّفه إلى نقطة الوصول endpoint التالية: id:/، حيث يعتبر إطار العمل Express أن ‎:id في نقطة الوصول ‎/accounts/:id هي نص بديل لأحد معاملات المستخدم ويطابقها مع القيمة المقابلة في الرابط المرسل لنقطة الوصول.

لاحظ النتيجة عند الانتقال إلى العنوان http://localhost:3000/accounts/3:

{ "id": 3, "username": "sarahjane", "role": "guest" } التعامل مع الطلبيات POST و PUT و DELETE

توفر طلبيات HTTP الأخرى غير النوع GET (اجلب) مرونة أكبر في التعامل مع البيانات وهي الطلبيات POST (انشر) و PUT (ضع) و DELETE (احذف)، إذ تنشئ الطلبية POST بيانات جديدة في الخادم (تنشر على الخادم)، بينما تعدل الطلبية PUT على البيانات الموجودة (تضع بيانات في الخادم)، أما الطلبية DELETE فتمسح البيانات من الخادم.

طلبية POST

استخدم طلبية POST لإنشاء بيانات جديدة في مصفوفة الحسابات accounts، عن طريق استدعاء التابع ()post. مع المتغير app وتحديد المسار accounts/ في حقل الوسيط الأول:

app.post('/accounts', (request, response) => { const incomingAccount = request.body; accounts.push(incomingAccount); response.json(accounts); })

ستُرسل البيانات القادمة من الطلبية POST إلى مصفوفة الحسابات accounts ثم يرسل الرد على هيئة كائن JSON.

لاحظ أن مصفوفة الحسابات accounts أصبحت تحتوي على مستخدم جديد:

[ { "id": 1, "username": "paulhal", "role": "admin" }, { "id": 2, "username": "johndoe", "role": "guest" }, { "id": 3, "username": "sarahjane", "role": "guest" }, { "id": 4, "username": "davesmith", "role": "admin" } ] طلبية PUT

يمكن تعديل حساب ما إن أرسلت طلبية PUT إلى الخادم، ويمكن له أن يعالجها عن طريق استدعاء التابع ()put. مع المتغير app وتمرير المسار '‎/accounts/:id' في حقل الوسيط الأول، وستجد من خلاله مُعرِّف الحساب المدخل، وتستخدم التابع الشرطي if لتعديل البيانات الجديدة:

app.put('/accounts/:id', (request, response) => { const accountId = Number(request.params.id); const body = request.body; const account = accounts.find((account) => account.id === accountId); const index = accounts.indexOf(account); if (!account) { response.status(500).send('Account not found.'); } else { const updatedAccount = { ...account, ...body }; accounts[index] = updatedAccount; response.send(updatedAccount); } });

يمكنك الآن تعديل البيانات في مصفوفة الحسابات، فإن أرسلت طلبية PUT إلى الخادم عبر الوجهة التالية:

http://localhost:3000/accounts/1

لطلب تغيير دور مستخدم محدد مثل إرسال البيانات التالية:

{ "role": "guest" }

فسيتغير دور "role" ذلك المستخدم من مدير admin إلى ضيف guest، وذلك عند الانتقال إلى العنوان http://localhost:3000/accounts/1:

{ "id": 1, "username": "paulhal", "role": "guest" } الطلبية DELETE

يمكن حذف المستخدمين وبياناتهم إن أرسلت طلبية DELETE إلى الخادم والذي يعالجها عن طريق استدعاء التابع ()delete. مع المتغير app وتضمين المسار '/accounts/:id' في حقل الوسيط الأول. سيبحث التابع بعدها ضمن مصفوفة الحسابات عن الحساب ذو المعرف المراد حذفه ويتخذ الإجراء المناسب.

app.delete('/accounts/:id', (request, response) => { const accountId = Number(request.params.id); const newAccounts = accounts.filter((account) => account.id != accountId); if (!newAccounts) { response.status(500).send('Account not found.'); } else { accounts = newAccounts; response.send(accounts); } });

لاحظ أنه عند إرسال طلبية DELETE إلى العنوان http://localhost:3000/accounts/1 أن الحساب ذا المعرف 1 من مصفوفة الحسابات accounts سيُحذَف.

ختامًا

تعلمنا في هذا المقال كيف يساعد تحديد المسارات واستخدام طلبيات HTTP في تفاعل الخادم مع الطلبيات التي يرسلها المتصفح كما وضحنا في مثال المستخدمين والتعامل مع بياناتهم من إنشاء وتحديث وحذف في خادم Express.

للحصول على المساعدة والدعم يمكنك إضافة سؤالك في قسم الأسئلة والأجوبة في أكاديمية حسوب.

ترجمة- وبتصرف للمقال How To Define Routes and HTTP Request Methods in Express لصاحبه Paul Halliday.

اقرأ أيضًا
77,295 إنشاء نموذج ثلاثي اﻷبعاد لقرد يرتدي قبعة احتفال باستخدام برنامج بلندر

سنتعلم في هذا المقال كيفية إنشاء مجسم ثلاثي الأبعاد لقرد يرتدي قبعة حفلات باستخدام برنامج بلندر Blender.

إليك النتيجة النهائية للمشروع:

ستحتاج في هذا المشروع إلى:

  • عتاديات: حاسوب مكتبي أو شخصي قادر على تشغيل برنامج بلندر.
  • برمجيات: برنامج بلندر Blender الإصدار 2.8 (أو أي إصدار أحدث).

كما يمكنك الحصول على موارد المشروع ونتيجته النهائية من مستودع GitHub.

خاصية التكبير والتدوير

لاحظ أنه عندما تفتح برنامج بلندر، ستظهر لك الشاشة الافتتاحية التالية:

أغلقها عبر الضغط على يمين الشاشة. ستظهر لك شاشة العرض ثلاثي الأبعاد، حيث ستضع جميع العناصر التي تريد عرضها أو تصيّيرها render، ولاحظ أن لديك حاليًا ثلاثة عناصر.

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

يمكنك تدوير الشاشة بالضغط على زر الفأرة الأوسط أو عجلة الفأرة وتدوير المؤشر بالاتجاه الذي ترغب.

وضع العرض ثلاثي الأبعاد

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

المكعب

يوجد لديك مكعب في منتصف الشاشة، وهو ما سنعمل عليه و نصيّره حتى نحصل على الشكل المطلوب.

مصدر ضوئي

يمكنك معرفة مصدر الضوء في المشهد وذلك عبر تدوير المشهد.

دوّر المشهد حتى ترى الجزء العلوي من مصدر الضوء.

كاميرا

تحدد الكاميرا الزاوية التي ستشاهد منها المشهد.

دوّر المشهد حتى يصبح خلف الكاميرا، وذلك بالضغط على الرقم 0 من لوحة المفاتيح أو من نافذة عرض View > Cameras > Active Cameras:

ستعرض لك الكاميرا صورة المكعب.

التصيير

يُعّرف التصيّير Rendering بأنه عملية توليد صورة من نموذج ثلاثي الأبعاد. صيّر المشهد التالي عبر الضغط على مفتاح F12 في لوحة المفاتيح، أو اضغط على مفتاحي F12 + Fn إذا كنت تستخدم حاسوبًا يعمل على نظام تشغيل.

لاحظ أن الجزء العلوي والجانب الأيمن من المكعب مضاءان، لكن الجانب الأيسر مظلم. يعود ذلك لأن مصدر الضوء موجود أعلى الجانب الأيمن من المكعب.

اضغط على مفتاح ESC للخروج من وضع التصيّير.

تحديد الكائنات ونقلها

يمكنك تحديد الكائنات في برنامج بلندر بمجرد الضغط على زر الفأرة الأيمن. الآن، حدد المكعب ولاحظ ظهور حد برتقالي حول المكعب. كما يمكنك تحريك الكائنات في بلندر باستخدام المقابض الزرقاء، والحمراء، والخضراء؛ إذ يُستخدم المقبض الأزرق للتحرك صعودًا وهبوطًا على محور z، ويُستخدم المقبض الأخضر للتنقل يمنةً ويسرى على المحور y، أما المقبض الأحمر فيتنقل على المحور x.

اقتباس

ملاحظة: ستحتاج إلى النقر على أداة النقل من قائمة الرموز الموجودة على يسار الشاشة لإظهار المقابض، اذا كان لديك برنامج بلندر النسخة 2.8.

حرك المكعب للأعلى بالضغط بزر الفأرة الأيسر على المقبض الأزرق، إذ سيظهر لك خطٌ أزرق، حرك المكعب على امتداده.

حرك المكعب لليمين باستخدام المقبض الأخضر، وسيظهر لك خطٌ أخضر، عندها حرك المكعب على امتداده.

حرك المكعب للأمام وللخلف باستخدام المقبض الأحمر، وسيظهر لك خطٌ أحمر حرك المكعب على امتداده.

انقل المكعب إلى مكان آخر، ثم اذهب إلى عرض التصيير لترى كيف يبدو المكعب، فمثلًا:

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

استخدم المقابض الثلاثة لتحريك المكعب حتى يصبح خلف الكاميرا.

صيّر المكعب لترى كيف يبدو، سترى ذات الشكل.

اضغط على مفتاح ESC للخروج من عرض التصيير.

إضافة القرد

احذف المكعب لأننا لن نحتاجه في هذه الخطوة، وذلك باتباع الخطوات التالية:

  • انقر بزر الفأرة الأيسر على المكعب لتحديده، لاحظ ظهور حد برتقالي حول المكعب.
  • ادخل وضع العرض ثلاثي الأبعاد.
  • اضغط على الرمز X لحذف المكعب، ستظهر لك رسالة تأكيد الحذف.

  • انقر على خيار الحذف Delete أو اضغط على مفتاح Enter.

يمكنك أيضًا إضافة كائنات للمشهد باستخدام قائمة الإضافة المنسدلة Add، وذلك بالضغط على خيار الشبكة Mesh من القائمة، ثم اختيار القرد Monkey.

لاحظ ظهور القرد على الشاشة:

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

إضافة قبعة الاحتفال

حان الآن وقت إضافة قبعة احتفال للقرد، سنستخدم لذلك مخروطًا، باتباع الخطوات التالية:

  • أضف مخروطًا Cone من خيار الشبكة Mesh من قائمة Add، وبهذا يصبح لديك مخروط وقرد في المشهد.

حان الآن وقت وضع المخروط فوق القرد. بدايةً، كبر الصورة لتحصل على رؤية أفضل لكلا المجسمين.

  • حدد المخروط بالضغط عليه بزر الفأرة الأيسر. استخدم أداة التحريك، ثم المقابض الثلاثة لوضع المخروط على رأس القرد، قرّب وكبر الصورة إن احتجت لذلك.

  • تحقق من زوايا مختلفة للتأكد من أن المخروط موضوع بشكل مناسب.

  • صيّر المجسم لترى كيف سيبدو:

لاحظ بعد التصيير أن الصورة غير مضاءة بشكل كافٍ. لا تنسى الضغط على مفتاح ESC للخروج من وضع التصيير.

تعديل الإضاءة

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

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

  • أضف المزيد من الكائنات من علامة التبويب إنشاء Create من شريط الأدوات.
  • حرك الكائنات لخلق مشهد مختلف.
  • أضف كرة ديسكو لإتمام مشهد الحفلة.
  • أضف حافة إلى قبعة الحفلة.
ختامًا

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

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

ترجمة -وبتصرف- للمقال Party Monkey من الموقع الرسمي لراسبيري باي.

اقرأ أيضًا
77,294 كيف تنشئ قوالب ألوان خاصة بك في أدوبي إليستريتور

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

أنماط الألوان المختلفة

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

1. أنماط الألوان المماثلة Analogous Colors

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

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

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

2. الأنماط أحادية اللون Monochromatic colors

يركز هذا النمط من الألوان على درجة لونيّة واحدة فقط مع تعديل قيم التشبع والسطوح، ولذلك يُعَد إنشاؤه في غاية السهولة، كما أنه قادر على إنتاج قوالب لونيّة رائعة، ولكن مشكلة هذا النمط أنه قد يكون مملًا إذا لم تُحسن التعامل معه. مع ذلك، يمكنك إضافة لون محايد، مثل الأبيض أو الأسود، للتغلب على هذه المشكلة وإنشاء قوالب لونيّة جذابة في أدوبي إليستريتور.

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

3. أنماط الألوان المكملة

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

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

كيف تصنع قالب لوني باستخدام صورة

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

1. افتح الصورة في أدوبي إليستريتور

افتح برنامج إليستريتور واستورد الصورة، إمّا من خلال فتحها باستخدام البرنامج، أو باستخدام طريقة السحب والإفلات. غيّر اسم الصورة إلى خلفيّة Background أو طبقة واحد Layer One، ثم أقفلها باستخدام زر الإقفال في قائمة الطبقات Layers Menu الموجودة في جانب الشاشة.

2. أنشئ المربع الأول

الآن أضف طبقةً جديدةً من خلال النقر على زر Add a new layer الموجود في أسفل لوحة الطبقات Layers Panel. بعد ذلك، غيّر لون الحد Stroke إلى لا شيء None ولون التعبئة Fill color إلى أي درجة في الوقت الحالي. بعد الانتهاء من ذلك، استخدم أداة المستطيل من خلال الضغط على اختصارها في لوحة المفاتيح M، أو اختيارها من صندوق الأدوات الموجود على يسار الشاشة. الآن اضغط زري Shift+Alt من لوحة المفاتيح مع السحب لإنشاء مربع صغير.

3. أنشئ مربعات متكررة

باستخدام الاختصار v في لوحة المفاتيح، فعّل أداة التحديد Selection tool واضغط على زري Shift+Alt في لوحة المفاتيح لنسخ المربع. يمكنك الآن استخدام زري Ctrl+D لتكرار المربع بقدر ما تشاء.

4. استخدم أداة القطارة للحصول على عينات

بعد تجهيز مربعات العيّنات، اضغط على المربع الأول، ثم اضغط زر I لتفعيل أداة القطارة Eyedropper tool، ثم استخدمها لاختيار اللون الأوّل في القالب.

5. كرر ذات العملية في باقي العينات

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

6. أنشئ مجموعة لونية جديدة

اضغط الزر V من لوحة المفاتيح لتفعيل أداة التحديد، ومن ثم اسحب الصندوق فوق جميع العيّنات التي أنشأتها. انقر الآن على رمز لوحة العيّنات Swatches panel وانقر من اللوحة على الأيقونة الخاصة بإضافة مجموعة لونيّة جديدة، بعد ذلك سوف تظهر لك شاشة تسمح لك بتسميّة المجموعة اللونيّة لو أردت. بعد الانتهاء من اختيار الاسم، انقر زر موافق OK. الآن يمكنك رؤية المجموعة اللونيّة الجديدة في لوحة العيّنات، وبذلك تكون قد نجحت في إنشاء مجموعة لونيّة لاستخدامها في مشروعك. مع ذلك، إذا فتحت مشروعًا جديدًا أو أغلقت البرنامج، فسوف تفقد هذه المجموعة، ولتجنّب هذه المشكلة، انتقل إلى الخطوة التالية.

7. احفظ العينة

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

8. افتح مشروعا جديدا لتشاهد قالبك الجديد

عندما تفتح مشروعًا جديدًا، انتقل إلى لوحة العيّنات وانقر على القائمة الخاصّة بمكتبة العيّنات. انزل في القائمة إلى خيار User Defined، والذي يمكّنك من الوصول إلى جميع العينات التي حفظتها سابقًا في ذلك الموقع. انقر على العينة التي ترغب باستخدامها ووسع النافذة، وسوف تجد عينتك الجديدة في الأسفل. انقر الآن على أيقونة المجلد الرمادي الموجودة على اليسار، ومن ثم اسحبها إلى لوحة العيّنات، وبهذا لقد أصبحت عينتك جاهزة الآن للاستخدام في أي مشروع.

إنشاء قالب لوني باستخدام عينة أو اثنتين

ثمة طريقة أخرى لإنتاج قوالب لونيّة مذهلة دون الحاجة إلى الاعتماد على الصور وانتقاء الألوان منها:

1. ابدأ مشروع جديدا

افتح مجلدًا جديدًا في برنامج أدوبي إليستريتور

2. أنشئ مربع عينة واحد

بعد ذلك، أنشئ مربعًا أو دائرة -أيًا كان ما تريده- باستخدام الأداة الملائمة من صندوق الأدوات الموجود على يسار الشاشة. اضغط زري Alt+Shift من لوحة المفاتيح لاختيار الحجم الملائم للشكل.

3. اختر لونا أساسيا لتعبئة العينة

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

4. كرر الصندوق

اضغط على زري Alt+Shift معًا، مع سحب الصندوق لإنشاء صندوق جديد على يمين الصندوق الأوّل. استخدم لونًا أغمق في هذا الصندوق.

5. اختر كلا الصندوقين

حدد الصندوقين معًا وانقر على الشكل، ثم امزجهما Blend، وانقر على خيارات المزج Blend Options لتظهر لك شاشة منبثقة. غيّر إعدادات التباعد من Smooth Color إلى Specified Steps، ومن ثم حدد القيمة العدديّة لعدد الخطوات التي ترغب بها بين الخطوة الأولى والخطوة الأخيرة. سوف تلاحظ عدم تغيّر شيء حتى الآن. ببساطة، عد إلى Objects واختر Make، لتنتج تدرجًا لونيًا من الأفتح إلى الأغمق. بعد ذلك، توجه إلى Objects من جديد، وانقر Expand، ثم اختر Fill in Stroke واضغط Ok، لتصبح قادرًا على التنقل بين الدرجات المختلفة كونها صناديقًا مختلفة.

6. جرب ألوانا مختلفة

لقد نجحت الآن في إنتاج قالب لوني متدرج باستخدام لون واحد أو تدرج من لونين. مع ذلك، إذا كنت ترغب بتدرج من لونين، فيمكنك النقر على المربع الأول وتغيير لون تعبئته إلى لون آخر، وسوف يتغيّر لون القالب بالكليّة بحسب المزيج بين لون الصندوق الأول والصندوق الأخير، ويمكنك فعل ذات الأمر في الصندوق الأخير كذلك.

7. أنشئ مجموعة لونية جديدة

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

8. احفظ قالبك لاستخدامه في المستقبل

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

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

ترجمة -وبتصرّف- للمقال Tips for Creating Perfect Color Templates in Adobe Illustrator لصاحبه Harsh Raval.

اقرأ أيضًا

الصفحات

أنت هنا