REST-Webservices mit Node.js, Teil 1: Connect als Fundament

Seite 2: Fehler-Handling & Architektur

Inhaltsverzeichnis

Tritt innerhalb eines Moduls ein Fehler auf, besteht das vermeintlich korrekte Vorgehen darin, eine Ausnahme mit dem SchlĂĽsselwort throw zu werfen. Das geht prinzipiell auch in Verbindung mit Connect, erzielt allerdings nicht den gewĂĽnschten Effekt. Deswegen ist ein alternativer Weg einzuschlagen: Statt die Ausnahme direkt zu werfen, bekommt sie der Aufruf der next-Funktion als Parameter ĂĽbergeben:

function (req, res, next) {
var err = new Error();
next(err);
}

Auf den ersten Blick scheint das Vorgehen äquivalent zu sein, tatsächlich besteht jedoch ein wesentlicher Unterschied:

  • Das Werfen einer Ausnahme mit dem throw-SchlĂĽsselwort funktioniert nicht innerhalb eines asynchronen Callbacks, da Connect in dem Fall nicht in der Lage ist, die Ausnahme abzufangen.
  • Wird hingegen die next-Funktion aufgerufen, behält Connect die Kontrolle und kann die Ausnahme entsprechend behandeln.

Im Fall einer Ausnahme sucht Connect nach einem Fehlermodul und setzt die AusfĂĽhrung dort fort. Fehlermodule weisen im Vergleich zu normalen Modulen eine leicht modifizierte Signatur auf, da sie als ersten Parameter die Ausnahme erwarten:

function (err, req, res, next)

Tritt ein Fehler auf, erfolgt die Ausführung direkt im Fehlermodul, die aller übrigen Module wird übersprungen. Tritt hingegen kein Fehler auf, verhält sich Connect umgekehrt: Fehlermodule werden von der normalen Ausführung ausgeschlossen und daher nicht aufgerufen. Das stellt sicher, dass Fehlermodule ausschließlich ausgeführt werden, wenn Bedarf besteht.

Die Installation von Connect geschieht mit einem Aufruf des Node Package Manager (npm), wobei sie lokal im Kontext der Webanwendung erfolgt:

$ npm install connect

Anschließend lässt sich Connect mit der require-Funktion importieren:

var connect = require('connect');

Im Vergleich zum sonst üblichen Vorgehen erhält die createServer-Funktion des http-Moduls in Verbindung mit Connect keinen handgeschriebenen, sondern einen von Connect vorgefertigten Callback. Dieser lässt sich über den Aufruf von Connect als Funktion abrufen. Anschließend wird er der createServer-Funktion auf dem üblichen Weg übergeben:

var app = connect();
http.createServer(app).listen(3000);

Da sich Connect in die Funktionen des http-Moduls integriert, kann der Entwickler alternativ oder ergänzend auch einen HTTPS-Server auf dem gleichen Weg starten. Allerdings erfüllt diese Anwendung keinen Zweck, da sie keine Funktionen implementiert. Das lässt sich jedoch leicht nachrüsten, indem man eine entsprechende Funktion registriert. Hierfür dient die use-Funktion, die Connect an der Funktion app bereitstellt:

var app = connect();
app.use(function (req, res) {
res.writeHead(200, {
'content-type': 'text/html'
});
res.end('Hallo Welt!');
});
http.createServer(app).listen(3000);

Diese Webanwendung lässt sich nun durch das mehrfache Aufrufen der use-Funktion um Module ergänzen. Die Reihenfolge der Aufrufe bestimmt dabei die der Verarbeitung der Module:

var app = connect();
app.use(function (req, res, next) {
console.log('Pre');
next();
console.log('Post');
});
app.use(function (req, res) {
res.writeHead(200, {
'content-type': 'text/html'
});
res.end('Hallo Welt!');
});
http.createServer(app).listen(3000);

Bei Aufruf der Anwendung im Webbrowser oder per curl gibt die Konsole die Texte Pre und Post aus. Das erfolgt jeweils vor beziehungsweise nach der eigentlichen Verarbeitung der Anfrage.

Um die Lesbarkeit des Codes zu erhöhen, lassen sich die einzelnen Aufrufe von use verketten, sodass die wiederholte Angabe der app-Funktion erfolgt:

var app = connect()
.use(function (req, res, next) {
console.log('Pre');
next();
console.log('Post');
})
.use(function (req, res) {
res.writeHead(200, {
'content-type': 'text/html'
});
res.end('Hallo Welt!');
});
http.createServer(app).listen(3000);

Unter Umständen soll verhindert werden, dass ein Modul aufgrund wiederholten Aufrufens der use-Funktion mehrfach verwendet wird. Die einfachste Möglichkeit, das zu garantieren, ist das Prüfen auf die Existenz einer Eigenschaft, die das Modul der eingehenden Anfrage hinzugefügt hat: Existiert sie, läuft das Modul bereits. Die erneute Ausführung lässt sich dann überspringen, stattdessen kommt es zum Aufruf des nachfolgenden Moduls:

var app = connect()
.use(function (req, res, next) {
if (req.foo) return next();
req.foo = true;
console.log('Pre');
next();
console.log('Post');
})
[...]

Nachteilig an dem Vorgehen ist die Tatsache, dass die hinzugefĂĽgte Eigenschaft innerhalb der Anfrage auch fĂĽr andere Module zugreifbar und daher quasi global ist.

Im vorangegangenen Beispiel wurde das Modul direkt der use-Funktion übergeben. Häufig lädt man Module jedoch aus externen Dateien und sie sind gegebenenfalls noch zu konfigurieren. Hierzu bietet sich an, das eigentliche Modul in eine Set-up-Funktion zu verpacken, die das Modul zunächst wunschgemäß konfiguriert und danach zurückgibt:

var foo = function (options) {
return function (req, res, next) {
// Konfigurieren ...
};
};

Die Set-up-Funktion kann dabei prinzipiell über beliebige Parameter verfügen. Da das Modul aufgrund des Function Scoping von JavaScript eine Closure darstellt, kann es auf sämtliche Parameter der Set-up-Funktion zugreifen und sein Verhalten dementsprechend konfigurieren.

Unter Umständen kann es wünschenswert sein, einige Module in Abhängigkeit der aufgerufenen Adresse auszuführen. Beispielsweise könnte gefordert sein, dass einige Module nur für Adressen unterhalb des /admin-Verzeichnisses ausgeführt werden. Zu dem Zweck lässt sich der use-Funktion außer dem Modul auch eine Route übergeben, die die Gültigkeit des Moduls auf Adressen einschränkt:

app.use('/admin', foo());

Dabei gilt, dass das Modul foo fĂĽr alle Anfragen ausgefĂĽhrt wird, die sich auf das Verzeichnis /admin oder ein Unterverzeichnis davon beziehen.