JavaScript überall – Webserverprogrammierung mit Node.js
Kommentare

Mit Node.js findet JavaScript auch auf dem Webserver eine immer größere Verbreitung, denn Webentwickler können damit auf Client und Server die gleiche Programmiersprache verwenden. Zudem zeichnet sich Node.js gerade durch den Verzicht auf Abstraktion und eine hohe Skalierbarkeit aus. Dieser Beitrag erstellt die Grundlagen von Node.js dar, gibt einen Überblick für die wichtigsten Node.js-Erweiterungspakete und behandelt auch die Integration von Node.js mit .NET über Edge.js.

Die ersten Versionen von Node.js stammen von dem Webentwickler Ryan Dahl aus dem Jahr 2009. Er machte sich damals auf die Suche nach einem effizienten Webserverframework, dass sehr viele Anfragen parallel bearbeiten kann, zum Beispiel vor dem Hintergrund einer Vielzahl von Clients, die vom Server einen Status via Polling erfragen müssen. Nachdem er bei einigen Versuchen auf der Basis der Programmiersprachen C, Lua und Haskel gescheitert war, kam er zu JavaScript, das auf dem Prinzip der asynchronen, nicht blockierenden Operationen basiert. Da JavaScript aber kein API für externe IO-Operationen enthält, entwickelte er zu diesem Zweck einen einfachen Abstraction-Layer und eine Core-Bibliothek, die selbst in JavaScript geschrieben ist. Dies gab ihm die Möglichkeit, konsequent auf nicht blockierende und ereignisgetriebene IO-Operationen zu setzen.

Node.js verwendet nur einen einzigen Prozess und nur einen einzigen Kern-Thread für die Ausführung von JavaScript-Code, was also heißt, dass Node.js eigentlich immer nur genau eine Anfrage zu einem Zeitpunkt bearbeiten kann. Alle anderen Anfragen müssten warten. Dennoch kann Node.js viele Anfragen scheinbar gleichzeitig bearbeiten, denn Node.js delegiert viele Aufgaben direkt an das Betriebssystem oder die Datenbank, d. h. Node.js schickt z. B. das Kommando zum Schreiben der Datei an das Betriebssystem oder das Aktualisieren eines Datensatzes an die Datenbank und registriert einen „Callback“, mit dem das Betriebssystem bzw. die Datenbank Node.js darüber informiert, dass die Aktion durchgeführt worden ist und Node.js nun die nächsten Schritte starten kann. Währenddessen kann Node.js eine andere Anfrage aus der Node.js Event Queue (Abb. 1) bearbeiten. Dies kann entweder ein Callback eines anderen Threads sein (siehe rote Anfrage in Abb. 1) oder eine ganz neue Anfrage (siehe gelbe Anfrage in Abb. 1). Alle Aufgaben für den Node.js-Kern-Thread laufen über die Event Queue. So muss sich auch der grüne Callback des Datenbank-Threads in die Event Queue stellen, wo er dann aber ohne Wartezeit entnommen und vom Node.js-Kern-Thread ausgeführt wird, da dieser gerade nichts zu tun hat.

Abb. 1: Architekturschaubild der asynchronen Architektur von node.js

Dieses Node.js-Prinzip bedeutet auch, dass Node.js immer dann sehr gut geeignet ist, wenn eine Webentwicklung viele IO-Operationen nutzt. Node.js ist hingegen schlecht für Anwendungen, bei denen Berechnungen direkt im Webserver stattfinden sollen. Solche Berechnungen würden alle anderen Anfragen aufhalten. Node.js sollte solche Tätigkeiten immer an einen anderen Thread oder Prozess delegieren. Die in Node.js eingesetzte JavaScript-Engine ist Googles V8.

Node.js ist ein Open-Source-Projekt, dessen Entwicklung von der Firma Joyent finanziell und durch eigene Entwickler im Core-Team unterstützt wird. Insgesamt haben schon mehr als 500 Entwickler Beiträge zum Kern von Node.js geliefert. Als so genannter „Gatekeeper“ fungiert mittlerweile Isaac Schlüter, ein angestellter Entwickler bei Joyent. Joyent betreibt zusammen mit Nodejitsu auch die Node.js-Erweiterungsmodulbibliothek „Node Package Manager“ (npm), in der Anfang Dezember 2013 schon mehr als 53 000 Erweiterungspakete registriert waren. Die große Zahl der Erweiterungspakete ist aber unter drei Blickwinkeln zu bewerten: Erstens sind die Kernfunktionen von Node.js sehr überschaubar. Zweitens gibt es viele Erweiterungen, die nur eine Hand voll Funktionen anbieten. Drittens gibt es viele Überschneidungen in den Erweiterungen.

Die aktuelle Version von Node.js trägt die Versionsnummer 0.10.24. Dass die Version mit einer 0 beginnt, basiert auf der Tatsache, dass sich die Entwickler der Node.js-Kernbibliothek immer noch Änderungen an den APIs vorbehalten. Erst wenn diese Änderungen abgeschlossen sind, soll es eine Version 1.0 geben.

Aufgrund seines einfachen Entwicklungskonzepts, der Möglichkeit, auf die große Menge an hochwertigen und leistungsfähigen Bibliotheken zugreifen zu können, und nicht zuletzt der Verwendung von JavaScript auf dem Client (Browser) und dem Server, ist der Einsatz von Node.js mittlerweile bei Webanwendungen eine beliebte Wahl. Die Entwicklung von Webanwendungen ist dabei aber nicht die einzige Domäne, in der Node.js zum Einsatz kommt, dies zeigt die Entwicklung eines SMTP-Server von sites“ class=“elf-external elf-icon“ rel=“nofollow“>Craiglist, der mehr als 50 000 000 Benutzer bedienen kann. Auch Firmen wie Paypal haben den einfachen Entwicklungsstil von Node.js zu schätzen gelernt und steigen von der bisherigen Java-Entwicklung um.

Es gibt inzwischen zahlreiche Hosting-Angebote für Node.js, die die Website auflistet. Darunter ist auch Microsofts Cloud-Angebot „Windows Azure.“. Das „Node.js Developer Center“ zeigt, wie man Node.js in Windows Azure nutzen kann. Zur Unterstützung bietet Microsoft ein Erweiterungspaket mit Namen „Azure“ an, dass den Zugriff auf Azure-Dienste wie den Table Storage, den Blog Storage, Queues, den Service Bus, DH Insight, SQL Azure, Notification Hubs und die Service Runtime unterstützt.

Um mit der Entwicklung beginnen zu können, ist zuerst die aktuelle Version von Node.js zu laden. Die unterstützten Plattformen von Node.js umfassen alle heute gängigen Betriebssysteme (Linux, Mac OS X, Windows, Solaris). Bei der Windows-Installation sollte man die Frage, ob der Pfad zu node.exe und dem Paketmanager zu den Umgebungsvariablen hinzugeführt werden sollen, mit „ja“ beantworten, um später Schwierigkeiten bei der Verwendung zu vermeiden.

Eine Konfiguration für den Node.js Prozess erfolgt über Windows-Umgebungsvariablen. Es haben sich zur Konfiguration des Ports und der Umgebung die beiden Variablen PORT und NODE_ENV etabliert. Diese können dann aus dem Node.js-Programm mit den Prozessvariablen process.env.PORT und process.env.NODE_ENV abgerufen werden.

Aufmacherbild: earth blue shining von Shutterstock / Urheberrecht: romrf

[ header = Seite 2: Hello World in Node.js]

Hello World in Node.js

Zur Erstellung des ersten Node.js-Programms reicht ein einfacher Texteditor. Listing 1 implementiert einen Webserver, der bei allen Anfragen „Hallo Welt“ zum Browser liefert. In Listing 1 wird in der ersten Zeile das HTTP-Modul aus der Standardbibliothek von Node.js eingebunden. Mit diesem Modul wird die Erstellung von HTTP-Servern ermöglicht und das Empfangen und das Versenden von Daten sowie der Umgang mit den HTTP-Objekten vereinfacht. In den Zeilen 3 bis 6 wird eine JavaScript-Funktion anfrageBearbeiter definiert, mit der die Anfragen bearbeitet werden. In dieser wird ein HTTP-Header erzeugt und danach der Text Hallo Welt als Inhalt gesendet.

Das eigentliche Hauptprogramm beginnt in Zeile 8 mit der Erstellung einer HTTP-Serverinstanz, die die zuvor erstellte Funktion anfrageBearbeiter als Parameter erhält. Mit Zeile 10 wird der Server gestartet, um auf dem Port 3000 zu lauschen und Anfragen entgegenzunehmen, zusätzlich wird eine Callback-Funktion übergeben, die dann, wenn das Starten des Server erfolgreich war, eine Ausgabe auf der Konsole erzeugt. Um diesen HTTP-Server zu starten, ruft man ihn nun einfach mit der Anwendung node.exe an der Kommandozeile auf: C:Program Filesnodejsnode.exe H:TFSDemoJSnodeJSNodejsWebNodejsWeb1b HelloWorldServer.js.

Listing 2 zeigt schon einen etwas realitätsnäheren HTTP-Server, der einen URL-Parameter mithilfe der Kernbibliothek url ausliest und diesen Wert in die Ausgabe einbaut. Die Portnummer kommt nun wahlweise aus der Umgebungsvariablen PORT. Für das Kodieren der HTML-Ausgabe mit der Funktion htmlEncode() ist bereits ein Node.js-Erweiterungsmodul mit Namen htmlencode notwendig. Eine entsprechende Funktion gibt es im Kern von Node.js leider nicht, was Nutzer in der Microsoft-Welt verwundern wird, denn dort gibt es eine entsprechende Funktion seit den Zeiten von Active Server Pages Mitte der Neunziger Jahre. Listing 3 zeigt eine Rückgabe im JSON-Format.

var http = require('http');

// aus der Anfrage die Antwort erzeugen
var anfrageBearbeiter = function (req, res) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
   res.write('Hallo Weltn');
    res.end(); }

// Server erzeugen
var server = http.createServer(anfrageBearbeiter);
// auf einem Port lauschen
server.listen(3000, function() {
    console.log("Server lauscht auf ht tp://localhost: 3000");
});
var http = require('http');
var url = require('url');
var htmlencode = require('htmlencode');
var port = process.env.PORT || 3000;

var anfrageBearbeiter = function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});
  // Auswertung des Querystring
  var parsed_url = url.parse(req.url, true)
  // HTML-Ausgabe
  res.write("");
  res.write("

Hallo Welt mit Querystring und HTML-Ausgabe

"); res.write("

Hallo " + htmlencode.htmlEncode(parsed_url.query.name) + "

"); res.write(""); res.write(""); res.end(); }; var server = http.createServer(anfrageBearbeiter); server.listen(port, function() { console.log("Server aufrufen mit ht tp://localhost:" + port +"?name="); });
var http = require('http');
var url = require('url');

var port = process.env.PORT || 3000;
var anfrageBearbeiter = function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/json'});
  var parsed_url = url.parse(req.url, true);
  var result = {
    gruss: "Hallo " + parsed_url.query.name,
    name: parsed_url.query.name
  };
  res.end(JSON.stringify(result));
};
var server = http.createServer(anfrageBearbeiter);
server.listen(port, function() {
    console.log("Server aufrufen mit ht tp://localhost:" + port + "?name=");
});

Erweiterungspakete

Mit den Kernpaketen von Node.js allein zu arbeiten, wäre also recht mühsam für echte Projekte. Um auf die vielen Tausend Erweiterungen auf https://npmjs.org/ zuzugreifen, gibt es den Node Package Manager (npm), der schon zusammen mit dem Node-Installationspaket auf den Rechner wandert. Bei der Installation eines Pakets mit npm kann der Entwickler zwischen unterschiedlichen Arten wählen. Die einfachste Variante ist die lokale Installation mit dem Befehl npm install , dabei wird das Node.js-Modul im Ordner node_modules installiert. Für das spätere Deployment unterstützt npm bei der Installation der Pakete drei Optionen zum Speichern der Abhängigkeiten in einer Paketdatei (package.json: Listing 4). Um die Pakete als zwingende Abhängigkeit zu installieren, ist die Option –save zu verwenden (npm install express –save). Damit wird das Paket unter dependencies installiert. Wenn das Paket nur zur Entwicklungszeit verwendet werden soll, kann dies mit der Option –save-dev erzielt werden (npm install karma –save-dev). Als dritte Variante können optionale Pakete mit –save-optional installiert werden.

Wenn im Anwendungsverzeichnis eine Datei package.json vorhanden ist, können alle dort eingetragenen Pakete mit einem einzigen Befehl npm install installiert werden. Auf dem Produktionsserver können die Entwicklungspakete mit dem Schalter –production ausgeschlossen werden. Als Pakete kann auf Namen aus der NPM-Registry verwiesen werden, es kann von einem Git Repository aus installiert werden oder ein TAR-Archiv angegeben werden, das lokal zur Verfügung steht. Es gibt zusätzlich eine Möglichkeit, Pakete global zu installieren, dies wird durch die Angaben des Schalters -g erreicht. Damit ist zurückhaltend umzugehen, da dies auf alle Anwendungen Einfluss haben kann.

Pakete können Abhängigkeiten zu anderen Paketen besitzen. Diese löst npm automatisch auf. Da Node.js nur einen sehr kleinen Kern besitzt, werden in realen Anwendungen typischerweise zwischen 10 und 30 Module verwendet, was durch das ausgereifte Paket- und Abhängigkeitssystem von npm aber in der Regel völlig problemlos ist.

{
  "name": "einBeispiel",
  "version": "0.0.1",
  "dependencies": {
    "express": "~3.4.6"
  },
  "devDependencies": {
    "karma-script-launcher": "~0.1.0",
    "karma-chrome-launcher": "~0.1.1",
    "karma-html2js-preprocessor": "~0.1.0",
    "karma-firefox-launcher": "~0.1.2",
    "karma-jasmine": "~0.1.4",
    "karma-coffee-preprocessor": "~0.1.1",
    "requirejs": "~2.1.9",
    "karma-requirejs": "~0.2.0",
    "karma-phantomjs-launcher": "~0.1.1",
    "karma": "~0.10.8"
  },
  "optionalDependencies": {
    "forever": "~0.10.10"
  }
}

[ header = Seite 3: Express.js]

Express.js

Den Beispielen in Listing 1 bis 3 fehlte völlig die Unterscheidung in verschiedene Ressourcen. Der Webserver liefert immer die gleiche Antwort, egal welche relative URL man verwendet. In Node.js definiert man Routen für die verschiedenen Ressourcen. Mit den eingebauten Bibliotheken von Node.js ist solch ein Routing recht aufwändig. Mithilfe der Node.js-Erweiterung Express.js können routenbasierte Webanwendungen auf sehr einfache Weise erstellt werden, ohne dabei die Kontrolle und den Zugriff auf die Komponenten des HTTP-Protokolls zu verlieren. Express.js baut seinerseits auf dem Paket Connect.js auf. Nach einem „npm install express“ ist die in Listing 5 gezeigte „Hello World“-Express-Anwendung möglich.

Zu Beginn entsteht mit dem Aufruf der express()-Funktion eine Anwendungsinstanz. Anschließend werden drei Routen ‚/‘, ‚/about‘ und ‚/autoren‘ definiert und für jeweils eine Antwort auf eine HTTP-GET-Anfrage festgelegt. Alternativ bietet das app-Objekt entsprechende Methoden für andere HTTP-Verben an: post, delete, merge, usw. Auch den Start des HTTP-Servers übernimmt hier mit listen() das app-Objekt.

var express = require('express');
var app = express();
var port = process.env.PORT || 3000;
app.get('/', function(req, res) {
    var body = 'Hello World';
    res.setHeader('Content-Type', 'text/plain');
    res.setHeader('Content-Length', Buffer.byteLength(body));
    res.end(body);
});

app.get('/about', function(req, res) {
    var body = 'Hello World-Demo-Anwendung';
    res.setHeader('Content-Type', 'text/plain');
    res.setHeader('Content-Length', Buffer.byteLength(body));
    res.end(body);
});

app.get('/autoren', function(req, res) {
    var body = 'Dr. Holger Schwichtenberg und Martin Möllenbeck';
    res.setHeader('Content-Type', 'text/plain');
    res.setHeader('Content-Length', Buffer.byteLength(body));
    res.end(body);
});
app.listen(port);
console.log("Server lauscht auf ht tp://localhost:" + port);

Für komplexere Anwendungen muss man die Ressourcen in verschiedene Dateien teilen. Express schlägt hier eine Struktur vor (Abb. 2):

  • Im Verzeichnis public werden die statischen Dateien (auch Assets genannt) abgelegt. Die entsprechenden Unterordner sind durch die Namen selbsterklärend.
  • Im Verzeichnis routes werden die Routen für die HTTP-Anfrage abgelegt.
  • Im Verzeichnis views werden die Templates für die Ausgabe von HTML-Dokumenten abgelegt.
  • Die Datei app.js beinhaltet das Starten der Webanwendung.

Die Ordnerstruktur aus Abbildung 2 kann man sich automatisch mit dem Kommando express Webname erstellen lassen, nachdem man die Batchdatei express.bat mit npm install express -g global installiert hat. Anschließend ist in das Verzeichnis zu wechseln und durch die Eingabe von npm install werden alle abhängigen npm-Module installiert. Die Anwendung kann dann mittels node app.js gestartet werden. In der aktuellen Version von Express.js (3.4.7) und der Template-Engine „jade“ (1.0.1) erzeugt der Generator leider eine fehlerhafte Datei (./views/layout.jade). Hier ist die erste Zeile (doctype 5) zu entfernen oder durch doctype html zu ersetzen (Listing 6).

Abb. 2: Verzeichnis einer einfachen Express-Anwendung

doctype html
html
  head
    title= title
    link(rel='stylesheet', href="/stylesheets/style.css?c87a2d")
  body
    block content

Die generierte Datei app.js (Listing 7.1) ist wie folgt aufgebaut: In den Zeilen 1 bis 5 werden die benötigten Module geladen. In der Zeile 7 wird die Anwendungsinstanz erstellt. Diese Instanz wird in den Zeilen 10 bis 19 konfiguriert. Die wichtigsten Einstellungen sind die direkte Ausgabe von JSON in der Zeile 15 und die Auslieferung von statischen Dateien, die im Unterverzeichnis public abgelegt worden sind. Die Zeilen 22 bis 24 bestimmen, dass eine Ausgabe des gesamten Fehlers inkl. Stacktrace im Browser erfolgt, wenn die Anwendung im Entwicklungsmodus gestartet wird. Diese Einstellung sollte in der Produktion nicht verwendet werden, da sie Angreifern eine Übersicht der Anwendung geben kann und mögliche Schwachstellen offen legen würde. Um die Anwendung im Produktionsbetrieb zu verwenden, ist die Umgebungsvariable NODE_ENV auf den Wert production zu setzen. In den Zeilen 26 und 27 werden die entsprechenden Routen (‚/‘ und ‚/users‘), an die in den Dateien (routes/index.js und routes/users.js, Listing 7.2 und 7.3) definierten Handler übergeben. Die Anwendung wird in den Zeilen 29 bis 31 gestartet, indem eine HTTP-Serverinstanz erstellt und die Express-Anwendung als Handler übergeben wird. Anschließend wird die HTTP-Serverinstanz zum Lauschen auf den konfigurierten Port gestartet. Diese zeigt die Möglichkeit auf, wie die Express-Serverinstanz mit verschiedenen HTTP-Serverinstanzen oder auch mit HTTPS verwendet werden kann.

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
    console.log('Express server listening on port ' + app.get('port'));
});
exports.index = function(req, res){
  res.render('index', { title: ' Startseite' });
};
exports.list = function(req, res){
  res.send("Hello World");
};

[ header = Seite 4: Verwendung von Jade als Node-Template-Engine]

Verwendung von Jade als Node-Template-Engine

Wie in den einfachen Beispielen gesehen, ist es sehr mühsam, die HTML-Ausgabe mittels String-Verkettung zu erledigen. Daher haben sich bei der Node.js-Entwicklung so genannte Template-Engines durchgesetzt. Ohne eine anderslautende Konfiguration wird bei Express.js die Template-Engine Jade verwendet. Dabei ist Jade auf die Erzeugung tagbasierter Ausgaben (HTML bzw. XML) beschränkt.

Der Vorteil einer Template-Engine ist die Verwendung von bestehenden Elementen für das Zusammensetzen einer Antwort. Der grundsätzliche Aufbau aller HTML-Dokumente ist innerhalb einer Website oft gleich und unterscheidet sich nur im Inhalt. Eine so genannte Layoutdatei ist in Listing 7.4 zu sehen. Bei Jade ist es nicht notwendig, die Tags zu schließen; die Einrückung gibt die Hierarchie vor. Dadurch ergibt sich eine gute Strukturierung des Quellcodes. In Zeile 3 wird der HTML-Kopf (head) definiert, wobei in diesem der Title ausgegeben wird, der über eine Variable an das Template übergeben wird (Listing 7.2: routes/index.js). Zu beachten ist, dass alle Texte nach einem Gleichheitszeichen als Programmcode interpretiert werden; auch h1= new Date() ist also gültig.

In Zeile 6 wird der HTML-Inhalt mit dem body-Tag begonnen und in Zeile 7 durch block ein Platzhalter für den weiteren Inhalt definiert. Zur Erstellung einer konkreten Seite auf Basis dieser Layoutdatei liefert Listing 7.5 das Beispiel. Durch extends layout wird Bezug auf die layout.jade-Datei genommen. Wenn man den Programmcode in Literaltext einbetten will, muss man in Jade den Programmcode in #{ } klammern, wie Listing 7.5 zeigt.

doctype html
html
  head
    title= title
    link(rel='stylesheet', href="/stylesheets/style.css?c87a2d")
  body
    block content
extends layout

block content
  h1= title
  p Willkommen auf der Seite #{title}!
  hr
  p Es ist jetzt #{new Date()}. 

Node.js-Werkzeuge – Anwendungen in Visual Studio

Man kann Node.js mit jedem beliebigen Texteditor entwickeln, aber man wünscht sich natürlich Syntaxfarbhervorhebung, Eingabeunterstützung und einen Debugger. Microsoft hat die Zeichen der Zeit erkannt und bietet seit November 2013 die „Node.js Tools for Visual Studio“ als kostenfreie Erweiterungen der Visual-Studio-Version 2012 und 2013 an. Entwickelt wurde diese Erweiterung in dem Team von Microsoft, das auch die Python-Tools für Visual Studio entwickelt hat. Die Integration von npm wurde durch Bart Read von Redgate beigesteuert. Dabei ist es interessant, dass Microsoft hier keine komplette Neuentwicklung gestartet hat, sondern auf die Tools, die bereits in der Community Verwendung finden, aufgesetzt hat. Als Profiler kommt „Google V8-Profile“ zum Einsatz, den Microsoft aber in Visual Studio integriert hat. Gleiches gilt für die Verwaltung der Node.js-Pakete, die direkt im Projektmappenexplorer erfolgen kann. Aber auch wenn man auf der Kommandozeile mittels npm-install Pakete installiert, werden diese korrekt in der IDE dargestellt.

Die „Node.js Tools for Visual Studio“ haben momentan den Versionsstand „1.0 alpha“; sie können unter https://nodejstools.codeplex.com/ geladen werden. Nach Installation und einem Neustart von Visual Studio können unter Other Languages/JavaScript folgende neuen Projekttypen ausgewählt werden:

  • „From Existing Node.js code“: eine Anwendung aus bestehendem Node.js-Quellcodeverzeichnis erstellen
  • „Blank node.js Console Application“: eine neue Hello World-Konsolenanwendung erstellen
  • „Blank node.js Web Application“: eine sehr einfache Node.js-Webanwendung erstellen
  • „Blank Express Application“: eine einfache Express.js-Anwendung erstellen, analog des Beispiels aus Listing 7.x
  • „Blank Windows Azure node.js Application“: eine einfache Node.js-Webanwendung inklusive der Option, diese auf der Microsoft-Cloud-Plattform Azure zu veröffentlichen
  • „Blank Windows Azure Express Application“: eine einfache Express-Anwendung, die auf der Microsoft-Azure-Plattform veröffentlicht werden kann

Eine so angelegte Anwendung kann man direkt aus Visual Studio heraus starten. Die Entwicklungsumgebung fährt nicht nur den Node.js-Prozess mit node.exe hoch, sondern auch den Browser, der darauf zugreift. Mit dem Kontextmenüeintrag „Set as node.js Startup file“ kann man festlegen, welche Datei dem Start an node.exe übergeben werden soll. Den Browser-URL kann man auch in den Projekteigenschaften beeinflussen.

Das Debugging des serverseitigen JavaScript-Programmcodes ist mit den üblichen Komfortfunktionen von Visual Studio möglich: auswählbare Fehlerarten, manuelle Haltepunkte, Variablenansicht in Autos/Locals/Watch und im Codefenster sowie die Call-Stack-Ansicht.

Außerdem steht – was natürlich aus der Sicht von Microsoft nicht fehlen darf – die Publish-Funktion im Kontextmenü eines Node.js-Projekts zur Verfügung. Hier kann man direkt eine individuelle Veröffentlichungsprofildatei von Windows Azure (.publishsettings) laden und damit komfortabel ohne manuelle Konfiguration und mit nur sechs Mausklicks in eine Windows-Azure-Website veröffentlichen. Das Veröffentlichen einer Node.js-Anwendung bei Windows Azure kostet den Entwickler so inklusive Anlegen einer neuen Website im Azure-Verwaltungsportal, Herunterladen der Azure-Veröffentlichungsprofildatei und Veröffentlichen aus Visual Studio heraus gerade mal etwas mehr als eine Minute (unter der Voraussetzung, dass man schon ein aktives Entwicklerkonto bei Azure besitzt).

Wenn eine Anwendung bereits entwickelt ist, stellt sich manchmal die kritische Frage, wo denn die meiste Zeit für die Verarbeitung aufgewendet werden muss. Die Node.js Tools for Visual Studio erweitern den Menüpunkt „Analyze“ um den Eintrag „Launch node.js Profiling …“ Anschließend wird die Anwendung gestartet und es beginnt die Aufzeichnung des Performanzreports. Nachdem die Anwendung beendet wurde, wird der Report im üblichen Stil von Visual Studio aufbereitet und anschließend angezeigt.

[ header = Seite 5: Integration in den IIS]

Integration in den IIS

In der Praxis wird man eine Node.js-Anwendung nicht im Konsolenfenster hosten wollen. Bei iisnode handelt es sich um einen Handler, der die Verwendung von Node.js innerhalb der Internet Information Services (IIS) ermöglicht. Während der IIS die anwendungsbasierten Requests an den Node.js-Prozess weiterleitet, kann er die statischen Dateien (Bilder, CSS etc.) selbst ausliefern. Microsoft setzt das iisnode-Modul beim Betrieb von Node.js in Windows Azure ein. Für die lokale Verwendung von iisnode sind folgende Voraussetzungen erforderlich:

  • Windows Vista, 7 oder 8/8.1, Windows Server 2008 oder Windows Server 2012/R2
  • Der IIS muss inkl. der IIS-Management Tools und ASP.NET installiert sein
  • Wenn der Einsatz von Websockets erforderlich ist, ist der IIS 8.x notwendig und Windows 8/8.1 oder Windows Server 2012/R2
  • Das URL-Rewrite-Module für den IIS muss installiert sein

Nachdem die Installation des iisnode-Modules erfolgt ist, ist noch eine manuelle Konfiguration notwendig. Dazu ist im Verzeichnis C:WindowsSystem32inetsrvconfig die Datei applicationHost.config mit einem Editor unter Administratorrechten zu öffnen und nach der Zeile <section name="handlers" overrideModeDefault… zu suchen und der Wert von Deny auf Allow zu ändern. Anschließend muss der Eintrag wie folgt aussehen:

.

Für den Betrieb in der Node.js-Anwendung im IIS wird dann auch noch eine Konfigurationsdatei web.config benötigt (Listing 8). In Zeile 4 wird der IIS-Handler iisnode konfiguriert und die Zuordnung auf die Express-Anwendungsdatei app.js getroffen. Ab Zeile 6 werden zwei Rewrite-Regeln definiert, sodass statische Inhalte vom IIS im Ordner /public selbst ausgeliefert werden und alle anderen Anfragen an app.js umgeleitet werden.

Danach kann die IIS-Website auf übliche Weise im IIS-Manager konfiguriert werden. Falls die Node.js-Anwendung nicht in der Wurzel einer IIS-Website, sondern in einem eigenen Unterverzeichnis liegen soll, sind bei dem standardmäßigen Routing einer Express-Anwendung noch ein paar Anpassungen in der Datei app.js (Listing 9) und der Layoutdatei views/layout.jade (Listing 10) vorzunehmen.

  
    
      
    
     
      
        
          
        
        
          
            
          
          
        
          
  
  

/**
 * Module dependencies.
 */

var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var http = require('http');
var path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
/* nicht erforderlich, da die Ausgabe mithilfe des IIS erfolgt.
app.use(express.static(path.join(__dirname, 'public'))); */

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/iisnode_test/', routes.index);
app.get('/iisnode_test/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
    console.log('Express server listening on port ' + app.get('port'));
});
doctype html
html
  head
    title= title
    link(rel='stylesheet', href="/iisnode_test/public/stylesheets/style.css?c87a2d")
  body
    block content

[ header = Seite 6: Integration mit .NET mit Edge.js]

Integration mit .NET mit Edge.js

Wenn für eine Node.js-Website die Daten aus einem bereits bestehenden System, das mit Microsoft .NET entwickelt ist, kommen, stellt sich die Frage der Integration. Der typische Weg ist, das .NET-Backend in einem eigenen Prozess zu hosten, den Node.js über HTTP-basierte Web Services (SOAP oder REST) aufruft (siehe oberer Teil in Abbildung 3). In .NET würde man diese Web Services über die Windows Communication Foundation (SOAP und REST, HTTP und viele andere Protokolle) oder das ASP.NET Web API (nur HTTP/REST) bereitstellen. Allerdings muss man sich im Klaren sein, dass die zusätzliche Prozessgrenze erheblich Leistung frisst. Bei Edge.js handelt es sich um eine Node.js-Erweiterungsbibliothek, die es Node.js-Programmen ermöglicht, auf in Form von .NET-Assemblies vorliegende DLLs zuzugreifen oder Quellcode in verschiedenen .NET-Sprachen (z. B. F#, Visual Basic .NET, Python und die Windows PowerShell) nach einer Ad-Hoc-Kompilierung im gleichen Prozess (siehe unterer Teil in Abbildung 3) aufzurufen. Da die Integration asynchron erfolgt, ist .NET Framework 4.5 die Voraussetzung. Autor von Edge.js ist Tomasz Janczuk, ein ehemaliger Development-Manager bei Microsoft im Windows-Azure-Team.

Listing 11 und 12 zeigen eine auf Edge.js basierende Integration zwischen JavaScript und C#. Der JavaScript-Code erzeugt mit edge.func() eine Proxy-Funktion für eine .NET-Methode. Dabei sind Assemblyname, Typname und Methodenname anzugeben. Diese im Bezeichner csFunktion abgelegte Funktion wird dann im Folgenden mit einem Nutzdatenobjekt aufgerufen, dass drei Attribute besitzt: eine Zeichenkette, ein Array von JavaScript-Objekten und den Zeiger auf eine JavaScript-Funktion, die .NET zurückrufen kann. Als zweiten Parameter der csFunktion muss man in JavaScript beim Aufruf der .NET-Methode eine Callback-Funktion mit zwei Parametern (Fehlerobjekt und Ergebnis der Funktion) angeben. Wenn das Fehlerobjekt-Objekt den Wert null hat, ist kein Fehler aufgetreten.

Die C#-Seite empfängt das JavaScript-Nutzdatenobjekt als System.Dynamic.ExpandoObject mit Schnittstelle IDictionary. In Listing 12 werden daher zuerst die drei Attribute „Aufrufer“, „Produktliste“ und „Callback“ aus dem Dictionary geholt. Anschließend erfolgt die Weiterverarbeitung. Die Produktliste wird als ein Array von ExpandoObject empfangen; daher müssen die C#-Produktobjekte in der Iteration über die Eingabemenge erst konstruiert werden. Die Console.WriteLine()-Arufrufe, die beim Debugging in dem Node.js-Konsolenfenster landen, sind natürlich nur zu Testzwecken vorhanden. Wie das Beispiel anhand der Klasse Ergebnis zeigt, kann .NET auch komplexe Datentypen zurückgeben. Voraussetzung ist ihre Serialisierbarkeit mit JSON und das Fehlen zirkulärer Referenzen. Die Callback-Funktion empfängt C# als Typ Func<object,Task>, der mit await aufgerufen werden kann. Wichtig dabei ist, dass in JavaScript die Callback-Funktion aktiv wieder C# aufrufen muss. Dafür empfängt sie im zweiten Parameter (hier callbackEnde genannt) den Rücksprungpunkt.

Die Ergebnisse von Listing 11 und 12 zeigt Abbildung 4. Der JavaScript-Code erzeugt Produktobjekte, die an C# übergeben und dort ausgewertet werden. Während der Auswertung sendet C# den Status (Produkt x empfangen!) an JavaScript zurück. JavaScript gibt diese Zwischenzustände und das Ergebnisobjekt in der HTML-Seite aus. Bei der Verwendung von Edge.js in Visual Studio ist das Debugging inzwischen über die Technikgrenze hinweg möglich.

Abb. 3: Integrationsoptionen zwischen Node.js und Microsoft.NET

var http = require('http');
var url = require('url');
var edge = require('edge');

var anfrageBearbeiter = function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/html'});

  res.write("");
  res.write("

Kopplung node.JS und .NET via edge

"); // Aufzurufende C#-Methode festlegen var csFunktion = edge.func( { assemblyFile: __dirname + "/DOTNETAssembly.dll", typeName: 'edgeJSDemos.Produktmanager', methodName: 'Invoke' // Func<object,Task> }); // Testprodukte generieren var Produkte = []; for(var i = 0; i<= 10; i++) { var Produkt = { Name: 'Produkt ' + i, Preis: 0.99+i*i }; Produkte.push(Produkt); } // C# aufrufen csFunktion( { // Nutzdaten Aufrufer: "Beispiel 6a", Produktliste: Produkte, Callback: function(daten, callbackEnde) { res.write("."); callbackEnde(null, null); } }, // Ergebnismethode function (error, result) { // Ergebnis von C# auswerten if (error) { res.write("Fehler: " + error); } else { res.write("Summe: " + result.Summe + " "); res.write("Durchschnitt: " + result.Durchschnitt + " "); res.write(""); res.write(""); res.end(); } }); }; // Server starten var server = http.createServer(anfrageBearbeiter); server.listen(1337, function() { console.log("Server aufrufen mit ht tp://localhost:1337?name="); });
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace edgeJSDemos
{
  public class ProduktManager
  {
    class Produkt
    {
      public string Name { get; set; }
      public double Preis { get; set; }
    }

    class Ergebnis
    {
      public double Summe { get; set; }
      public double Durchschnitt { get; set; }
    }

    public async Task Invoke(System.Dynamic.ExpandoObject input)
    {
      Console.WriteLine("C# beginnt...");

      var liste = new List();

      Console.WriteLine("Empfangener Datentyp: " + input.GetType().FullName);

      // Zugriff auf die Eigenschaften des Nutzdatenobjekts
      var NutzDaten = (IDictionary)input;
      var Aufrufer = (string)NutzDaten["Aufrufer"];
      var Produktliste = (object[])NutzDaten["Produktliste"];
      var Callback = (Func<object, Task>)NutzDaten["Callback"];

      Console.WriteLine("Aufrufer {0} lieferte {1} Produkte.", Aufrufer, Produktliste.Length);

      // Iteration über alle Produkte
      foreach (var obj in Produktliste)
      {
        IDictionary payload = (IDictionary)obj;
        Console.WriteLine("Empfangener Datentyp: " + payload);
        // Produktobjekte erzeugen
        var p = new Produkt()
        {
          Name = (string)payload["Name"],
          Preis = (double)payload["Preis"]
        };
        liste.Add(p);
        // Nur Testausgabe
        Console.WriteLine("C#: " + p.Name + " kostet " + p.Preis);

        await Callback("Produkt " + p.Name + " empfangen!");
      }

      // Auswertung erstellen
      var e = new Ergebnis()
      {
        Summe = Math.Round(liste.Sum(x => x.Preis), 2),
        Durchschnitt = Math.Round(liste.Average(x => x.Preis), 2)
      };

      Console.WriteLine("C# ist fertig!");

      return e;
    }
  }
}

Abb. 4: Ausgabe von Server und Client aus Listing 11 und 12

Node.js für Automobilsportrennen Seine hohe Skalierbarkeit konnte Node.js gut in einem Projekt unter Beweis stellen, bei dem Automobilsportfans während eines laufenden Rennens stets aktuell in Echtzeit über den Wettkampfstand der 22 Piloten informiert werden. Mit der in Abbildung 5 dargestellten Architektur auf Basis der hier dargestellten Techniken Node.js, Express.js und iisnode sowie weiterer Komponenten (Datenbank MongoDB, O/R Mapper Mongoose, Redis für Datenaustausch, Socket.IO für die Benachrichtigung der Clients) können viele tausend Browser parallel über den Rennstand informiert werden. Im Browser werden die Daten als Single Page Application mit HTML5, CSS3, Twitter Bootstrap und dem MVC-Framework Backbone.js dargestellt. Details zu dieser Lösung findet man hier. Abb. 5: Architektur einer hochskalierbaren Node.js-basierten Website

Fazit

Auch wenn es Node.js noch nicht in die Top 10 der Webserverentwicklungsframeworks von buildwith.com (vgl. [21]) geschafft hat, so sind die große Anzahl von Erweiterungspaketen, die hohe Anzahl von mehr als 2 Millionen Paketdownloads von npmjs.org täglich und auch die zunehmende Unterstützung dieser Technik durch Microsoft klare Indikatoren dafür, dass Node.js eine aufstrebende Technik ist. Und nicht unerwähnt bleiben soll, sowie auch, dass Microsoft mit dem Windows Azure Mobile Services [19] und dem Backend für das neue Visual Studio Online [20] nun schon zwei Cloud-Dienste auf Basis von Node.js anbietet. Man kann also erwarten, dass Node.js in Zukunft noch bedeutender werden wird.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -