Funktionen der Arbeit und des internen Geräts express.js

Wenn Sie für die Plattform node.js entwickelt haben, haben Sie wahrscheinlich von express.js gehört . Dies ist eines der beliebtesten Frameworks für die Erstellung von Webanwendungen für Knoten.



Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, bietet an, die Merkmale der internen Struktur des Express-Frameworks durch Analyse seines Quellcodes und Berücksichtigung eines Beispiels seiner Verwendung zu untersuchen. Er glaubt, dass die Untersuchung der Mechanismen, die den beliebten Open-Source-Bibliotheken zugrunde liegen, zu einem tieferen Verständnis dieser Mechanismen beiträgt, den Vorhang des „Geheimnisses“ von ihnen entfernt und dazu beiträgt, bessere Anwendungen auf der Grundlage dieser Mechanismen zu erstellen.

Es kann zweckmäßig sein, den Express-Quellcode beim Lesen dieses Materials griffbereit zu halten. Diese Version wird hier verwendet. Sie können diesen Artikel lesen, ohne den Express-Code zu öffnen, da hier gegebenenfalls Codefragmente dieser Bibliothek angegeben werden. An den Stellen, an denen der Code abgekürzt ist, werden Kommentare der Form // ...

Ein grundlegendes Beispiel für die Verwendung von Express


Schauen wir uns zunächst die „Hallo Welt!“ An, die bei der Entwicklung neuer Computertechnologien traditionell ist - ein Beispiel. Es ist auf der offiziellen Website des Frameworks zu finden und dient als Ausgangspunkt für unsere Forschung.

 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!')) 

Dieser Code startet einen neuen HTTP-Server an Port 3000 und sendet eine Hello World! auf Anfragen, die auf dem GET / Route empfangen wurden. Wenn Sie nicht auf Details eingehen, können wir vier Phasen des Geschehens unterscheiden, die wir analysieren können:

  1. Erstellen Sie eine neue Express-Anwendung.
  2. Erstellen Sie eine neue Route.
  3. Starten des HTTP-Servers unter der angegebenen Portnummer.
  4. Verarbeitung eingehender Anfragen an den Server.

Erstellen einer neuen Expressanwendung


Mit dem Befehl var app = express() können Sie eine neue Express-Anwendung erstellen. Die Funktion createApplication aus der Datei lib / express.js ist die standardmäßig exportierte Funktion. Wir greifen darauf zu, indem wir die Funktion express() aufrufen. Hier sind einige wichtige Dinge, auf die Sie achten sollten:

 // ... 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; } 

Das von dieser Funktion zurückgegebene app Objekt ist eines der in unserem Anwendungscode verwendeten Objekte. Die app.get Methode app.get mithilfe der mixin Funktion der Merge-Descriptors- Bibliothek hinzugefügt, die für die Zuweisung der in proto deklarierten app Methoden verantwortlich ist. Das proto Objekt selbst wird aus lib / application.js importiert.

Erstellen Sie eine neue Route


Schauen wir uns nun den Code an , der für die Erstellung der app.get Methode aus unserem Beispiel verantwortlich ist.

 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; }; }); 

Es ist interessant festzustellen, dass zusätzlich zu den semantischen Merkmalen alle Methoden, die HTTP-Aktionen implementieren, wie z. B. app.get , app.post , app.put und dergleichen, hinsichtlich der Funktionalität als gleich angesehen werden können. Wenn Sie den obigen Code vereinfachen und ihn auf die Implementierung nur einer get Methode reduzieren, erhalten Sie Folgendes:

 app.get = function(path, handler){ // ... var route = this._router.route(path); route.get(handler) return this } 

Obwohl die obige Funktion 2 Argumente hat, ähnelt sie der Funktion app[method] = function(path){...} . Das zweite Argument, handler , wird durch Aufrufen von slice.call(arguments, 1) .

Kurz gesagt, app.<method> speichert die Route nur im Anwendungsrouter mithilfe ihrer Routenmethode und übergibt den handler an route.<method> .

Die Router-Methode route() ist in lib / router / index.js deklariert :

 // 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; }; 

Es überrascht nicht, dass die Deklaration der route.get Methode in lib / router / route.js der Deklaration von 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; }; }); 

Jede Route kann mehrere Handler haben. Auf der Grundlage jedes Handlers wird eine Variable vom Typ Layer , bei der es sich um eine Datenverarbeitungsschicht handelt, die dann auf den Stapel gelangt.

Ebenenobjekte


Sowohl _router als auch route verwenden Objekte vom Typ Layer . Um die Essenz eines solchen Objekts zu verstehen, schauen wir uns seinen Konstruktor an :

 function Layer(path, options, fn) { // ... this.handle = fn; this.regexp = pathRegexp(path, this.keys = [], opts); // ... } 

Beim Erstellen von Objekten vom Typ Layer sie einen Pfad, bestimmte Parameter und eine Funktion. Bei unserem Router handelt es sich bei dieser Funktion um route.dispatch (wir werden im Folgenden näher darauf route.dispatch Im Allgemeinen dient sie dazu, eine Anforderung an eine separate Route zu senden). Im Fall der Route selbst ist diese Funktion eine Handlerfunktion, die im Code unseres Beispiels deklariert ist.

Jedes Objekt vom Typ Layer verfügt über eine handle_request- Methode, die für die Ausführung der Funktion verantwortlich ist, die bei der Initialisierung des Objekts übergeben wurde.

Erinnern Sie sich daran, was beim Erstellen einer Route mit der Methode app.get passiert:

  1. Eine Route wird im Router der Anwendung ( this._router ) erstellt.
  2. Die dispatch wird als Handlermethode des entsprechenden Layer Objekts zugewiesen, und dieses Objekt wird auf den Router-Stack verschoben.
  3. Der Anforderungshandler wird als Handlermethode an das Layer Objekt übergeben, und dieses Objekt wird auf den Routenstapel übertragen.

Infolgedessen werden alle Handler in der app Instanz in Form von Objekten des Layertyps gespeichert, die sich im Routenstapel befinden und deren dispatch Layer im Router-Stack zugewiesen sind:


Layer-Objekte auf dem Router-Stack und dem Route-Stack

Eingehende HTTP-Anfragen werden gemäß dieser Logik verarbeitet. Wir werden unten darüber sprechen.

Start des HTTP-Servers


Nach dem Konfigurieren der Routen müssen Sie den Server starten. In unserem Beispiel wenden wir uns der Methode app.listen und übergeben ihr die Portnummer und die Rückruffunktion als Argumente. Um die Funktionen dieser Methode zu verstehen, können wir auf die Datei lib / application.js verweisen:

 app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; 

app.listen nur ein Wrapper um http.createServer . Eine solche Sichtweise ist sinnvoll, denn wenn Sie sich an das erinnern, worüber wir am Anfang gesprochen haben, ist die app nur eine Funktion mit der Signaturfunktion function(req, res, next) {...} , die mit den für http.createServer erforderlichen Argumenten kompatibel ist http.createServer (die Signatur dieser Methode ist function (req, res) {...} ).

Nachdem wir erkannt haben, dass am Ende alles, was express.js uns gibt, auf einen sehr intelligenten Funktionshandler reduziert werden kann, sieht das Framework nicht mehr so ​​kompliziert und mysteriös aus wie zuvor.

HTTP-Anforderungsverarbeitung


Nachdem wir wissen, dass die app nur ein Anforderungshandler ist, folgen wir dem Pfad, den eine HTTP-Anforderung in einer Expressanwendung übergibt. Dieser Weg führt zu dem von uns deklarierten Handler.

Zunächst geht die Anforderung an die Funktion createApplication ( lib / express.js ):

 var app = function(req, res, next) {   app.handle(req, res, next); }; 

Dann geht es zur app.handle Methode ( lib / application.js ):

 app.handle = function handle(req, res, callback) { // `this._router` -  ,    ,  `app.get` var router = this._router; // ... //     `handle` router.handle(req, res, done); }; 

Die router.handle Methode router.handle in lib / router / index.js deklariert :

 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 gesagt, die Funktion router.handle durchläuft alle Ebenen auf dem Stapel, bis eine gefunden wird, die dem in der Anforderung angegebenen Pfad entspricht. Anschließend wird die Layer-Methode handle_request aufgerufen, die die vordefinierte handle_request ausführt. Diese Handlerfunktion ist eine dispatch , die in lib / route / route.js deklariert ist :

 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);   // ... } }; 

Genau wie im Fall des Routers werden während der Verarbeitung jeder Route die Layer dieser Route aufgelistet und ihre handle_request Methoden, die die Layer-Handler-Methoden ausführen, handle_request . In unserem Fall ist dies ein Anforderungshandler, der im Anwendungscode deklariert ist.

Hier fällt schließlich die HTTP-Anfrage in den Codebereich unserer Anwendung.


Pfad in Express-Anwendung anfordern

Zusammenfassung


Hier haben wir nur die grundlegenden Mechanismen der Bibliothek express.js untersucht, die für den Betrieb des Webservers verantwortlich sind. Diese Bibliothek verfügt jedoch auch über viele andere Funktionen. Wir haben nicht bei den Überprüfungen angehalten, die die Anforderungen durchlaufen, bevor sie die Handler erreichen, und wir haben nicht über die Hilfsmethoden gesprochen, die bei der Arbeit mit den Variablen res und req verfügbar sind. Und schließlich haben wir keine der mächtigsten Funktionen von Express angesprochen. Es besteht in der Verwendung von Middleware, mit der nahezu jedes Problem gelöst werden kann - von der Analyse von Anforderungen bis zur Implementierung eines vollständigen Authentifizierungssystems.

Wir hoffen, dieses Material hat Ihnen geholfen, die Hauptfunktionen des Express-Geräts zu verstehen, und jetzt können Sie bei Bedarf alles andere verstehen, indem Sie die Teile des Quellcodes dieser Bibliothek, die Sie interessieren, unabhängig analysieren.

Liebe Leser! Verwenden Sie express.js?

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


All Articles