Node.js: وحدة HTTP
آخر تحديث: 2026-08-26
كان بوب بحاجة إلى التحقق بسرعة من صحة فكرة واجهة برمجة تطبيقات (API)، لكنه لم يرغب في إنشاء مشروع Express كامل، لذا استخدم وحدة HTTP الأصلية لتشغيل خادم API في 20 سطرًا فقط من التعليمات البرمجية. بدءًا من معالجة أساليب الطلبات وتحليل مسارات عناوين URL وصولاً إلى إرجاع بيانات JSON وتعيين رموز الحالة، وجد بوب أنه بمجرد فهمه للمبادئ الأساسية، أصبح استخدام إطار العمل أسهل بكثير بالفعل.
1. ما ستتعلمه
- استخدم
http.createServer/server.listenلإنشاء خادم HTTP - قراءة الخصائص الأساسية (الطرق / عناوين URL / الرؤوس) للكائن
request - استخدم الكائن
responseلإرسال استجابة (writeHead / end / statusCode) - استخدم
new URL()لتحليل مسارات «route» ومعلمات «query» - معالجة طلبات GET ومعلمات الاستعلام
- معالجة طلبات POST وجمع محتويات الطلبات
- تعيين نوع المحتوى (Content-Type) ورؤوس الاستجابة المخصصة
- معاني رموز حالة HTTP الشائعة وحالات استخدامها
2. إنشاء أول خادم HTTP خاص بك
http.createServer يقبل دالة استدعاء (callback) يتم تشغيلها في كل مرة يتم فيها استلام طلب. تتلقى دالة الاستدعاء معلمتين: request (كائن الطلب) وresponse (كائن الاستجابة). server.listen يحدد منفذ الاستماع.
▶ مثال: تقليل حجم خادم HTTP
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
node server.js
Server running at http://localhost:3000/
ما عليك سوى زيارة http://localhost:3000/ في متصفحك لعرض Hello, World!.
3. دورة حياة طلبات واستجابات HTTP
يتبع كل تفاعل عبر بروتوكول HTTP المسار التالي: الطلب → التوجيه → المعالجة → الاستجابة؛ ويُعد فهم دورة الحياة هذه الأساس لبناء خدمات الويب.
flowchart LR
A["Client"] -->|"Send Request"| B["request Object<br/>method / url / headers"]
B --> C["Routing Resolution<br/>pathname + searchParams"]
C --> D{"Request Method?"}
D -->|GET| E["Read Query Parameters"]
D -->|POST / PUT| F["Collect the request body"]
E --> G["Business Processing"]
F --> G
G --> H["response Object<br/>statusCode / headers / body"]
H -->|"Return Response"| A
4. الخصائص الأساسية للكائن request
request يحتوي هذا الكائن على جميع معلومات الطلب التي أرسلها العميل؛ وأهم ثلاث خصائص تُستخدم بشكل شائع هي method وurl وheaders.
| الخاصية / الطريقة | النوع | الوصف | مثال على القيمة |
|---|---|---|---|
req.method |
سلسلة | طريقة الطلب | 'GET'، 'POST' |
req.url |
سلسلة | مسار الطلب (بما في ذلك سلسلة الاستعلام) | '/api/users?id=1' |
req.headers |
كائن | كائن رأس الطلب | { 'content-type': 'application/json' } |
req.httpVersion |
سلسلة | إصدار بروتوكول HTTP | '1.1' |
req.socket |
كائن | كائن المقبس الأساسي | — |
▶ مثال: معلومات طلب الطباعة
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`Method: ${req.method}`);
console.log(`URL: ${req.url}`);
console.log(`Content-Type: ${req.headers['content-type'] || 'N/A'}`);
res.end('Check your terminal for request info.');
});
server.listen(3000);
أرسل طلبًا تجريبيًّا باستخدام curl:
curl -X POST http://localhost:3000/api/data -H "Content-Type: application/json"
Method: POST
URL: /api/data
Content-Type: application/json
5. الطرق الأساسية للكائن response
response يُستخدم هذا الكائن لإرسال بيانات الاستجابة إلى العميل، بما في ذلك رمز الحالة ورؤوس الاستجابة ونص الاستجابة.
| الطريقة / الخاصية | الوصف | مثال |
|---|---|---|
res.writeHead(statusCode, headers) |
كتابة رموز الحالة ورؤوس استجابة متعددة في عملية واحدة | res.writeHead(200, { 'Content-Type': 'text/plain' }) |
res.statusCode = n |
ضبط رمز الحالة بشكل فردي | res.statusCode = 404 |
res.setHeader(name, value) |
تعيين رأس استجابة واحد | res.setHeader('Content-Type', 'application/json') |
res.write(data) |
كتابة بيانات نص الرد (يمكن استدعاؤها عدة مرات) | res.write('partial') |
res.end(data) |
إرسال نص الاستجابة وإنهاء الاستجابة | res.end('done') |
res.writeHead بعد استدعائه، ثم res.end() |
إرسال البيانات المخزنة مؤقتًا | — |
▶ مثال: إرجاع استجابة بتنسيق JSON
const http = require('http');
const server = http.createServer((req, res) => {
const data = { message: 'Success', timestamp: Date.now() };
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
});
server.listen(3000);
curl http://localhost:3000/
{"message":"Success","timestamp":1719792000000}
6. تحديد مسار عناوين URL
يحتوي req.url على مسار الطلب وسلسلة الاستعلام بالكامل. ويُسهّل استخدام new URL() فصل اسم المسار و«searchParams»، مما يتيح التوجيه استنادًا إلى المسار.
▶ مثال: التوجيه القائم على المسار
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
res.writeHead(200, { 'Content-Type': 'text/plain' });
if (pathname === '/') {
res.end('Home Page');
} else if (pathname === '/about') {
res.end('About Page');
} else if (pathname === '/api/status') {
res.end('OK');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
7. طلبات GET ومعلمات الاستعلام
يتم تضمين معلمات طلب GET في سلسلة الاستعلام الخاصة بعنوان URL، ويمكنك استرداد أزواج المفاتيح والقيم مباشرةً باستخدام url.searchParams.
▶ مثال: تحليل معلمات الاستعلام
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Method Not Allowed');
return;
}
const url = new URL(req.url, `http://${req.headers.host}`);
const name = url.searchParams.get('name') || 'Guest';
const page = url.searchParams.get('page') || '1';
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name, page }));
});
server.listen(3000);
curl "http://localhost:3000/?name=Bob&page=3"
{"name":"Bob","page":"3"}
ملاحظة: تُرجع الدالة
searchParams.get()دائمًا سلسلة نصية، لذا يجب عليك تحويلها يدويًّا إلى رقم أو نوع آخر.
8. طلبات POST وجمع نص الطلب
يتم إرسال بيانات طلبات POST عبر نص الطلب. يُعد الكائن request دفقًا قابلًا للقراءة؛ حيث يتعين عليك الاستماع إلى الحدث data لجمع كتل البيانات، والاستماع إلى الحدث end لمعالجة البيانات الكاملة.
▶ مثال: استرداد نص طلب POST
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/users') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 1, ...data }));
} catch (e) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Bob\",\"age\":30}"
{"id":1,"name":"Bob","age":30}
9. نوع المحتوى ورؤوس الاستجابة
Content-Type يُعد هذا أحد أهم رؤوس الاستجابة في الاتصالات عبر بروتوكول HTTP، حيث إنه يحدد تنسيق بيانات نص الاستجابة المرسلة إلى العميل. وسيؤدي ضبطه بشكل غير صحيح إلى منع العميل من تحليل البيانات بشكل صحيح.
| نوع المحتوى | الغرض | الوصف |
|---|---|---|
text/plain |
نص عادي | النوع الأساسي للنص، بدون أي تنسيق |
text/html |
صفحة HTML | يعرض المتصفح هذه الصفحة كصفحة ويب |
application/json |
بيانات JSON | تنسيق الاستجابة الأكثر استخدامًا في واجهات برمجة التطبيقات (API) |
application/x-www-form-urlencoded |
بيانات النموذج | التنسيق الافتراضي لإرسال النموذج |
multipart/form-data |
تحميل الملفات | إرسال النموذج مع الملفات |
application/xml |
بيانات XML | واجهة برمجة تطبيقات SOAP أو واجهة قديمة |
text/css |
ورقة أنماط CSS | ورقة أنماط |
application/octet-stream |
دفق ثنائي | سيناريوهات تنزيل الملفات |
▶ مثال: تأثير أنواع المحتوى المختلفة على نفس البيانات
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/plain') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('<h1>This is plain text</h1>');
} else if (url.pathname === '/html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>This is HTML</h1>');
} else if (url.pathname === '/json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'This is JSON' }));
}
});
server.listen(3000);
عند زيارة /plain، يعرض المتصفح علامة تبويب «الكود المصدري»؛ وعند زيارة /html، يعرض المتصفح العنوان الرئيسي؛ وعند زيارة /json، يعرض المتصفح بيانات JSON.
10. مرجع سريع لرموز حالة HTTP
رمز الحالة هو تمثيل موحد لاستجابة الخادم لطلب ما؛ ويحدد العميل الإجراء التالي الذي سيتخذه بناءً على رمز الحالة.
| رمز الحالة | الفئة | المعنى | الحالات الشائعة |
|---|---|---|---|
| 200 | 2xx نجاح | OK | طلب GET أعاد البيانات بنجاح |
| 201 | 2xx نجاح | تم الإنشاء | تم إنشاء المورد بنجاح عبر طلب POST |
| 204 | 2xx نجاح | لا يوجد محتوى | تم الحذف بنجاح، لم يتم إرجاع أي محتوى |
| 301 | إعادة التوجيه 3xx | تم النقل بشكل دائم | إعادة التوجيه الدائم إلى عنوان URL الجديد |
| 302 | إعادة توجيه 3xx | تم العثور عليه | إعادة توجيه مؤقتة |
| 304 | إعادة توجيه 3xx | لم يتم التعديل | تم العثور على النسخة المخزنة مؤقتًا؛ لا حاجة لإعادة الإرسال |
| 400 | خطأ عميل 4xx | طلب غير صحيح | تنسيق معلمة الطلب غير صحيح |
| 401 | خطأ عميل 4xx | غير مصرح به | لم تتم المصادقة؛ يلزم تسجيل الدخول |
| 403 | خطأ عميل 4xx | ممنوع | تم المصادقة ولكن لا توجد أذونات |
| 404 | خطأ العميل 4xx | غير موجود | المسار أو المورد غير موجود |
| 405 | خطأ عميل 4xx | الطريقة غير مسموح بها | طريقة الطلب غير مسموح بها |
| 500 | خطأ الخادم 5xx | خطأ داخلي في الخادم | خطأ داخلي في الخادم |
| 502 | خطأ خادم 5xx | بوابة غير صالحة | تلقت البوابة/الوكيل استجابة غير صالحة |
| 503 | خطأ خادم 5xx | الخدمة غير متاحة | الخدمة غير متاحة مؤقتًا |
▶ مثال: إرجاع رموز حالة مختلفة بناءً على الشروط
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const id = url.searchParams.get('id');
if (!id) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Missing id parameter' }));
} else if (id === '0') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id, name: 'Bob' }));
}
});
server.listen(3000);
11. مثال شامل: خادم بسيط لواجهة برمجة التطبيقات REST
اجمع بين جميع المفاهيم التي تمت تغطيتها حتى الآن لإنشاء خادم واجهة برمجة تطبيقات REST يدعم مسارات GET/POST، وردود JSON، وتحليل معلمات الاستعلام. احتفظ بقائمة بالمستخدمين في الذاكرة وادعم ثلاث عمليات: الاستعلام عن جميع المستخدمين، والاستعلام عن مستخدم واحد، وإنشاء مستخدم.
const http = require('http');
const users = [
{ id: 1, name: 'Bob', email: 'bob@example.com' },
{ id: 2, name: 'Alice', email: 'alice@example.com' },
];
let nextId = 3;
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => {
try { resolve(JSON.parse(body)); }
catch (e) { reject(e); }
});
req.on('error', reject);
});
}
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
'X-Powered-By': 'Node.js',
});
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
// GET /api/users
if (req.method === 'GET' && pathname === '/api/users') {
const limit = parseInt(url.searchParams.get('limit')) || 10;
sendJSON(res, 200, users.slice(0, limit));
return;
}
// GET /api/users/:id
if (req.method === 'GET' && pathname.startsWith('/api/users/')) {
const id = parseInt(pathname.split('/').pop());
const user = users.find((u) => u.id === id);
if (!user) {
sendJSON(res, 404, { error: 'User not found' });
} else {
sendJSON(res, 200, user);
}
return;
}
// POST /api/users
if (req.method === 'POST' && pathname === '/api/users') {
try {
const data = await parseBody(req);
if (!data.name || !data.email) {
sendJSON(res, 400, { error: 'name and email are required' });
return;
}
const newUser = { id: nextId++, name: data.name, email: data.email };
users.push(newUser);
sendJSON(res, 201, newUser);
} catch (e) {
sendJSON(res, 400, { error: 'Invalid JSON body' });
}
return;
}
// 404 fallback
sendJSON(res, 404, { error: 'Route not found' });
});
server.listen(3000, () => {
console.log('REST API server running at http://localhost:3000/');
});
اختبار جميع الواجهات:
# Query All Users
curl http://localhost:3000/api/users
# Query a Single User
curl http://localhost:3000/api/users/1
# Create a New User
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Charlie\",\"email\":\"charlie@example.com\"}"
# Accessing a Route That Does Not Exist
curl http://localhost:3000/unknown
[{"id":1,"name":"Bob","email":"bob@example.com"},{"id":2,"name":"Alice","email":"alice@example.com"}]
{"id":1,"name":"Bob","email":"bob@example.com"}
{"id":3,"name":"Charlie","email":"charlie@example.com"}
{"error":"Route not found"}
❓ أسئلة شائعة
req هو دفق قابل للقراءة. تحتاج إلى الاستماع إلى الحدث data لجمع كتل المخزن المؤقت وربطها معًا، ثم الاستماع إلى الحدث end للإشارة إلى اكتمال استقبال البيانات، وأخيرًا استخدام JSON.parse() أو Buffer.concat() لمعالجة البيانات الكاملة.res.end() ترسل البيانات وتغلق الاستجابة؛ ويجب استدعاؤها مرة واحدة — ومرة واحدة فقط — لكل طلب؛ res.write() تكتب البيانات فقط دون إغلاق الاستجابة؛ ويمكن استدعاؤها عدة مرات من أجل البث المتواصل أو الإرسال على أجزاء، ولكن res.end() يجب استدعاؤها في النهاية لإغلاق الاستجابة.text/plain. ولن تقوم العملاء (المتصفحات، وfetch، وcurl) بتحليل الاستجابة تلقائيًا على أنها JSON، مما قد يؤدي إلى حدوث أخطاء response.json() أو عدم عرض البيانات بشكل صحيح. ويضمن تعيينه إلى application/json أن العملاء يعرفون كيفية تحليل نص الاستجابة.new URL(req.url, 'http://localhost') لإنشاء كائن URL، ثم استخدم url.searchParams.get('key') لاسترداد قيم المعلمات، أو استخدم url.searchParams.entries() للتنقل بين جميع المعلمات.server.listen؟server.on('listening', callback)، الذي يحقق نفس الغرض.new URL base؟req.url على جزء المسار فقط (على سبيل المثال، /api?id=1) وهي ليست عنوان URL كاملاً. يتطلب new URL() معلمة base لإكمال البروتوكول واسم المضيف؛ وإلا، فسيتم إصدار استثناء TypeError. ولا تؤثر قيمة base على نتائج تحليل pathname وsearchParams.📖 ملخص
- المفاهيم الأساسية وكيفية تطبيقها
- المفاهيم الأساسية وكيفية استخدام أول خادم HTTP
- المفاهيم الأساسية ودورة حياة طلبات واستجابات HTTP
- المفاهيم الأساسية واستخدامات الخصائص الأساسية للكائن
request - المفاهيم الأساسية واستخدامات الطرق الأساسية للكائن
response - المفاهيم الأساسية لتوجيه عناوين URL وكيفية استخدامها
- المفاهيم الأساسية لطلبات GET ومعلمات الاستعلام وكيفية استخدامها
- المفاهيم الأساسية لطلبات POST واستخداماتها، وجمع نص الطلب
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لإنشاء مشروع صغير.