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?
