خادم ويب بسيط ل SPA / PWA في 5 دقائق

كيفية إنشاء خادم ويب بسيط باستخدام إرشادات nodejs القياسية فقط


غالبًا ما يتطلب الأمر خادم ويب بسيطًا لتطوير تطبيقات MPA / SPA / PWA. مرة واحدة ، في حشد كبير رداً على السؤال: "ماذا كنت تفعل؟" ، قلت أنني كنت أرفع خادم ويب لاستضافة تطبيق PWA. ضحكنا جميعا لفترة طويلة ونعم ، بالمناسبة ، PWA ليس الغراء. مثل SPA ، ليس صالون تجميل. هذه هي جميع أنواع تطبيقات الويب. و SSR ليس بلدًا :-). إذا قمت بتشغيل مثل هذا التطبيق ببساطة عن طريق فتح صفحة البدء في index.html من خلال متصفح ، فلن يعمل كما ينبغي ، وفي أفضل الحالات ، سنحصل على إصدار غير متصل بالإنترنت. أحب JavaScript وسأحل المشكلة باستخدام الوسائل المتاحة لي فقط ، إذا جاز التعبير.


لنبدأ بالخطة:


  1. إذا لم يكن هناك NodeJS ، قم بتنزيل LTS ، قم بتثبيت ، لا تقم بتغيير الإعدادات ، انقر فوق "التالي"
  2. في مكاننا المعزول ، حيث يتم جمع كل المشاريع ، قم بإنشاء مجلد خادم ويب بسيط
  3. في مجلد المشروع ، قم بتنفيذ الأمر npm init - نعم // بدون - نعم ، سوف يقوم المُهيئ بطرح الكثير من الأسئلة
  4. في ملف package.json في قسم البرامج النصية ، أضف الخاصية وقيمتها - "main": "index.js" - حتى نتمكن من بدء تشغيل خادمنا بسرعة باستخدام أمر تشغيل npm
  5. إنشاء مجلد lib يوصى بوضع كل التعليمات البرمجية فيه ، والذي لا يتطلب التجميع وخطوات إضافية لتشغيله
  6. قم بإنشاء ملف index.js في مجلد lib ، وهذا هو خادمنا المستقبلي.
  7. إنشاء مجلد dist - سيكون هذا هو المجلد الذي سيكون فيه ملفات يمكن الوصول إليها بشكل عام ، بما في ذلك index.html ، وبمعنى آخر ، الثابت الذي سيقوم خادمنا بتوزيعه
  8. افتح الملف /index.js
  9. اكتب بعض الكود

إذن ما الذي نعرفه حول ما يجب أن يفعله خادمنا؟


  1. التعامل مع الطلبات
  2. قراءة الملفات
  3. الرد على الطلب مع محتويات الملف

أولاً ، أنشئ خادمنا ، واستورده إلى ملف idex.js


const {createServer} = require('http'); 

هذا الإرشادات يدمر كائن الوحدة النمطية http ويعين تعبيرًا ، دالة createServer ، لمعرف متغير createServer .


قم بإنشاء خادم جديد باستخدام العبارة التالية


  const server = createServer(); 

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


  const eventsEmitter = server.listen(3000); 

سيكون تعبير هذه الطريقة كائن EventEmitter الذي سيتم تخزينه في متغير مع أحداث معرف. هذا الكائن يمكن ملاحظته. نحن نشترك في أحداثها باستخدام استدعاء الأسلوب on / addEventListener مع معلمتين لوظيفة سلسلة مطلوبة. تشير المعلمة الأولى إلى الأحداث التي تهمنا ؛ اطلب الثانية هي وظيفة ستقوم بمعالجة هذا الحدث.


  eventsEmitter.on('request', (req, res) => { debugger; }); 

افتح الرابط في المتصفح


صورة


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


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


دعونا نلقي نظرة على متصفحنا مرة أخرى: نرى أننا لم نشير إلى هذا صراحة. دعونا نحاول مرة أخرى ، لكننا سنحدد بالفعل index.html بشكل صريح.


صورة


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


  eventsEmitter.addListener('request', ({url: requestUrl}, res) => { debugger }); 

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


  const {extname} = require('path'); 

يمكنك الآن تسميتها بتمرير تعبير معرف معرف requestUrl كمعلمة والحصول على تعبير سلسلة التنسيق التقريبي '.extension' . إذا لم يحدد الطلب ملفًا بشكل صريح ، فسيتم إرجاع سلسلة فارغة. باستخدام هذا المبدأ ، سنضيف القيمة الافتراضية لـ "index.html" . نكتب التعليمات التالية


  const url = extname(requestUrl) === '' ? DEFAULT_FILE_NAME : requestUrl; 

أنا متأكد من أن مستخدم الخادم سيرغب في تجاوز هذا الاسم وأيضًا إعداد متغير بيئة باستخدام العبارة التالية


  const {env: {DEFAULT_FILE_NAME = '/index.html'}} = process;` 

في عملية المتغير الشامل ، هناك الكثير من المعلومات المفيدة ، سنقوم فقط بدفن جزء منها ، على وجه الخصوص ، خاصية env التي تحتوي على جميع خصائص بيئة المستخدم وسنبحث عن DEFAULT_FILE_NAME فيها إذا لم يحددها المستخدم - نستخدم index.html بشكل افتراضي.


هام: إذا كانت قيمة خاصية البيئة DEFAULT_FILE_NAME هي أي شيء غير محدد ، فلن يعمل تعيين قيمة افتراضية. هذا يستحق التذكر ، ولكن ليس الآن ، نحن نفعل كل شيء إلى الحد الأدنى :-)

صورة


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


  const {resolve, extname} = require('path'); 

بعد ذلك ، على السطر 10 ، نكتب تعليمة ستتلقى وتحفظ المسار المطلق إلى متغير filePath ، كما أنني أيضاً "wang" مقدماً بأنه يمكن إعادة تعريف اسم هذا المجلد للمرونة. لذلك ، أقوم بتوسيع التعليمات في السطر 6 ، بإضافة الاسم
متغير البيئة DIST_FOLDER !


صورة


كل شيء جاهز الآن لقراءة الملف. يمكنك قراءة ملف بطرق مختلفة بشكل غير متزامن أو متزامن أو يمكنك استخدام التدفقات . سأستخدم الجداول: -) إنها جميلة وأكثر فاعلية ، من وجهة نظر الموارد المستهلكة. أولاً ، قم بإنشاء ملف اختبار في مجلد dist بحيث يكون هناك شيء للقراءة :-)


 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> TESTING 1,2,3... </body> </html> 

نحتاج الآن إلى وظيفة من شأنها إنشاء دفق قراءة ملف ، يتم تضمينها أيضًا في توزيع NodeJS القياسي ، نقوم باستخراجها من وحدة fs باستخدام الإرشادات التالية


  const {createReadStream} = require('fs'); 

وعلى السطر 12 في نص معالج الطلب ، نستخدم العبارة التالية


  createReadStream(filePath) 

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


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


  createReadStream(filePath).pipe(res); 

صورة


هل هذا كل شيء؟ NOOOO. ومن سيتعامل مع الأخطاء؟ أي نوع من الأخطاء؟ دعنا نحاول تحميل ملف css إلى ملف index.html ، لكننا لن ننشئه ونرى ما سيحدث :-)


 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="index1.css"> </head> <body> TESTING 1,2,3... </body> </html> 

صورة


المتوقع ، ولكن الخادم تحطمت! هذا ليس هو الحال على الإطلاق. الحقيقة هي أنه لا يتم اكتشاف الأخطاء بشكل افتراضي في التدفقات وتحتاج إلى القيام بذلك بنفسك :-) createReadStream بإرجاع الدفق الذي يحدث فيه الخطأ. لذلك ، نضيف معالج خطأ. باستخدام استدعاء الأسلوب on . تحديد اسم حدث الخطأ ووظيفة المعالج. سيتم إنهاء دفق قراءة القراءة مع رمز استجابة 404.


  createReadStream(filePath) .on('error', error => res.writeHead(404).end()) .pipe(res); 

تحقق!


صورة


شيء اخر بالمناسبة ، الخادم ليس جاهزًا بعد وإذا حاولنا فتحه في متصفح آخر ، فلن تعمل الصفحة بشكل صحيح :-) من خمنها ، يرجى الكتابة في التعليقات ، ماذا نسيت أن تفعل؟ الحقيقة هي أنه عندما يجيب الخادم على طلب خادم بملف ، فإن إضافة واحدة لا تكفي للمتصفح لفهم نوع هذا الملف والمتصفحات الأخرى: لن تعمل الإصدارات chrome أو الأقدم مع الملفات التي تم تنزيلها دون تحديد رأس استجابة Content-Type. معالجة الملف ، من بين أشياء أخرى ، يجب أن يحدد خادمنا نوع MIME ، وللقيام بذلك ، سنقوم بإنشاء متغير منفصل مع جميع أنواع مايو الشائعة. نوفر أيضًا الفرصة لتوسيعها بالمرور كمتغير بيئة


  const {env: {DEFAULT_FILE_NAME = '/index.html', DIST_FOLDER = 'dist', DEFAULT_MIME_TYPES = '{}'}} = process; const {text} = mimeTypes = { 'html': 'text/html', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'png': 'image/png', 'js': 'text/javascript', 'css': 'text/css', 'text': 'plain/text', 'json': 'application/json', ...JSON.parse(DEFAULT_MIME_TYPES) }; 

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


  const fileExtension = extname(url).split('.').pop(); 

وبمساعدة معالج أحداث توجيه الإخراج ، قمنا بتعيين نوع MIME المطلوب


 res.on('pipe', () => res.setHeader(contentType, mimeTypes[fileExtension] || text)); 

مراجعة


صورة


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


رمز المشروع الكامل



Source: https://habr.com/ru/post/ar481898/


All Articles