مشروع مصغر: خادم ويب يعرض صفحة HTML مختلفة بناءً على الرابط المطلوب


مشروع مصغر: خادم ويب يعرض صفحة HTML مختلفة بناءً على الرابط المطلوب

ماذا سنبني اليوم؟ سنقوم ببناء خادم ويب بسيط باستخدام Node.js يعرض صفحات HTML مختلفة بناءً على المسار (URL) المطلوب من العميل. هذا سيعلمنا أساسيات إنشاء الخوادم والتعامل مع الطلبات المختلفة.

الخطوة 1: تهيئة المشروع وإنشاء الخادم الأساسي

في هذه الخطوة، سنقوم بإنشاء ملف مشروع جديد، ونتعلم كيفية إعداد خادم HTTP بسيط يستمع على منفذ معين. سنستخدم وحدة http المدمجة في Node.js.

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

أولاً، أنشئ ملفاً جديداً باسم server.js وأضف الكود التالي:

// استيراد وحدة http المدمجة في Node.js لإنشاء خادم الويب
const http = require('http');

// تحديد المنفذ الذي سيستمع عليه الخادم
const PORT = 3000;

// إنشاء الخادم
const server = http.createServer((req, res) => {
    // تعيين رأس الاستجابة (Content-Type) ليكون نص عادي (plain text) مؤقتاً
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    // إرسال استجابة بسيطة للعميل
    res.end('مرحباً بك في خادمنا!\n');
});

// جعل الخادم يستمع على المنفذ المحدد
server.listen(PORT, () => {
    console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الخادم يعمل على http://localhost:${PORT}</code>);
    console.log('لإيقاف الخادم، اضغط Ctrl+C');
});

شغل الخادم من سطر الأوامر: node server.js ثم افتح متصفحك على http://localhost:3000. يجب أن ترى رسالة "مرحباً بك في خادمنا!".

الخطوة 2: قراءة المسار (URL) وتوجيه الطلبات

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

الكائن req (request) يحتوي على جميع معلومات الطلب الوارد، بما في ذلك المسار (req.url) والأسلوب (req.method).

عدّل ملف server.js ليصبح كالتالي:

const http = require('http');
// استيراد وحدة url لتحليل المسارات (URLs)
const url = require('url');

const PORT = 3000;

const server = http.createServer((req, res) => {
    // تحليل المسار المطلوب من العميل
    const parsedUrl = url.parse(req.url, true);
    const path = parsedUrl.pathname; // الحصول على المسار بدون البارامترات

    // تعيين رأس الاستجابة الافتراضي ليكون HTML
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });

    // منطق التوجيه بناءً على المسار
    if (path === '/') {
        // إذا كان المسار هو الجذر، اعرض الصفحة الرئيسية
        res.end('<h1>الصفحة الرئيسية</h1><p>أهلاً بك في الصفحة الرئيسية!</p>');
    } else if (path === '/about') {
        // إذا كان المسار هو /about، اعرض صفحة "من نحن"
        res.end('<h1>من نحن</h1><p>نحن فريق شغوف بتقديم دروس برمجية.</p>');
    } else if (path === '/contact') {
        // إذا كان المسار هو /contact، اعرض صفحة "اتصل بنا"
        res.end('<h1>اتصل بنا</h1><p>يمكنك التواصل معنا عبر info@example.com</p>');
    } else {
        // لأي مسار آخر غير معروف، اعرض صفحة خطأ 404
        res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); // تغيير حالة الاستجابة إلى 404
        res.end('<h1>404 - الصفحة غير موجودة</h1><p>عذراً، الصفحة التي تبحث عنها غير موجودة.</p>');
    }
});

server.listen(PORT, () => {
    console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الخادم يعمل على http://localhost:${PORT}</code>);
    console.log('لإيقاف الخادم، اضغط Ctrl+C');
});

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

  • http://localhost:3000/
  • http://localhost:3000/about
  • http://localhost:3000/contact
  • http://localhost:3000/nonexistent

الخطوة 3: قراءة ملفات HTML من النظام

بدلاً من كتابة HTML مباشرة في الكود، من الأفضل قراءتها من ملفات منفصلة. سنستخدم وحدة fs (File System) لقراءة محتوى ملفات HTML.

وحدة fs تسمح لنا بالتفاعل مع نظام الملفات، مثل قراءة وكتابة وحذف الملفات. استخدام fs.readFile هو الأنسب هنا لأنه غير متزامن ولا يوقف تنفيذ الخادم.

أولاً، أنشئ مجلداً جديداً باسم views في نفس مستوى ملف server.js. داخل مجلد views، أنشئ ثلاثة ملفات: index.html, about.html, و contact.html.

views/index.html:

<!DOCTYPE html>
<html lang="ar">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>الصفحة الرئيسية</title>
</head>
<body>
    <h1>أهلاً بك في الصفحة الرئيسية!</h1>
    <p>هنا تجد محتوى صفحتنا الرئيسية.</p>
    <p>انتقل إلى <a href="/about">من نحن</a> أو <a href="/contact">اتصل بنا</a>.</p>
</body>
</html>

views/about.html:

<!DOCTYPE html>
<html lang="ar">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>من نحن</title>
</head>
<body>
    <h1>من نحن</h1>
    <p>نحن فريق مبرمجين خبراء نقدم حلولاً تقنية مبتكرة.</p>
    <p><a href="/">العودة للصفحة الرئيسية</a></p>
</body>
</html>

views/contact.html:

<!DOCTYPE html>
<html lang="ar">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>اتصل بنا</title>
</head>
<body>
    <h1>اتصل بنا</h1>
    <p>للتواصل، أرسل بريداً إلكترونياً إلى: <a href="mailto:info@example.com">info@example.com</a></p>
    <p><a href="/">العودة للصفحة الرئيسية</a></p>
</body>
</html>

الآن، عدّل ملف server.js لاستخدام fs لقراءة هذه الملفات:

const http = require('http');
const url = require('url');
// استيراد وحدة fs للتعامل مع نظام الملفات
const fs = require('fs');
// استيراد وحدة path للتعامل مع مسارات الملفات بطريقة آمنة ومتوافقة مع أنظمة التشغيل المختلفة
const path = require('path');

const PORT = 3000;

// دالة مساعدة لقراءة ملف HTML وإرساله كاستجابة
function serveHtmlFile(filePath, res, statusCode = 200) {
    fs.readFile(filePath, (err, data) => {
        if (err) {
            // إذا حدث خطأ في قراءة الملف (مثلاً، الملف غير موجود)
            res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
            res.end('<h1>500 - خطأ داخلي في الخادم</h1><p>عذراً، حدث خطأ أثناء تحميل الصفحة.</p>');
            console.error(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">خطأ في قراءة الملف ${filePath}:</code>, err);
            return;
        }
        // إذا تم قراءة الملف بنجاح، أرسل محتواه كاستجابة HTML
        res.writeHead(statusCode, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end(data);
    });
}

const server = http.createServer((req, res) => {
    const parsedUrl = url.parse(req.url, true);
    const pathName = parsedUrl.pathname;

    // تحديد المسار المطلق لملفات العرض (views)
    const viewsPath = path.join(__dirname, 'views');

    if (pathName === '/') {
        // عرض الصفحة الرئيسية
        serveHtmlFile(path.join(viewsPath, 'index.html'), res);
    } else if (pathName === '/about') {
        // عرض صفحة من نحن
        serveHtmlFile(path.join(viewsPath, 'about.html'), res);
    } else if (pathName === '/contact') {
        // عرض صفحة اتصل بنا
        serveHtmlFile(path.join(viewsPath, 'contact.html'), res);
    } else {
        // عرض صفحة 404 لأي مسار آخر
        serveHtmlFile(path.join(viewsPath, '404.html'), res, 404); // نفترض وجود ملف 404.html
    }
});

server.listen(PORT, () => {
    console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الخادم يعمل على http://localhost:${PORT}</code>);
    console.log('لإيقاف الخادم، اضغط Ctrl+C');
});

لا تنسَ إنشاء ملف views/404.html لصفحة الخطأ:

<!DOCTYPE html>
<html lang="ar">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 - الصفحة غير موجودة</title>
</head>
<body>
    <h1>404 - الصفحة غير موجودة</h1>
    <p>عذراً، الصفحة التي تبحث عنها غير موجودة في خادمنا.</p>
    <p><a href="/">العودة للصفحة الرئيسية</a></p>
</body>
</html>

أعد تشغيل الخادم (node server.js) واختبر الروابط مرة أخرى. الآن، سيقوم الخادم بقراءة المحتوى من ملفات HTML الفعلية.

الكود النهائي الكامل (server.js)

هذا هو الكود الكامل لملف server.js بعد جميع التعديلات:

const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');

const PORT = 3000;

// دالة مساعدة لقراءة ملف HTML وإرساله كاستجابة
function serveHtmlFile(filePath, res, statusCode = 200) {
    fs.readFile(filePath, (err, data) => {
        if (err) {
            // إذا حدث خطأ في قراءة الملف (مثلاً، الملف غير موجود)
            res.writeHead(500, { 'Content-Type': 'text/html; charset=utf-8' });
            res.end('<h1>500 - خطأ داخلي في الخادم</h1><p>عذراً، حدث خطأ أثناء تحميل الصفحة.</p>');
            console.error(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">خطأ في قراءة الملف ${filePath}:</code>, err);
            return;
        }
        // إذا تم قراءة الملف بنجاح، أرسل محتواه كاستجابة HTML
        res.writeHead(statusCode, { 'Content-Type': 'text/html; charset=utf-8' });
        res.end(data);
    });
}

// إنشاء الخادم
const server = http.createServer((req, res) => {
    // تحليل المسار المطلوب من العميل
    const parsedUrl = url.parse(req.url, true);
    const pathName = parsedUrl.pathname; // الحصول على المسار بدون البارامترات

    // تحديد المسار المطلق لملفات العرض (views)
    const viewsPath = path.join(__dirname, 'views');

    // منطق التوجيه بناءً على المسار
    if (pathName === '/') {
        // إذا كان المسار هو الجذر، اعرض الصفحة الرئيسية
        serveHtmlFile(path.join(viewsPath, 'index.html'), res);
    } else if (pathName === '/about') {
        // إذا كان المسار هو /about، اعرض صفحة "من نحن"
        serveHtmlFile(path.join(viewsPath, 'about.html'), res);
    } else if (pathName === '/contact') {
        // إذا كان المسار هو /contact، اعرض صفحة "اتصل بنا"
        serveHtmlFile(path.join(viewsPath, 'contact.html'), res);
    } else {
        // لأي مسار آخر غير معروف، اعرض صفحة خطأ 404
        // نستخدم path.join لضمان المسار الصحيح بغض النظر عن نظام التشغيل
        serveHtmlFile(path.join(viewsPath, '404.html'), res, 404);
    }
});

// جعل الخادم يستمع على المنفذ المحدد
server.listen(PORT, () => {
    console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">الخادم يعمل على http://localhost:${PORT}</code>);
    console.log('لإيقاف الخادم، اضغط Ctrl+C');
});

النتيجة المتوقعة

عند تشغيل السكربت node server.js، سيظهر في سطر الأوامر:

الخادم يعمل على http://localhost:3000
لإيقاف الخادم، اضغط Ctrl+C

ثم، عند زيارة الروابط التالية في متصفح الويب:

  • http://localhost:3000/: ستعرض محتوى ملف views/index.html.
  • http://localhost:3000/about: ستعرض محتوى ملف views/about.html.
  • http://localhost:3000/contact: ستعرض محتوى ملف views/contact.html.
  • http://localhost:3000/any-other-path: ستعرض محتوى ملف views/404.html مع رمز حالة HTTP 404 (Not Found).

لقد نجحت في بناء خادم ويب بسيط يعرض صفحات HTML ديناميكياً بناءً على المسار المطلوب!