مميزات العمل والجهاز الداخلي express.js

إذا كنت تقوم بالتطوير من أجل 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 / الطريق. إذا لم تدخل في التفاصيل ، فيمكننا التمييز بين أربع مراحل لما يحدث ، والتي يمكننا تحليلها:

  1. إنشاء تطبيق صريح جديد.
  2. إنشاء طريق جديد.
  3. بدء خادم HTTP على رقم المنفذ المحدد.
  4. معالجة الطلبات الواردة إلى الخادم.

إنشاء تطبيق صريح جديد


يتيح لك الأمر 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 :

  1. يتم إنشاء مسار في موجه التطبيق ( this._router ).
  2. يتم تعيين طريقة توجيه dispatch كطريقة معالج لكائن Layer المقابلة ، ويتم دفع هذا الكائن إلى مكدس جهاز التوجيه.
  3. يتم تمرير معالج الطلب إلى كائن 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؟

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


All Articles