2.4إنشاء خادم HTTP (HTTP Server) أصلي
الأساسي في 30 ثانية
- خادم في سطور قليلة: createServer((req, res) => ...) ثم .listen(port) — كل طلب يطلق الدالة.
- تقرأ req وتجيب res: الطريقة والعنوان والترويسات من جهة؛ والحالة والترويسات والجسم من جهة أخرى — مفردات HTTP لواجهات API.
- يدويًا، ثم Express: فالتوجيه بسلسلة if وتحليل الجسم يُظهر ما يبسّطه Express — لا صندوق أسود أبدًا.
حالة مستمرة — ساليم يقدّم الجرد في المتصفح
الفصل الأخير: يريد ساليم أن يطّلع الفريق على الجرد (2.1) من المتصفح، دون تثبيت أي شيء. فينشئ خادمًا بوحدة http: مسار يسرد التقارير بصيغة JSON، ومسار يعرض الاستقبال بصيغة HTML، و404 للباقي — كلها موجّهة يدويًا حسب req.method وreq.url. وبمعاناته مع تحليل الجسم وسلسلة if، يفهم بالضبط ما سيبسّطه له Express (المستوى 3). اكتمل مشروع المستوى 2: ملفات وأحداث وتدفقات وخادم.
خاتمة الخيط الرابط 2.1-2.3: تصبح أدوات ساليم خادمًا يطّلع عليه الفريق.
1. خادم ويب دون اعتماد
ينشئ Node خوادم الويب وواجهات API بوحدته الأصلية http، دون أي اعتماد — وفعله «يدويًا» قبل Express تعليمي جدًا. ننشئ خادمًا بـhttp.createServer() بتمرير دالة تُستدعَى عند كل طلب: تستقبل req (الطلب: طريقة GET/POST والعنوان والترويسات والجسم) وres (الردّ: ما نبنيه ونرسله). ثم يجعل .listen(port) الخادم يستمع للاتصالات (مثل 3000). بضعة أسطر لخادم يعمل: يعرض ساليم الجرد في متصفح الفريق بعشرة أسطر.
2. الطلب req والردّ res ومفردات HTTP
من جهة الطلب، يوفّر req الخاصية req.method (GET وPOST وPUT وDELETE…) والخاصية req.url (المسار، مثل /rapports) والخاصية req.headers — وفي POST، يُقرَأ الجسم كـتدفق (القسم 2-3)، قطعةً قطعةً. ومن جهة الردّ، يبني res: res.statusCode = 200 وres.setHeader('Content-Type', 'application/json') وres.write(...) ثم res.end(...). ويجب معرفة HTTP: الطرق (GET للقراءة، POST للإنشاء، PUT/PATCH للتعديل، DELETE للحذف — مفردات واجهات REST، القسم 4-1)، والرموز (200 OK و201 Created و400 Bad Request و401 Unauthorized و404 Not Found و500 Internal Server Error…)، والترويسات (نوع المحتوى، والاستيثاق، والتخزين المؤقت…).
3. كل شيء يدويًا — ثم Express
في الأصلي، ندبّر كل شيء يدويًا: التوجيه حسب العنوان والطريقة (سلسلة if)، وتحليل الجسم، وتحديد الترويسات، وتدبير الأخطاء — فيصبح مضنيًا سريعًا لتطبيق حقيقي. وهنا يتدخّل Express (القسم 3-1): توجيه أنيق، وتحليل للجسم، ووسائط — لكن كل شيء يقوم على الوحدة http. فإنشاء خادم أصلي إذن تمرين مؤسِّس: يكشف الأساس (req/res وHTTP والمنافذ) ويُقدِّر ما يجلبه إطار العمل. ولن نكتب خوادم أصلية في الإنتاج (سنستخدم Express)، لكن هذا الفهم هو الفرق بين استخدام Express بفهم وتحمّله كصندوق أسود — بما فيه التنقيح عندما تفشل «السحرية».
هل تعلم؟
عشرة أسطر من الأصلي خير من إطار عمل مُتحمَّل.
🔎 مثال ملموس — 🔎 مثال ملموس — خادم جرد ساليم
ثلاثة مسارات موجّهة يدويًا، كما عند ساليم:
GET / → 200، صفحة استقبال HTML
GET /rapports → 200، الجرد بصيغة JSON (Content-Type: application/json)
مسار آخر → 404، رسالة خطأ
POST /rapports → 201 بعد قراءة الجسم بالتدفق
القراءة: لكل تركيبة طريقة + عنوان ردّها وحالتها.
الدرس: نقيس ما سيبسّطه Express (التوجيه، التحليل).
مفردات القسم
- وحدة http (أصلية)
- وحدة تنشئ خادم ويب دون اعتماد: http.createServer() بدالة تُستدعَى عند كل طلب، ثم .listen(port).
مثال بسيط: يقدّم ساليم الجرد على المنفذ 3000 بعشرة أسطر.
ما ليس كذلك: إطار عمل مثل Express (الذي يقوم عليه).
رد فعل: افهم الأساس قبل استخدام إطار العمل.
- الطلب req (request)
- كائن الطلب الوارد: الطريقة (req.method) والمسار (req.url) والترويسات (req.headers)، والجسم يُقرَأ كتدفق في POST.
مثال بسيط: يوجّه ساليم حسب req.method وreq.url.
ما ليس كذلك: الكائن الذي نبني به الردّ (فذلك res).
رد فعل: قراءة الطلب = req.
- الردّ res (response)
- كائن بناء الردّ: الحالة (res.statusCode) والترويسات (res.setHeader) والجسم (res.write/res.end).
مثال بسيط: يرسل ساليم res.statusCode = 200 ثم res.end(JSON.stringify(...)).
ما ليس كذلك: الطلب الذي يرسله العميل (فذلك req).
رد فعل: بناء الردّ = res، وأنهِ دائمًا بـend().
- طرق HTTP ورموزه
- الطرق: GET (قراءة) وPOST (إنشاء) وPUT/PATCH (تعديل) وDELETE (حذف). والرموز: 200 OK و201 Created و400/401/404 (عميل) و500 (خادم).
مثال بسيط: GET /rapports ← 200، وPOST /rapports ← 201، ومسار مجهول ← 404.
ما ليس كذلك: إرسال 200 لكل شيء، نجاحًا وأخطاء.
رد فعل: كل ردّ = الفعل الصحيح + الرمز الصحيح.
- ما يبسّطه Express
- يدبّر الأصلي كل شيء يدويًا (توجيه بسلسلة if، وتحليل الجسم، والترويسات)؛ ويوفّر Express التوجيه والتحليل والوسائط — على وحدة http.
مثال بسيط: يستبدل ساليم سلسلة if بـapp.get('/rapports', ...) في المستوى 3.
ما ليس كذلك: خادم منافس يستبدل http.
رد فعل: المضني يدويًا = حاجة يغطيها Express.
في التطبيق — تطبيق عملي — إنشاء خادم HTTP (HTTP Server) أصلي
- أنشئ خادمًا بـhttp.createServer((req, res) => {...}) وأقلعه بـ.listen(3000)؛ وجرّبه في المتصفح (localhost:3000).
- أرسل ردًّا: حدّد res.statusCode وres.setHeader('Content-Type', ...) وأنهِ بـres.end('Hello').
- وجّه «يدويًا» حسب req.method وreq.url (سلسلة if): أرسل ردودًا مختلفة حسب المسار.
- أرسل JSON (Content-Type application/json مع res.end(JSON.stringify(data))) ولاحظ كم يصبح تدبير كل شيء يدويًا مضنيًا سريعًا.
نص جاهز — الدفاع عن التمرين الأصلي قبل Express (30 ثانية)
«قبل Express، وجّهت ثلاثة مسارات يدويًا: أعرف ما الطلب، وكيف نختار الردّ حسب الطريقة والعنوان، ولماذا تُحسَب كل حالة. والآن، عندما أكتب app.get، سأعرف ما يفعله Express لأجلي — وعندما ينكسر، سأعرف أين أنظر.»
يُقال مع عرض الخادم الأصلي يعمل على localhost:3000؛ فالعرض في المتصفح يقنع.
نموذج نسخ ولصق — خادم أصلي بتوجيه يدوي
const http = require('node:http');
const rapports = [
{ nom: 'rapport-01.txt', taille: 1240 },
{ nom: 'rapport-02.txt', taille: 986 }
];
const serveur = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end('Inventaire des rapports
');
} else if (req.method === 'GET' && req.url === '/rapports') {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(rapports));
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('Non trouvé');
}
});
serveur.listen(3000, () => console.log('Serveur sur http://localhost:3000'));
عرض محلي
localhost:3000 أمام الفريق: يظهر جرد ساليم في المتصفح، صفر نشر.
واجهة JSON للفريق
يرسل GET /rapports الجرد بصيغة JSON: يستهلكه الزملاء من سكربتاتهم الخاصة.
ملف كبير للتقديم
لإرسال تصدير 2 Go: نصِل createReadStream نحو res بالأنبوب (القسم 2.3) بدل تحميل كل شيء.
نحو Express
متى تتراكم سلاسل if: يستبدل app.get('/rapports', ...) السلسلة — نفس الأساس http تحتها.
✓ ممارسات جيدة
- اختبر كل مسار في المتصفح أو بـcurl (الطريقة + العنوان + الحالة).
- أرسل دائمًا الحالة الصحيحة (201 بعد الإنشاء، و404 إن كان مجهولًا، و500 عند خطأ).
- أنهِ كل ردّ بـres.end()، وإلا انتظر العميل إلى ما لا نهاية.
✗ أخطاء شائعة
- تخطّي الأصلي وتحمّل Express كصندوق أسود.
- إرسال 200 لكل شيء، نجاحًا وأخطاء.
- نسيان res.end() (طلب معلّق إلى ما لا نهاية).
النقاط الأساسية
- ينشئ Node خوادم الويب بالوحدة الأصلية http (دون اعتماد): http.createServer((req, res) => ...) ثم .listen(port). تعليمي قبل Express.
- req = الطلب (الطريقة والعنوان والترويسات والجسم بالتدفق)؛ وres = الردّ (statusCode وsetHeader وwrite/end).
- اعرف HTTP: الطرق (GET/POST/PUT/DELETE) ورموز الحالة (200 و201 و400 و401 و404 و500) — مفردات واجهات REST.
- يدبّر الخادم الأصلي كل شيء يدويًا (توجيه بسلسلة if، وتحليل الجسم، والترويسات) ← فيصبح مضنيًا سريعًا؛ يبسّطه Express لكنه يقوم على http.
تحدي 48 ساعة — طبّق غدًا
ضع خادم النموذج على الخط ببياناتك الحقيقية (جردك 2.1): استقبال HTML، ومسار JSON، و404 متقَنة. واعرضه على زميل في متصفحه، ثم دوّن في جملة ما سيبسّطه لك Express.
- أستطيع شرح دورة طلب ← توجيه ← ردّ.
- أعرف تقديم HTML وJSON بالحالات الصحيحة.
- اطّلع زميل على خادمي الأصلي في متصفحه.
أسئلة شائعة
لماذا أتعلّم خادم HTTP (HTTP Server) الأصلي إن كنت سأستخدم Express على أي حال؟
لتستخدم Express بفهم بدل تحمّله. فبعد التوجيه اليدوي (سلسلة if) وتحليل جسم بالتدفق وتحديد الترويسات، تعرف ما يحدث حقًا عندما تكتب app.get() — وتقيس ما يبسّطه Express (توجيه أنيق، وexpress.json()، ووسائط). والأهم أنك تنقّح: ترويسة ناقصة، حالة غير صحيحة، جسم غير محلَّل — فتنظر إلى المستوى الصحيح بدل العجز عندما تفشل «السحرية». وكساليم: ثلاثة مسارات يدويًا قبل أول app.get.
ما أساسيات بروتوكول HTTP التي يجب معرفتها حتمًا؟
دورة طلب ← ردّ، والطرق (GET للقراءة، POST للإنشاء، PUT للاستبدال، PATCH للتعديل الجزئي، DELETE للحذف — فعنوان واحد يفعل أشياء مختلفة حسب الطريقة)، ورموز الحالة (2xx نجاح: 200 OK و201 Created؛ و4xx خطأ عميل: 400 و401 و403 و404؛ و5xx خطأ خادم: 500 — فالواجهة الجيّدة ترسل الرمز الصحيح، لا 200 لكل شيء)، والترويسات (Content-Type والاستيثاق والتخزين المؤقت). وتبني هذه المفردات واجهات REST (القسم 4-1) وتُمارَس منذ الخادم الأصلي: فكل مسار من مسارات ساليم يرسل الطريقة والحالة وContent-Type الصحيحة.
يصل مسار مجهول إلى خادم ساليم الأصلي. ماذا يجب أن يرسل؟