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

Published on June 15, 2018

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

Ursprünglicher Autor: Soham Kamani
  • Übersetzung
Wenn Sie für die node.js-Plattform entwickelt haben, haben Sie wahrscheinlich von express.js gehört . Dies ist eines der beliebtesten, leichtgewichtigen Frameworks, das beim Erstellen von Webanwendungen für einen Knoten verwendet wird. Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, schlägt vor, die Merkmale der internen Struktur des Express-Frameworks anhand einer Analyse des Quellcodes und eines Verwendungsbeispiels zu untersuchen. Er glaubt, dass das Studium der Mechanismen, die populären Open-Source-Bibliotheken zugrunde liegen, zu einem tieferen Verständnis dieser Bibliotheken beiträgt, den Schleier des "Mysteriums" beseitigt und dazu beiträgt, bessere Anwendungen zu erstellen.





Es kann zweckmäßig sein, den Express-Quellcode beim Lesen dieses Materials bereit zu halten. Diese Version wird hier verwendet . Sie können diesen Artikel gut lesen, ohne den Expresscode zu öffnen, da hier, wo immer es angebracht ist, Fragmente des Codes dieser Bibliothek angegeben werden. An den Stellen, an denen der Code abgekürzt wird, werden Kommentare des Formulars verwendet.// ...

Grundlegendes Beispiel für die Verwendung von Express


Schauen Sie sich zunächst das traditionelle „Hello World!“ An, bei der Entwicklung neuer Computertechnologien - 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 Antwort Hello World!auf Anforderungen, die entlang der Route eingehen GET /. Wenn Sie nicht ins Detail gehen, können Sie vier Stufen des Geschehens auswählen, die wir analysieren können:

  1. Erstellen Sie eine neue Expressanwendung.
  2. Neue Route erstellen
  3. Starten Sie den HTTP-Server an der angegebenen Portnummer.
  4. Verarbeiten eingehender Anforderungen an den Server.

Erstellen einer neuen Expressanwendung


Mit dem Befehl var app = express()können Sie eine neue Expressanwendung erstellen. Die Funktion createApplicationaus der Datei lib / express.js ist eine standardmäßig exportierte Funktion, auf die wir durch Ausführen eines Funktionsaufrufs zugreifen express(). Hier sind einige wichtige Dinge, die Sie hier beachten 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 appvon dieser Funktion zurückgegebene Objekt ist eines der Objekte, die im Code unserer Anwendung verwendet werden. Eine Methode wird app.getmit der mixinBibliotheksfunktion merge-descriptors hinzugefügt , die für die Zuweisung der in appdeklarierten Methoden verantwortlich ist proto. Das Objekt selbst wird protoaus lib / application.js importiert .

Neue Route erstellen


Betrachten wir nun den Code , der für die Erstellung der Methode app.getaus 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 zu implementieren, wie app.get, app.post, app.putund dergleichen in Bezug auf Funktionalität, kann die gleiche in Betracht gezogen werden. Wenn Sie den obigen Code vereinfachen und ihn auf die Implementierung nur einer Methode reduzieren get, erhalten Sie etwa Folgendes:

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

Obwohl die obige Funktion zwei Argumente hat, ist sie der Funktion ähnlich app[method] = function(path){...}. Das zweite Argument ,, wird handlerdurch Aufruf erhalten slice.call(arguments, 1).

Kurz gesagt, app.<method>speichert es die Route im Router der Anwendung mithilfe seiner Methode routeund sendet sie anschließend handleran route.<method>.

Die Routermethode ist route()in lib / router / index.js angegeben :

// 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 Methodendeklaration route.getin lib / router / route.js wie eine Deklaration aussieht 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 Prozessoren haben, auf deren Basis eine Typvariable aufgebaut wird Layer, die eine Datenverarbeitungsschicht ist, die dann auf den Stapel geht.

Layer-Objekte


Und _router, und routedie Art der Objekte verwenden Layer. Um das Wesen eines solchen Objekts zu verstehen, betrachten wir seinen Konstruktor :

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

Beim Erstellen von Objekten vom Typ erhalten Layersie einen Pfad, bestimmte Parameter und eine Funktion. Bei unserem Router handelt es sich um diese Funktion route.dispatch(genauer werden wir im Folgenden näher darauf eingehen, allgemein gesagt, es ist beabsichtigt, eine Anfrage an eine separate Route zu senden). Bei der Route selbst handelt es sich bei dieser Funktion um eine Handlerfunktion, die im Code unseres Beispiels deklariert ist.

Jedes TypobjektLayer verfügt über eine handle_request- Methode , die für die Ausführung der Funktion verantwortlich ist, die während der Objektinitialisierung übergeben wird.

Erinnern Sie sich, was passiert, wenn Sie eine Route mit der Methode erstellen app.get:

  1. Im Anwendungsrouter ( this._router) wird eine Route erstellt.
  2. Die Route-Methode dispatchwird als Handler-Methode des entsprechenden Objekts zugewiesen Layerund dieses Objekt wird auf den Stack des Routers verschoben.
  3. Der Anforderungshandler wird als Handlermethode an das Objekt übergeben Layer, und dieses Objekt wird auf den Routenstapel verschoben.

Daher werden alle Handler in einer Instanz appals Objekte des Typs gespeichert Layer, die sich im Routenstapel befinden, deren Methoden dispatchObjekten zugeordnet Layersind, die sich im Stapel des Routers befinden:


Objekte vom Typ Layer im Stack des Routers und im Route-Stack

Empfangene HTTP-Requests werden entsprechend dieser Logik verarbeitet. Wir werden unten über sie sprechen.

Starten Sie den HTTP-Server


Nach dem Einrichten der Routen müssen Sie den Server starten. In unserem Beispiel verweisen wir auf die Methode app.listenund übergeben 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);
};

Es sieht so aus app.listen- es ist nur ein Wrapper http.createServer. Diese Sichtweise ist sinnvoll, denn wenn wir uns an das erinnern, was wir zu Beginn gesagt haben, appist dies nur eine Funktion mit einer Signatur function(req, res, next) {...}, die mit den erforderlichen Argumenten kompatibel ist http.createServer(die Signatur dieser Methode ist function (req, res) {...}).

Nachdem man verstanden hat, dass alles, was uns express.js gibt, letztendlich auf eine sehr intelligente Handlerfunktion reduziert werden kann, sieht das Framework nicht so kompliziert und geheimnisvoll aus, wie es früher war.

HTTP-Anforderungsverarbeitung


Wenn wir nun wissen, dass appdies nur ein Request-Handler ist, folgen wir dem Pfad, den eine HTTP-Anfrage durch die Express-Anwendung durchläuft. Dieser Weg führt ihn zu dem von uns angegebenen Handler.

Zuerst geht die Anfrage an die Funktion createApplication( lib / express.js ):

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

Dann geht es zur Methode 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);
};

Die Methode ist router.handlein 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);
      }
      // ...
    });
  }
};

Wenn Sie beschreiben, was in einer Nussschale passiert, router.handledurchläuft die Funktion alle Schichten im Stapel, bis sie eine findet, die mit dem in der Anforderung angegebenen Pfad übereinstimmt. Dann wird die Layer-Methode aufgerufen handle_request, die die vordefinierte Handlerfunktion ausführt. Diese Handlerfunktion ist eine Routenmethodedispatch , 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);
    // ...
  }
};

Auf dieselbe Weise wie im Fall eines Routers werden bei der Verarbeitung jeder Route die Layer dieser Route durchsucht und ihre Methoden handle_request, die die Layer-Handler-Methoden ausführen, werden aufgerufen. In unserem Fall ist dies der Anforderungshandler, der im Anwendungscode deklariert ist.

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


Der Anforderungspfad in der Expressanwendung

Ergebnisse


Hier haben wir nur die grundlegenden Mechanismen der Bibliothek express.js behandelt, die für den Betrieb des Webservers verantwortlich sind, aber diese Bibliothek hat viele andere Funktionen. Wir haben uns nicht auf die Überprüfungen konzentriert, die die Anforderungen bestehen, bevor sie von den Handlern empfangen werden. Wir haben nicht über die Hilfsmethoden gesprochen, die beim Arbeiten mit Variablen resund verfügbar sind req. Und schließlich haben wir keine der mächtigsten Expressfunktionen angesprochen. Dabei handelt es sich um die Verwendung von Middleware, mit der praktisch jede Aufgabe gelöst werden kann - von der Analyse von Anforderungen bis zur Implementierung eines vollwertigen Authentifizierungssystems.

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

Liebe Leserinnen und Leser! Verwenden Sie express.js?