اذهب إلى المحتوى

التعامل مع الطلبيات الشبكية في Node.js


Ola Abbas

سنتعرّف من خلال هذا المقال على طريقة إرسال واستقبال الطلبيات بين الخادم والعميل عبر الشبكة في Node.js باستخدام مكتبة Axios، ولكن سنبدأ بشرح وسيلة التواصل الأساسية بين الخادم والمتصفح وهو بروتوكول HTTP، وسنتعرّف على بديل اتصال HTTP في تطبيقات الويب الذي هو مقابس الويب WebSockets.

كيفية عمل بروتوكول HTTP

يُعَدّ بروتوكول نقل النص الفائق Hyper Text Transfer Protocol -أو HTTP اختصارًا- أحد بروتوكولات تطبيق TCP/IP وهي مجموعة البروتوكولات التي تشغّل شبكة الإنترنت، إذ يُعَدّ البروتوكول الأنجح والأكثر شعبية على الإطلاق، كما يُشغِّل هذا البروتوكول شبكة الويب العالمية World Wide Web، مما يمنح المتصفحات لغة للتواصل مع الخوادم البعيدة التي تستضيف صفحات الويب.

وُحِّد بروتوكول HTTP لأول مرة في عام 1991على أساس نتيجة لعمل تيم بيرنرز لي Tim Berners-Lee في المركز الأوروبي للأبحاث النووية European Center of Nuclear Research -أو CERN اختصارًا- منذ عام 1989، وكان الهدف هو السماح للباحثين بتبادل أبحاثهم بسهولة وربطهم ببعضهم بعضًا على أساس وسيلة تحسِّن عمل المجتمع العلمي، كما تكوّنت تطبيقات الإنترنت الرئيسية في ذلك الوقت من بروتوكول FTP أي بروتوكول نقل الملفات File Transfer Protocol والبريد الإلكتروني ونظام يوزنت Usenet أي مجموعات الأخبار newsgroups، ولكنها أصبحت غير مُستخدَمة حاليًا تقريبًا.

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

عُدِّل بروتوكول HTTP تعديلًا ثانويًا في عام 1997 في الإصدار HTTP/1.1، وخلفه الإصدار HTTP/2 الذي وُحِّد في عام 2015 ويُطبَّق الآن على خوادم الويب الرئيسية المُستخدَمة في جميع أنحاء العالم، كما يُعَدّ بروتوكول HTTP غير آمن مثل أيّ بروتوكول آخر غير مخدَّم عبر اتصال مشفَّر مثل بروتوكولات SMTP وFTP وغيرها، وهذا هو السبب في التوجّه الكبير حاليًا نحو استخدام بروتوكول HTTPS، وهو بروتوكول HTTP مخدَّم عبر بروتوكول TLS، ولكن بروتوكول HTTP هو حجر الأساس لبروتوكول HTTP/2 وHTTPS.

مستندات HTML

بروتوكول HTTP هو الطريقة التي تتواصل بها متصفحات الويب web browsers مثل Chrome وFirefox وEdge ومتصفحات أخرى سنسمّيها عملاء clients مع خوادم الويب web servers، كما اُشتق الاسم بروتوكول نقل النص الفائق Hyper Text Transfer Protocol من الحاجة إلى نقل الملفات كما هو الحال في بروتوكول FTP والذي يشير إلى بروتوكول نقل الملفات File Transfer Protocol، بالإضافة إلى النصوص الفائقة hypertexts التي ستُكتَب باستخدام لغة HTML، ثم تُمثَّل رسوميًا باستخدام المتصفح مع عرض جميل وروابط تفاعلية، وساهمت الروابط بقوة في اعتماد بروتوكول HTTP إلى جانب سهولة إنشاء صفحات ويب جديدة، حيث ينقل هذا البروتوكول ملفات النصوص الفائقة بالإضافة إلى الصور وأنواع الملفات الأخرى عبر الشبكة.

الروابط والطلبيات

يمكن أن يؤشّر مستند إلى مستند آخر باستخدام الروابط ضمن متصفح الويب، حيث يحدِّد جزء الرابط الأول كل من البروتوكول وعنوان الخادم من خلال إما اسم نطاق domain name أو عنوان IP، وليس هذا الجزء خاصًا ببروتوكول HTTP؛ أما الجزء الثاني فهو جزء المستند الذي يتبع جزء العنوان ويمثِّل مسار المستند مثل https://flaviocopes.com/http/‎ الذي يتكوّن مما يلي:

  • https هو البروتوكول.
  • flaviocopes.com هو اسم النطاق الذي يؤشر إلى الخادم.
  • /http/ هو عنوان URL النسبي للمستند إلى مسار الخادم الجذر.

يمكن أن يتداخل المسار مثل https://academy.hsoub.com/files/c5-programming/‎، حيث يكون عنوان URL للمستند هو ‎/files/c5-programming؛ أما خادم الويب فيُعَدّ مسؤولًا عن تفسير الطلب وتقديم الاستجابة الصحيحة بعد تحليل الطلب، كما يمكن أن يكون الطلب عنوان URL الذي رأيناه سابقًا، فإذا أدخلنا عنوانًا وضغطنا Enter من لوحة المفاتيح في المتصفح، فسيرسل الخادم طلبًا في الخلفية إلى عنوان IP الصحيح مثل الطلب التالي:

GET /a-page

حيث ‎‎‎‎/a-page‎ هو عنوان URL الذي طلبته، كما يمكن أن يكون الطلب تابع HTTP ويُسمّى فعلًا verb أيضًا، حيث حدّد بروتوكول HTTP سابقًا ثلاثةً من هذه التوابع وهي:

  • GET.
  • POST.
  • HEAD.

وقدّم الإصدار HTTP/1.1 التوابع:

  • PUT.
  • DELETE.
  • OPTIONS.
  • TRACE.

سنتحدّث عنها لاحقًا، وقد يكون الطلب مجموعة ترويسات HTTP، فالترويسات Headers هي مجموعة من أزواج المفتاح-القيمة key: value التي تُستخدَم للتواصل مع المعلومات الخاصة بالخادم المُحدَّدة مسبقًا ليتمكّن الخادم من فهم ما نعنيه، كما أنّ جميع الترويسات اختيارية باستثناء الترويسة Host.

توابع HTTP

أهم توابع HTTP هي:

  • GET: هو التابع الأكثر استخدامًا، وهو الخيار الذي يُستخدَم عند كتابة عنوان URL في شريط عنوان المتصفح، أو عند النقر على رابط، كما يطلب هذا التابع من الخادم إرسال المورد المطلوب على أساس استجابة.
  • HEAD: يتشابه هذا التابع مع التابع GET تمامًا، ولكن HEAD يخبر الخادم بعدم إرسال جسم الاستجابة response body، بل إرسال الترويسات فقط.
  • POST: يستخدِم العميل هذا التابع لإرسال البيانات إلى الخادم، حيث يُستخدَم عادةً في النماذج forms مثلًا، وعند التفاعل مع واجهة برمجة تطبيقات REST.
  • PUT: يهدف هذا التابع إلى إنشاء مورد في عنوان URL المحدَّد باستخدام المعاملات المُمرَّرة في جسم الطلب، كما يُستخدم استخدامًا رئيسيًا في واجهات برمجة تطبيقات REST.
  • DELETE: يُستدعَى هذا التابع مع عنوان URL لطلب حذف المورد المقابل لهذا العنوان، كما يُستخدَم استخدامًا رئيسيًا في واجهات برمجة تطبيقات REST.
  • OPTIONS: يجب أن يرسِل الخادم قائمة توابع HTTP المسموح بها إلى عنوان URL المحدَّد عندما يتلقى طلب OPTIONS.
  • TRACE: يعيد هذا التابع إلى العميل الطلب المُستلَم، حيث يُستخدَم هذا التابع لتنقيح الأخطاء debugging أو لأغراض التشخيص.

اتصال HTTP خادم/عميل

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

يُعَدّ بروتوكول HTTP مرنًا واتصاله سريعًا جدًا اعتمادًا على حِمل الشبكة، وهذا يتناقض مع البروتوكولات الأكثر استخدامًا في وقت صدوره مثل TCP وPOP/SMTP وبروتوكولات البريد التي تتضمن كثيرًا من عمليات المصافحة handshaking والتأكيدات على النهايات المُستقبَلة، كما تجرِّد المتصفحات الرسومية هذا الاتصال، ولكن يمكن توضيحه كما يلي، إذ يبدأ سطر الرسالة الأول بتابع HTTP ثم مسار المَورد النسبي وإصدار البروتوكول كما يلي:

GET /a-page HTTP/1.1

ثم يجب إضافة ترويسات طلبات HTTP، إذ توجد هناك ترويسات متعددة، ولكن الترويسة الإلزامية الوحيدة هي Host:

GET /a-page HTTP/1.1
Host: flaviocopes.com

يمكنك اختبار ذلك باستخدام أداة telnet، وهي أداة سطر أوامر تتيح لنا الاتصال بأي خادم وإرسال الأوامر إليه، والآن افتح طرفيتك terminal واكتب telnet flaviocopes.com 80 مثلًا، حيث سيؤدي ذلك إلى فتح طرفية تعرض ما يلي:

Trying 178.128.202.129...
Connected to flaviocopes.com.
Escape character is '^]'.

أنت الآن متصل بخادم الويب Netlify، ثم اكتب ما يلي:

GET /axios/ HTTP/1.1
Host: flaviocopes.com

اضغط بعد ذلك على زر Enter في سطر فارغ لتشغيل الطلب، وستكون الاستجابة كما يلي:

HTTP/1.1 301 Moved Permanently
Cache-Control: public, max-age=0, must-revalidate
Content-Length: 46
Content-Type: text/plain
Date: Sun, 29 Jul 2018 14:07:07 GMT
Location: https://flaviocopes.com/axios/
Age: 0
Connection: keep-alive
Server: Netlify
Redirecting to https://flaviocopes.com/axios/

وهذه هي استجابة HTTP التي حصلنا عليها من الخادم، وهي طلب 301‎ Moved Permanently الذي يخبرنا بانتقال المَورد إلى موقع آخر انتقالًا دائمًا، وذلك لأننا اتصلنا بالمنفذ 80 وهو المنفذ الافتراضي لبروتوكول HTTP، ولكننا ضبطنا الخادم على إعادة التوجيه التلقائي إلى HTTPS، كما حُدِّد الموقع الجديد في ترويسة استجابة HTTP التي هي Location، وهناك ترويسات استجابة أخرى سنتحدث عنها لاحقًا، كما يفصل سطر فارغ ترويسة الطلب عن جسمه في كل من الطلب والاستجابة، حيث يحتوي جسم الطلب في مثالنا على السلسلة النصية التالية:

Redirecting to https://flaviocopes.com/axios/

يبلغ طول هذه السلسلة النصية 46 بايتًا كما هو محدَّد في ترويسة Content-Length، إذ تظهر هذه السلسلة في المتصفح عند فتح الصفحة ريثما يُعاد توجيهك إلى الموقع الصحيح تلقائيًا، كما نستخدم أداة telnet في مثالنا، وهي أداة منخفضة المستوى يمكننا استخدامها للاتصال بأي خادم، لذلك لا يمكننا الحصول على أي نوع من إعادة التوجيه التلقائي، فلنتصل الآن بالمنفذ 443 وهو المنفذ الافتراضي لبروتوكول HTTPS، حيث لا يمكننا استخدام أداة telnet بسبب مصافحة SSL التي يجب أن تحدث،فولنستخدم الآن أداة curl وهي أداة سطر أوامر أخرى، إذ لا يمكننا كتابة طلب HTTP مباشرةً، لكننا سنرى الاستجابة:

curl -i https://flaviocopes.com/axios/

سنحصل في المقابل على ما يلي:

HTTP/1.1 200 OK
Cache-Control: public, max-age=0, must-revalidate
Content-Type: text/html; charset=UTF-8
Date: Sun, 29 Jul 2018 14:20:45 GMT
Etag: "de3153d6eacef2299964de09db154b32-ssl"
Strict-Transport-Security: max-age=31536000
Age: 152
Content-Length: 9797
Connection: keep-alive
Server: Netlify

<!DOCTYPE html>
<html prefix="og: http://ogp.me/ns#" lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>HTTP requests using Axios</title>
....

لن ينقل خادم HTTP ملفات HTML فقط، وإنما يمكنه نقل ملفات أخرى مثل ملفات CSS و JS و SVG و PNG و JPG وأنواع ملفات متعددة أخرى، إذ يعتمد ذلك على الإعداد، فبروتوكول HTTP قادر تمامًا على نقل هذه الملفات، وسيعرف العميل نوع الملف، وبالتالي سيفسرها بالطريقة الصحيحة، وهذه هي الطريقة التي يعمل بها الويب عند استرداد صفحة HTML بواسطة المتصفح، إذ تُفسَّر هذه الصفحة وأي مَورد آخر يحتاجه المتصفح لعرض خاصية (CSS و JavaScript والصور وغير ذلك) مُسترَدّة عبر طلبات HTTP إضافية إلى الخادم نفسه.

بروتوكول HTTPS والاتصالات الآمنة

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

قد تكون العواقب وخيمةً، فقد يراقب ويسجّل طرف ثالث كل أنشطة شبكتك دون علمك، وقد تحقن بعض الشبكات إعلانات، وقد تكون عرضةً لهجوم الوسيط man-in-the-middle، وهو تهديد أمني يستطيع المهاجم من خلاله التلاعب ببياناتك وحتى انتحال شخصية حاسوبك عبر الشبكة، إذ يمكن لأي شخص الاستماع بسهولة إلى حزم HTTP المُرسَلة عبر شبكة واي فاي Wi-Fi عامة وغير مشفَّرة، حيث يهدف بروتوكول HTTPS إلى حل هذه المشكلة من خلال تشفير الاتصال الكامل بين متصفحك وخادم الويب.

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

يُعَدّ استخدام HTTPS إلزاميًا على جميع المواقع في الوقت الحالي، إذ يستخدِمه حاليًا أكثر من 50% من مواقع الويب، وقد بدأ Google Chrome مؤخرًا في تمييز مواقع HTTP بأنها غير آمنة، لمنحك سببًا وجيهًا في جعل بروتوكول HTTPS إلزاميًا على جميع مواقع الويب الخاصة بك.

يكون منفذ الخادم الافتراضي هو 80 عند استخدام بروتوكول HTTP، في حين يكون 443 عند استخدام بروتوكول HTTPS، وليست إضافته بصورة صريحة أمرًا إلزاميًا إذا استخدَم الخادم المنفذ الافتراضي، كما يُطلق على بروتوكول HTTPS أحيانًا اسم HTTP عبر SSL أو HTTP عبر TLS، حيث يكون بروتوكول TLS خلَفًا لبروتوكول SSL؛ أما الشيء الوحيد غير المشفَّر عند استخدام بروتوكول HTTPS، فهو نطاق خادم الويب ومنفذ الخادم، بينما تُشفَّر كل المعلومات الأخرى بما في ذلك مسار المَورد والترويسات وملفات تعريف الارتباط cookies ومعامِلات الاستعلام.

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

يتيح HTTPS استخدام أحدث بروتوكول وهو HTTP/2 الذي يحتوي على ميزة إضافية يتفوق بها على الإصدار HTTP/1.1، وهذه الميزة هي أنه أسرع لعدة أسباب مثل ضغط الترويسة وتعدُّد الموارد، كما يمكن للخادم زجّ مزيد من الموارد عند طلب أحدها، فإذا طلب المتصفح صفحةً، فسيتلقى جميع الموارد اللازمة مثل الصور وملفات CSS وJS، كما يُعَدّ HTTP/2 تحسّنًا كبيرًا على HTTP/1.1 ويتطلب بروتوكول HTTPS، وهذا يعني أنّ HTTPS أسرع من HTTP بكثير إذا ضُبِط كل شيء ضبطًا صحيحًا باستخدام إعداد حديث على الرغم من وجود عبء التشفير الإضافي.

كيفية عمل طلبات HTTP

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

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

تحليل طلبات URL

تملك المتصفحات الحديثة القدرة على معرفة ما إذا كان الشيء الذي كتبته في شريط العناوين هو عنوان URL فعلي أو مصطلح بحث، حيث سيستخدم المتصفح محرّك البحث الافتراضي إذا لم يكن عنوان URL صالحًا، فلنفترض أنك كتبتَ عنوان URL فعليًا، حيث ينشئ المتصفح أولًا عنوان URL الكامل عند إدخال العنوان ثم الضغط على مفتاح Enter، فإذا أدخلت نطاقًا مثل flaviocopes.com، فسيضيف المتصفح إلى بدايته HTTP://‎ افتراضيًا اعتمادًا على بروتوكول HTTP.

اقتباس

توضيح: يجب عليك معرفة أنّ نظام ويندوز Windows قد يطبّق بعض الأشياء بطريقة مختلفة قليلًا عن نظامَي macOS ولينكس Linux.

مرحلة بحث DNS

يبدأ المتصفح عملية بحث DNS للحصول على عنوان IP الخادم، ويُعَدّ اسم النطاق اختصارًا مفيدًا للبشر، ولكن الإنترنت منظَّم بطريقة تمكّن الحواسيب من البحث عن موقع الخادم الدقيق من خلال عنوان IP الخاص به، وهو عبارة عن مجموعة من الأعداد مثل 222.324.3.1 في الإصدار IPv4، حيث يتحقق المتصفح أولًا من ذاكرة DNS المخبئية المحلية، للتأكد من أن النطاق قد جرى تحليله resolved مؤخرًا، كما يحتوي كروم Chrome على عارض مفيد لذاكرة DNS المخبئية الذي يمكنك رؤيته من خلال chrome://net-internals/#dns، فإذا لم تعثر على أي شيء هناك، فهذا يعني استخدام المتصفح محلّل DNS عن طريق استدعاء نظام ‎gethostbyname POSIX لاسترداد معلومات المضيف.

gethostbyname

يبحث استدعاء النظام gethostbyname أولًا في ملف المضيفِين hosts المحلي، والذي يوجد في نظامَي macOS أو لينكس Linux ضمن ‎/etc/hosts، للتأكد من أن النظام يوفِّر المعلومات محليًا، فإذا لم يقدّم ملف المضيفِين المحلي أيّ معلومات عن النطاق، فسيقدّم النظام طلبًا إلى خادم DNS، حيث يُخزَّن عنوان خادم DNS في تفضيلات النظام، كما يُعَدّ الخادمان التاليان خادمي DNS شهيرين:

  • 8.8.8.8: خادم DNS العام الخاص بجوجل.
  • 1.1.1.1: خادم CloudFlare DNS.

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

قد يحتوي خادم DNS على عنوان IP النطاق في الذاكرة المخبئية، فإذا لم يكن كذلك، فسيسأل خادم DNS الجذر، إذ يتكون هذا النظام من 13 خادم حقيقي موزع في أنحاء العالم، حيث يقود هذا النظام شبكة الإنترنت بأكملها، كما لا يعرف خادم DNS عنوان كل اسم نطاق على هذا الكوكب، ولكن يكفي معرفة مكان وجود محلّلي DNS من المستوى الأعلى، إذ يُعَدّ نطاق المستوى الأعلى top-level domain امتداد النطاق مثل ‎.com و‎.it و‎.pizza وغير ذلك.

يَعيد خادم DNS توجيه الطلب عند تلقّيه إلى خادم DNS الخاص بنطاق المستوى الأعلى TLD، ولنفترض أنك تبحث عن موقع flaviocopes.com، حيث يعيد خادم DNS الخاص بالنطاق الجذر عنوان IP الخاص بخادم نطاق المستوى الأعلى ‎.com، ويخزّن بعدها محلّل DNS الخاص بنا عنوان IP لخادم نطاق المستوى الأعلى، بحيث لا يتعيّن عليه أن يسأل خادم DNS الجذر مرةً أخرى عنه.

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

  • ns1.dreamhost.com
  • ns2.dreamhost.com
  • ns3.dreamhost.com

يبدأ محلل DNS بالخادم الأول، ويحاول طلب عنوان IP الخاص بالنطاق مع النطاق الفرعي أيضًا الذي تبحث عنه، وهو المصدر النهائي لعنوان IP.

إنشاء اتصال/مصافحة handshaking طلب TCP

يمكن للمتصفح الآن بدء اتصال TCP عند توفر عنوان IP الخادم، حيث يتطلب اتصال TCP عملية مصافحة handshaking قبل تهيئته بالكامل والبدء بإرسال البيانات، إذ يمكننا إرسال الطلب بعد إنشاء الاتصال.

إرسال الطلب

يكون الطلب عبارةً عن مستند نصي منظَّم بطريقة دقيقة يحدّدها بروتوكول الاتصال، ويتكون من 3 أجزاء هي:

  • سطر الطلب request line.
  • ترويسة الطلب request header.
  • جسم الطلب request body.

يضبط سطر الطلب ما يلي في سطر واحد:

  • تابع HTTP.
  • موقع المَورد.
  • إصدار البروتوكول.

إليك المثال التالي:

GET / HTTP/1.1

تتكون ترويسة الطلب من مجموعة من أزواج الحقل-القيمة field: value التي تحدِّد قيمًا معينةً، وهناك حقلان إلزاميان هما Host وConnection، بينما جميع الحقول الأخرى اختيارية:

Host: flaviocopes.com
Connection: close

يشير الحقل Host إلى اسم النطاق الذي نريد الوصول إليه، بينما يُضبَط الحقل Connection على القيمة close دائمًا إلّا في حالة إبقاء الاتصال مفتوحًا، وبعض حقول الترويسة الأكثر استخدامًا هي:

  • Origin
  • Accept
  • Accept-Encoding
  • Cookie
  • Cache-Control
  • Dnt

وهناك غيرها الكثير، ويُنهَى جزء الترويسة بسطر فارغ.

أما جسم الطلب فهو اختياري ولا يُستخدَم في طلبات GET، ولكنه يُستخدَم بكثرة في طلبات POST وفي أفعال أخرى في بعض الأحيان، كمايمكن أن يحتوي على بيانات بتنسيق JSON، وبما أننا الآن نحلّل طلب GET، فإن الجسم فارغ.

الاستجابة Response

يعالِج الخادم الطلب بعد إرساله ويرسل استجابةً، حيث تبدأ الاستجابة برمز الحالة status code ورسالة الحالة status message، فإذا كان الطلب ناجحًا ويعيد القيمة 200، فستبدأ الاستجابة بما يلي:

200 OK

قد يعيد الطلب رمز ورسالة حالة مختلفَين مثل الأمثلة التالية:

404 Not Found
403 Forbidden
301 Moved Permanently
500 Internal Server Error
304 Not Modified
401 Unauthorized

تحتوي الاستجابة بعد ذلك على قائمة بترويسات HTTP وجسم الاستجابة الذي سيكون HTML لأننا ننفّذ الطلب في المتصفح.

تحليل HTML

تلقّى المتصفح الآن ملف HTML وبدأ في تحليله، وسيكرّر العملية نفسها بالضبط على جميع الموارد التي تطلبها الصفحة مثل:

  • ملفات CSS.
  • الصور.
  • الأيقونة المفضلة أو رمز الموقع favicon.
  • ملفات جافا سكريبت.
  • وغير ذلك.

الطريقة التي تصيّر render بها المتصفحاتُ الصفحةَ خارج نطاق مناقشتنا، ولكن يجب فهم أن العملية التي شرحناها غير مقتصرة على صفحات HTML فقط، بل يمكن تطبيقها على أيّ عنصر مُقدَّم عبر بروتوكول HTTP.

بناء خادم HTTP باستخدام Node.js

خادم ويب HTTP الذي سنستخدِمه هو الخادم نفسه الذي استخدمناه سابقًا مثل تطبيق Node Hello World.

const http = require('http')

const port = 3000

const server = http.createServer((req, res) => {
  res.statusCode = 200
  res.setHeader('Content-Type', 'text/plain')
  res.end('Hello World\n')
})

server.listen(port, () => {
  console.log(`Server running at http://${hostname}:${port}/`)
})

لنحلّل المثال السابق بإيجاز:

ضمّنا وحدة http التي نستخدمها لإنشاء خادم HTTP، وضُبِط الخادم للاستماع على المنفذ المحدَّد 3000، حيث تُستدعَى دالة رد النداء listen عندما يكون الخادم جاهزًا، فدالة رد النداء التي نمررها هي الدالة التي ستُنفَّذ عند وصول كل طلب، ويُستدَعى حدث request عند تلقّي طلب جديد، مما يوفّر كائنين هما: طلب (كائن http.IncomingMessage) واستجابة ( كائن http.ServerResponse).

يوفّر الطلب request تفاصيل الطلب، حيث نصل من خلاله إلى ترويسات الطلبات وبياناتها؛ أما الاستجابة response فتُستخدَم لتوفير البيانات التي سنعيدها إلى العميل، كما ضبطنا خاصية statusCode على القيمة 200 في مثالنا، للإشارة إلى استجابة ناجحة.

res.statusCode = 200

وضبطنا ترويسة Content-Type كما يلي:

res.setHeader('Content-Type', 'text/plain')

ثم أغلقنا الاستجابة في النهاية بإضافة المحتوى على أساس وسيط للتابع end()‎:

res.end('Hello World\n')

إجراء طلبات HTTP

سنشرح كيفية إجراء طلبات HTTP في Node.js باستخدام GET و POST و PUT و DELETE.

اقتباس

توضيح: سنستخدم مصطلح HTTP، ولكن HTTPS هو ما يجب استخدامه في كل مكان، لذلك تستخدِم هذه الأمثلة بروتوكول HTTPS بدلًا من HTTP.

إجراء طلب GET

const https = require('https')
const options = {
  hostname: 'flaviocopes.com',
  port: 443,
  path: '/todos',
  method: 'GET'
}

const req = https.request(options, (res) => {
  console.log(`statusCode: ${res.statusCode}`)

  res.on('data', (d) => {
    process.stdout.write(d)
  })
})

req.on('error', (error) => {
  console.error(error)
})

req.end()

إجراء طلب POST

const https = require('https')

const data = JSON.stringify({
  todo: 'Buy the milk'
})

const options = {
  hostname: 'flaviocopes.com',
  port: 443,
  path: '/todos',
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length
  }
}

const req = https.request(options, (res) => {
  console.log(`statusCode: ${res.statusCode}`)

  res.on('data', (d) => {
    process.stdout.write(d)
  })
})

req.on('error', (error) => {
  console.error(error)
})

req.write(data)
req.end()

PUT وDELETE

تستخدِم طلبات PUT وDELETE تنسيق طلب POST نفسه مع تغيير قيمة options.method فقط.

مكتبة Axios

تُعَدّ Axios مكتبة جافاسكربت يمكنك استخدامها لإجراء طلبات HTTP، وتعمل في المنصَّتين المتصفح Browser ونود Node.js.

01_Axios.png

تدعم هذه المكتبة جميع المتصفحات الحديثة بما في ذلك الإصدار IE8 والإصدارات الأحدث، كما تستند على الوعود، وهذا يتيح لنا كتابة شيفرة صيغة عدم التزامن/الانتظار async/await لإجراء طلبات XHR بسهولة، كما يتمتع استخدام مكتبة Axios ببعض المزايا بالموازنة مع واجهة Fetch API الأصيلة، وهذه المزايا هي:

  • تدعم المتصفحات القديمة، حيث تحتاج Fetch إلى تعويض نقص دعم المتصفحات polyfill.
  • لديها طريقة لإبطال طلب.
  • لديها طريقة لضبط مهلة الاستجابة الزمنية.
  • تحتوي على حماية CSRF مبنية مسبقًا.
  • تدعم تقدّم التحميل.
  • تجري تحويل بيانات JSON تلقائيًا.
  • تعمل في Node.js.

تثبيت Axios

يمكن تثبيت Axios باستخدام npm:

npm install axios

أو باستخدام yarn:

yarn add axios

أو يمكنك تضمينها ببساطة في صفحتك باستخدام unpkg.com كما يلي:

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

واجهة برمجة تطبيقات Axios

يمكنك بدء طلب HTTP من كائن axios:

axios({
  url: 'https://dog.ceo/api/breeds/list/all',
  method: 'get',
  data: {
    foo: 'bar'
  }
})

لكنك ستستخدم التوابع التالية كما هو الحال في jQuery، حيث يمكنك استخدام ‎$.get()‎ و‎$.post()‎ بدلًا من ‎$.ajax()‎:

  • axios.get()‎
  • axios.post()‎

توفِّر مكتبة Axios توابعًا لجميع أفعال HTTP، والتي تُعَدّ أقل شيوعًا ولكنها لا تزال مُستخدَمة:

  • ‎axios.delete()‎
  • axios.put()‎
  • axios.patch()‎
  • axios.options()‎
  • axios.head()‎: وهو تابع يُستخدَم للحصول على ترويسات HTTP لطلب ما مع تجاهل الجسم.

إرسال واستقبال الطلبات

إحدى الطرق الملائمة لاستخدام مكتبة Axios هي استخدام صيغة async/await الحديثة في الإصدار ES2017، حيث يستعلم مثال Node.js التالي عن واجهة Dog API لاسترداد قائمة بجميع سلالات الكلاب dogs breeds باستخدام التابع axios.get()‎، ويحصي هذه السلالات:

const axios = require('axios')

const getBreeds = async () => {
  try {
    return await axios.get('https://dog.ceo/api/breeds/list/all')
  } catch (error) {
    console.error(error)
  }
}

const countBreeds = async () => {
  const breeds = await getBreeds()

  if (breeds.data.message) {
    console.log(`Got ${Object.entries(breeds.data.message).length} breeds`)
  }
}

countBreeds()

إذا لم ترغب في استخدام صيغة async/await، فيمكنك استخدام صيغة الوعود Promises:

const axios = require('axios')

const getBreeds = () => {
  try {
    return axios.get('https://dog.ceo/api/breeds/list/all')
  } catch (error) {
    console.error(error)
  }
}

const countBreeds = async () => {
  const breeds = getBreeds()
    .then(response => {
      if (response.data.message) {
        console.log(
          `Got ${Object.entries(response.data.message).length} breeds`
        )
      }
    })
    .catch(error => {
      console.log(error)
    })
}

countBreeds()

يمكن أن تحتوي استجابة GET على معامِلات في عنوان URL مثل https://site.com/?foo=bar، حيث يمكنك تطبيق ذلك في مكتبة Axios عن طريق استخدام عنوان URL كما يلي:

axios.get('https://site.com/?foo=bar')

أو يمكنك استخدام خاصية params في الخيارات كما يلي:

axios.get('https://site.com/', {
  params: {
    foo: 'bar'
  }
})

يشبه إجراء طلب POST تمامًا إجراء طلب GET مع استخدم axios.post بدلًا من axios.get:

axios.post('https://site.com/')

الكائن الذي يحتوي على معامِلات POST هو الوسيط الثاني:

axios.post('https://site.com/', {
  foo: 'bar'
})

مقابس الويب Websockets

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

02_BrowserSupportForWebSockets.png

قد تتساءل، ما وجه الاختلاف بين WebSockets وبين HTTP؟ حسنًا، يُعَدّ HTTP بروتوكولًا وطريقة تواصل مختلفة تمامًا، فهو بروتوكول طلب/استجابة request/response، إذ يعيد الخادم البيانات التي يطلبها العميل، بينما تفيد مقابس WebSockets فيما يلي:

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

تُعَدّ مقابس WebSockets مناسبةً للاتصالات طويلة الأمد في الوقت الحقيقي، بينما يُعَدّ بروتوكول HTTP مفيدًا لتبادل البيانات والتفاعلات المؤقتة التي يبدأها العميل، كما يُعَدّ بروتوكول HTTP أبسط بكثير في التطبيق، بينما تتطلب مقابس WebSockets مزيدًا من العبء الإضافي.

مقابس الويب الآمنة

استخدم دائمًا البروتوكول الآمن والمشفّر لمقابس الويب أي wss://‎، ويشير ws://‎ إلى إصدار مقابس WebSockets غير الآمن -مثل http://‎ في مقابس WebSockets- الذي يجب تجنبه.

إنشاء اتصال WebSockets جديد

إليك المثال التالي:

const url = 'wss://myserver.com/something'
const connection = new WebSocket(url)

يُعَدّ connection كائن WebSocket، كما يُشغَّل حدث open عند إنشاء الاتصال بنجاح، ويمكنك الاستماع إلى الاتصال عن طريق إسناد دالة رد نداء callback إلى خاصية onopen الخاصة بكائن connection كما يلي:

connection.onopen = () => {
  //...
}

إذا كان هناك أي خطأ، فستُشغَّل دالة رد النداء onerror كما يلي:

connection.onerror = error => {
  console.log(`WebSocket error: ${error}`)
}

إرسال البيانات إلى الخادم باستخدام WebSockets

يمكنك إرسال البيانات إلى الخادم بمجرد فتح الاتصال، حيث يمكنك إرسال البيانات بسهولة ضمن دالة رد النداء onopen كما يلي:

connection.onopen = () => {
  connection.send('hey')
}

استقبال البيانات من الخادم باستخدام WebSockets

استمع إلى الاتصال باستخدام دالة رد النداء onmessage التي تُستدعَى عند تلقي حدث message كما يلي:

connection.onmessage = e => {
  console.log(e.data)
}

تطبيق خادم WebSockets في Node.js

تُعَدّ مكتبة ws مكتبة WebSockets شائعةً ومُستخدَمةً مع Node.js، كما سنستخدمها لبناء خادم WebSockets، ويمكن استخدامها أيضًا لتطبيق العميل مع استخدام مقابس WebSockets للتواصل بين خدمَتين من الخدمات الخلفية، كما يمكنك تثبيت هذه المكتبة بسهولة باستخدام الأمر التالي:

yarn init
yarn add ws

ليست الشيفرة التي تحتاج إلى كتابتها كبيرةً كما يلي:

const WebSocket = require('ws')

const wss = new WebSocket.Server({ port: 8080 })
wss.on('connection', ws => {
  ws.on('message', message => {
    console.log(`Received message => ${message}`)
  })
  ws.send('ho!')
})

تُنشئ الشيفرة السابقة خادمًا جديدًا على المنفذ 8080 وهو المنفذ الافتراضي لمقابس الويب WebSockets-، وتضيف دالة رد نداء عند إنشاء اتصال، مما يؤدي إلى إرسال ho!‎ إلى العميل، وتسجيل الرسائل التي يتلقاها.

شاهد مثالًا حيًا لخادم مقابس الويب WebSockets ومثالًا حيًا لعميل WebSockets يتفاعل مع الخادم على Glitch.

ترجمة -وبتصرّف- للفصل Networking من كتاب The Node.js handbook لصاحبه Flavio Copes.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...