إذا كنت تقوم بالتطوير من أجل node.js للنظام الأساسي ، فربما سمعت عن
express.js . هذا هو واحد من أكثر أطر العمل خفيفة الوزن المستخدمة لإنشاء تطبيقات الويب للعقدة.

يعرض مؤلف المادة ، التي ننشر ترجمتها اليوم ، دراسة ميزات الهيكل الداخلي للإطار السريع من خلال تحليل شفرة المصدر الخاصة به والنظر في مثال على استخدامه. وهو يعتقد أن دراسة الآليات التي تقوم عليها مكتبات المصادر المفتوحة الشعبية تساهم في فهم أعمق لها ، وتزيل ستار "الغموض" منها وتساعد على إنشاء تطبيقات أفضل بناءً عليها.
قد تجد أنه من الملائم إبقاء كود المصدر السريع في متناول اليد أثناء قراءة هذه المادة. يستخدم
هذا الإصدار هنا. يمكنك قراءة هذه المقالة دون فتح الرمز السريع ، حيث يتم هنا هنا ، حيثما كان ذلك مناسبًا ، تقديم أجزاء التعليمات البرمجية لهذه المكتبة. في تلك الأماكن حيث يتم اختصار الرمز ،
// ...
تعليقات النموذج
// ...
مثال أساسي لاستخدام صريح
بادئ ذي بدء ، دعونا نلقي نظرة على "Hello World!" التقليدية في تطوير تقنيات الكمبيوتر الجديدة - مثال. يمكن العثور عليه على الموقع الرسمي للإطار ، وسيكون بمثابة نقطة انطلاق في بحثنا.
const express = require('express') const app = express() app.get('/', (req, res) => res.send('Hello World!')) app.listen(3000, () => console.log('Example app listening on port 3000!'))
يبدأ هذا الرمز خادم HTTP جديدًا على المنفذ 3000 ويرسل
Hello World!
للطلبات المستلمة على
GET /
الطريق. إذا لم تدخل في التفاصيل ، فيمكننا التمييز بين أربع مراحل لما يحدث ، والتي يمكننا تحليلها:
- إنشاء تطبيق صريح جديد.
- إنشاء طريق جديد.
- بدء خادم HTTP على رقم المنفذ المحدد.
- معالجة الطلبات الواردة إلى الخادم.
إنشاء تطبيق صريح جديد
يتيح لك الأمر
var app = express()
إنشاء تطبيق صريح جديد. إن دالة
createApplication
من
ملف lib / express.js هي الوظيفة المصدرة الافتراضية ؛ فنحن الذين نصل إليها باستدعاء وظيفة
express()
. إليك بعض الأشياء المهمة التي يجب الانتباه إليها:
// ... var mixin = require('merge-descriptors'); var proto = require('./application'); // ... function createApplication() { // , . // : `function(req, res, next)` var app = function(req, res, next) { app.handle(req, res, next); }; // ... // `mixin` `proto` `app` // - `get`, . mixin(app, proto, false); // ... return app; }
يعد كائن
app
إرجاعه من هذه الوظيفة أحد الكائنات المستخدمة في رمز التطبيق الخاص بنا. تمت
app.get
طريقة
app.get
باستخدام وظيفة
mixin
بمكتبة
دمج الواصفات ، وهي المسؤولة عن تعيين طرق
app
المعلن عنها في
proto
. يتم استيراد الكائن
proto
نفسه من
lib / application.js .
إنشاء طريق جديد
الآن دعونا نلقي نظرة على
الكود المسؤول عن إنشاء طريقة
app.get
من مثالنا.
var slice = Array.prototype.slice; // ... /** * `.VERB(...)` `router.VERB(...)`. */ // `methods` HTTP, ( ['get','post',...]) methods.forEach(function(method){ // app.get app[method] = function(path){ // // var route = this._router.route(path); // route[method].apply(route, slice.call(arguments, 1)); // `app`, return this; }; });
من المثير للاهتمام ملاحظة أنه بالإضافة إلى الميزات الدلالية ، يمكن اعتبار جميع الطرق التي تنفذ إجراءات HTTP ، مثل
app.get
و
app.post
و
app.put
وما شابه ، من حيث الوظائف ، كما هي. إذا قمت بتبسيط الكود أعلاه ، واختزاله إلى تنفيذ طريقة
get
واحدة فقط ، فستحصل على شيء مثل ما يلي:
app.get = function(path, handler){ // ... var route = this._router.route(path); route.get(handler) return this }
على الرغم من أن الدالة أعلاه لها وسيطتان ، إلا أنها تشبه
app[method] = function(path){...}
الوظيفة
app[method] = function(path){...}
. يتم الحصول على الوسيطة الثانية ،
handler
، عن طريق استدعاء
slice.call(arguments, 1)
.
باختصار ،
app.<method>
يحفظ فقط المسار في موجه التطبيق باستخدام طريقة التوجيه الخاصة به ، ثم يمرر
handler
إلى التوجيه
route.<method>
.
تم تعريف طريقة جهاز التوجيه
route()
في
lib / router / index.js :
// proto - `_router` proto.route = function route(path) { var route = new Route(path); var layer = new Layer(path, { sensitive: this.caseSensitive, strict: this.strict, end: true }, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route; };
ليس من المستغرب أن يكون الإعلان عن طريقة
route.get
في
lib / router / route.js مشابهًا لإعلان
app.get
:
methods.forEach(function (method) { Route.prototype[method] = function () { // `flatten` , [1,[2,3]], var handles = flatten(slice.call(arguments)); for (var i = 0; i < handles.length; i++) { var handle = handles[i]; // ... // , , Layer, // var layer = Layer('/', {}, handle); // ... this.stack.push(layer); } return this; }; });
يمكن أن يحتوي كل مسار على عدة معالجات ؛ على أساس كل معالج ، يتم إنشاء متغير من النوع
Layer
، وهو عبارة عن طبقة معالجة بيانات ، والتي تدخل بعد ذلك في المكدس.
كائنات الطبقة
_router
كل من
_router
و
route
كائنات من النوع
Layer
. من أجل فهم جوهر هذا الشيء ، دعنا ننظر إلى
مُنشئه :
function Layer(path, options, fn) { // ... this.handle = fn; this.regexp = pathRegexp(path, this.keys = [], opts); // ... }
عند إنشاء كائنات من النوع
Layer
يتم إعطاؤها مسارًا ومعلمات معينة ودالة. في حالة جهاز التوجيه الخاص بنا ، هذه الوظيفة هي
route.dispatch
(سنتحدث عنها أكثر أدناه ، بعبارات عامة ، تم تصميمها لإرسال طلب إلى مسار منفصل). في حالة المسار نفسه ، هذه الوظيفة هي وظيفة معالج معلن عنها في رمز مثالنا.
يحتوي كل كائن من النوع
Layer
على أسلوب
handle_request ، وهو المسؤول عن تنفيذ الوظيفة التي تم تمريرها عند تهيئة الكائن.
تذكر ما يحدث عند إنشاء مسار باستخدام طريقة
app.get
:
- يتم إنشاء مسار في موجه التطبيق (
this._router
). - يتم تعيين طريقة توجيه
dispatch
كطريقة معالج لكائن Layer
المقابلة ، ويتم دفع هذا الكائن إلى مكدس جهاز التوجيه. - يتم تمرير معالج الطلب إلى كائن
Layer
كطريقة معالج ، ويتم دفع هذا الكائن إلى رصة التوجيه.
ونتيجة لذلك ، يتم تخزين جميع المعالجات داخل مثيل
app
في شكل كائنات من نوع
Layer
الموجودة داخل مكدس التوجيه ، حيث يتم تعيين طرق
dispatch
الخاصة بكائنات
Layer
الموجودة في مكدس جهاز التوجيه:
كائنات طبقة على مكدس جهاز التوجيه ومكدس التوجيهتتم معالجة طلبات HTTP الواردة وفقًا لهذا المنطق. سنتحدث عنها أدناه.
بدء تشغيل خادم HTTP
بعد تكوين المسارات ، تحتاج إلى بدء الخادم. في مثالنا ، ننتقل إلى أسلوب
app.listen
،
app.listen
برقم المنفذ ووظيفة رد الاتصال كوسيطة. لفهم ميزات هذه الطريقة ، يمكننا الرجوع إلى ملف
lib / application.js :
app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); };
app.listen
أن
app.listen
مجرد غلاف حول
http.createServer
. وجهة النظر هذه منطقية ، لأنه إذا تذكرت ما تحدثنا عنه في البداية ، فإن
app
هو مجرد وظيفة ذات
function(req, res, next) {...}
توقيع
function(req, res, next) {...}
، وهو متوافق مع الحجج اللازمة لـ
http.createServer
(توقيع هذه الطريقة هو
function (req, res) {...}
).
بعد إدراك أنه في النهاية ، كل شيء يمنحه لنا Express.js يمكن اختزاله إلى معالج وظائف ذكي للغاية ، لم يعد الإطار معقدًا وغامضًا كما كان من قبل.
معالجة طلب HTTP
الآن بعد أن علمنا أن
app
مجرد معالج طلبات ، سنتبع المسار الذي يمر به طلب HTTP داخل تطبيق سريع. يؤدي هذا المسار إلى المعالج المعلن عنه.
أولاً ، ينتقل الطلب إلى دالة
createApplication
(
lib / express.js ):
var app = function(req, res, next) { app.handle(req, res, next); };
ثم ينتقل إلى طريقة
app.handle
(
lib / application.js ):
app.handle = function handle(req, res, callback) { // `this._router` - , , `app.get` var router = this._router; // ... // `handle` router.handle(req, res, done); };
تم
router.handle
طريقة
router.handle
في
lib / router / index.js :
proto.handle = function handle(req, res, out) { var self = this; //... // self.stack - , // Layer ( ) var stack = self.stack; // ... next(); function next(err) { // ... // var path = getPathname(req); // ... var layer; var match; var route; while (match !== true && idx < stack.length) { layer = stack[idx++]; match = matchLayer(layer, path); route = layer.route; // ... if (match !== true) { continue; } // ... HTTP, } // ... // process_params , self.process_params(layer, paramcalled, req, res, function (err) { // ... if (route) { // `layer.handle_request` // `next` // , `next` , // , `next` , return layer.handle_request(req, res, next); } // ... }); } };
إذا كنت تصف ما يحدث باختصار ، فإن وظيفة
router.handle
تمر عبر جميع الطبقات على المكدس حتى تعثر على
router.handle
التي تطابق المسار المحدد في الطلب. بعد ذلك ، سيتم استدعاء طريقة طبقة
handle_request
، والتي ستقوم بتنفيذ وظيفة المعالج المحددة مسبقًا. وظيفة المعالج هذه هي طريقة توجيه
dispatch
، والتي يتم الإعلان عنها في
lib / route / route.js :
Route.prototype.dispatch = function dispatch(req, res, done) { var stack = this.stack; // ... next(); function next(err) { // ... var layer = stack[idx++]; // ... layer.handle_request(req, res, next); // ... } };
تمامًا كما هو الحال في جهاز التوجيه ، أثناء معالجة كل مسار ، يتم تعداد الطبقات التي يحتوي عليها هذا المسار
handle_request
الخاصة بـ
handle_request
التي تنفذ طرق معالج الطبقة. في حالتنا ، هذا هو معالج الطلب ، الذي تم تعريفه في رمز التطبيق.
هنا ، أخيرًا ، يقع طلب HTTP في منطقة التعليمات البرمجية لتطبيقنا.
طلب المسار في التطبيق السريعالملخص
قمنا هنا بفحص الآليات الأساسية لمكتبة express.js ، وهي تلك المسؤولة عن تشغيل خادم الويب ، ولكن هذه المكتبة لديها أيضًا العديد من الميزات الأخرى. لم نتوقف عند الشيكات التي تمر بها الطلبات قبل وصولها إلى المعالجات ؛ ولم نتحدث عن الأساليب المساعدة المتاحة عند العمل مع متغيرات
res
req
. وأخيرًا ، لم نتطرق إلى واحدة من أقوى ميزات Express. وهو يتألف من استخدام البرامج الوسيطة ، والتي يمكن أن تهدف إلى حل أي مشكلة تقريبًا - من طلبات التحليل إلى تطبيق نظام مصادقة كامل.
نأمل أن تساعدك هذه المادة على فهم الميزات الرئيسية للجهاز السريع ، والآن ، إذا لزم الأمر ، يمكنك فهم كل شيء آخر من خلال تحليل أجزاء التعليمات البرمجية المصدر لهذه المكتبة التي تهمك بشكل مستقل.
أعزائي القراء! هل تستخدم express.js؟
