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:
- Erstellen Sie eine neue Express-Anwendung.
- Erstellen Sie eine neue Route.
- Starten des HTTP-Servers unter der angegebenen Portnummer.
- 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:
- Eine Route wird im Router der Anwendung (
this._router ) erstellt. - Die
dispatch wird als Handlermethode des entsprechenden Layer Objekts zugewiesen, und dieses Objekt wird auf den Router-Stack verschoben. - 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-StackEingehende 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 anfordernZusammenfassung
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?
