nodejs-icon

Node.js Dojo webserver

Ondertussen heb ik al enkele tutorials geschreven over Node.js, Dojo Toolkit en Node.js als back-end + Dojo toolkit als front-end. In deze tutorial ga ik Dojo toolkit gebruiken als hulpmiddel bovenop Node.js waardoor ik de Dojo AMD style kan gebruiken in Node.js scripts.

Heads up!

In deze tutorial wordt met regelmaat verwezen naar mijn vorige Node.js tutorials. Het is dus handig dat je deze eens doorneemt.

Project opzetten

De opzet van dit project is iets ingewikkelder dan het opzetten van een gewoon Node.js project. De reden hiervoor is dat Node.js zijn eigen require() functie heeft. Hierdoor moet eigenlijk het werklijke Dojo/Node.js script gebootstrapped worden.

Maak een nieuw project aan en maak hierin de mappen src, logs en htdocs aan. Het eerste wat je nodig gaat hebben is de Dojo Toolkit SDK, download deze en extract de dojo map naar de src map. We hebben enkel de dojo-map nodig omdat de dijit-map widgets bevat (dus geen back-end functionaliteit) en DojoX is een combinatie van en uitenidelijk gaan we deze packages toch niet nodig hebben.

Maak in je src map tevens de mappen app en utils aan. In de app  package gaat het Node.js script terecht komen dat gebootstrapped wordt. In de utils package gaat dan weer een Dojo utility module terecht komen.
Maak in de app package een JavaScript bestand aan met de naam web-server-dojo.js en in de utils package een JavaScript bestand met de naam FileSystemUtils.js. In de root map van je project moet je nu enkel nog het JavaScript bestand bootstrap.js plaatsen.

De laatste stap is dat we weer eens de simple-mime module gaan gebruiken, open dus een terminal in je project en gebruik het volgende commando:

npm install simple-mime

Linux gebruikers zullen wellicht dit commando als super user moeten uitvoeren (

sudo

). Eenmaal klaar zou je project er moeten uitzien als in onderstaande afbeelding.

project-structure

Bootstrapping

Om Dojo werkende te krijgen onder Node.js moet je eerst enkele aanpassingen maken aan je Dojo configuratie via de

dojoConfig

variabel. De code hiervoor is:

dojoConfig = {
    baseUrl: "./src/",
    async: true,
    hasCache: {
        "host-node": true,
        "dom": false
    },
    packages: [{
        name: "dojo",
        location: "dojo"
    },
    {
        name: "app",
        location: "app"
    },
    {
        name: "utils",
        location: "utils"
    }],
    deps: [
        "app/web-server-dojo"
    ]
};

for (var i = 0; i < dojoConfig.packages.length; i++) {
    if (dojoConfig.packages[i].name == "dojo") {
        require(dojoConfig.baseUrl + dojoConfig.packages[i].location + "/dojo.js");
    }
}

Wat we hier doen is eigenlijk vrij eenvoudig, we geven een base directory aan (de src map), we maken duidelijk dat we gebruik willen maken van de AMD style door

async

op

true

te zetten en uiteraard hebben we ook nog enkele packages nodig, namelijk app, utils en dojo. In het

deps

veld plaatsen we dan weer het script dat gebootstrapped moet worden, namelijk web-server-dojo.

Ten slotte moeten we enkel nog de Dojo core (dojo.js) inladen zoals je dat ook in je browser zou doen (door het script toe te voegen). Dit doen we met de

require()

functie.
Omdat de locatie van de Dojo module gekend is (is namelijk de baseUrl + de locatie van de Dojo package) haal ik het ook op die manier op door in een for-lus uit te zoeken wat de locatie van de Dojo package is.

FileSystemutil

Het eerste wat we gaan doen is een groot deel van de Node.js fs (filesystem) module omzetten naar de Dojo AMD style via de

dojo/Deferred

module. De code hiervoor is:

define(["dojo/Deferred", "dojo/node!fs"], function(Deferred, fs) {
    return {
        readFile: function(filename) {
            var def = new Deferred();
            fs.readFile(filename, function(err, data) {
                if (err) {
                    def.reject(err);
                } else {
                    def.resolve(data);
                }
            });
            return def.promise;
        },
        exists: function(filename) {
            var def = new Deferred();
            fs.exists(filename, function(isExisting) {
                if (isExisting) {
                    def.resolve(isExisting);
                } else {
                    def.reject(isExisting);
                }
            });
            return def.promise;
        },
        rename: function(file, newFile) {
            var def = new Deferred();
            fs.rename(file, newFile, function(err) {
                if (err) {
                    def.reject(err);
                } else {
                    def.resolve();
                }
            });
            return def.promise;
        }
    };
});

Deze module gaan we gebruiken als een soort van statische module waarbij de functies

readFile()

,

exists()

en

rename()

gebruikt kunnen worden. De code hiervan moet jullie vrij bekend voorkomen omdat deze module gebruik maakt van de standaard fs-module van Node.js.
Wat je wel zou moeten opmerken is dat we deze Node.js module nu niet meer op de ouderwetse manier importeren (dat gaat ook niet meer want door het bootstrappen is de oude

require()

functionaliteit overschreven door die van Dojo). Node.js modules importeren we nu door de module dojo/node te gebruiken met daarachter als parameter de naam van de Node.js module, bijvoorbeeld

dojo/node!fs

.

Het tweede dat je zou moeten merken is dat we de output van de callback abstract gemaakt hebben via de dojo/Deferred module. Als we een error tegen komen gebruiken we de

reject()

functie en als we een goede response terug krijgen gebruiken we

resolve()

.

Bij de

exists()

functie heb ik dit gebruikt om als het bestand bestaat

resolve()

te gebruiken en indien het niet bestaat

reject()

te gebruiken.

Web server

Het volgende stuk code is uiteraard de web server zelf. Qua structuur is hier relatief weinig veranderd ten opzichte van het Node.js script dat we eerder al geschreven hebben om een web server te maken in deze tutorial. De code voor de webserver (src/app/web-server-dojo.js) is:

require([
    "dojo/node!http",
    "dojo/node!url",
    "dojo/node!fs",
    "dojo/node!simple-mime",
    "dojo/on",
    "dojo/aspect",
    "utils/FileSystemUtil"], function(http, url, fs, getMime, on, aspect, fsUtil) {

    var logFile = "logs/http.log";

    aspect.after(fs, "appendFile", function(file, data) {
        console.log(data.replace("\r\n", ""));
    }, true);

    http.createServer(function(request, response) {
        var myUrl = url.parse(request.url, true).pathname;
        myUrl = "htdocs" + (myUrl.charAt(myUrl.length - 1) == "/" ? "/index.html" : myUrl);

        fs.appendFile(logFile, request.method + " " + request.url + " HTTP/" + request.httpVersion + "\r\n");
        for (name in request.headers) {
            fs.appendFile(logFile, name + ": " + request.headers[name] + "\r\n");
        }

        on(request, "end", function() {
            fs.appendFile(logFile, "\r\n");
            fsUtil.exists(myUrl).then(function(exists) {
                fsUtil.readFile(myUrl).then(function(data) {
                    response.writeHead(200, {
                        "Content-Type": getMime(myUrl)
                    });
                    response.end(data, "UTF-8");
                }, function(err) {
                    response.writeHead(500);
                    response.end(err, "UTF-8");
                });
            }, function() {
                response.writeHead(404);
                response.end("File not found", "UTF-8");
            });
        });
    }).listen(8080, function() {
        fsUtil.exists(logFile).then(function(exists) {
            fsUtil.rename(logFile, logFile + "." + Math.round((new Date()).getTime() / 1000)).then(function() {
                fs.appendFile(logFile, "Server initialized\r\n\r\n");
            });
        });
    });
});

Net zoals in de FileSystemUtil maken we hier gebruik van de Dojo AMD-style

require()

functie om de Node.js modules (en Dojo modules) in te laden. Interessant hieraan is dus dat je een abstractielaag hebt waarmee je zowel Node.js als Dojo module kan inladen.

Wat nieuw is in deze tutorial is het gebruik van de dojo/aspect module waarmee je eigenlijk AOP features kan gaan gebruiken, maar dan in JavaScript. In dit geval ga ik het gebruiken om als er iets aan de logfile toegevoegd wordt, het ook op de console uit te printen zodat je eigenlijk niet steeds in je log moet kijken wat er gebeurt. De code hiervoor is:

aspect.after(fs, "appendFile", function(file, data) {
    console.log(data.replace("\r\n", ""));
}, true);

Wat deze code doet is ervoor zorgen dat nadat de functie

fs.appendFile()

aangeroepen wordt dat de meegegeven callback uitgevoerd wordt. De parameter true achteraan zorgt ervoor dat we de parameters op dezelfde manier binnen krijgen als we in de functie zelf doen. Geven we deze niet mee dan krijgen we één object met alle parameters daarin.

Het volgende dat je zou moeten opvallen is dat we de dojo/on module (die je kan gebruiken voor DOM events) tevens kan gebruiken voor Node.js events. Zo hebben we hier gebruik gemaakt van deze module om het request end event aan te roepen.

on(request, "end", function() {
    ...
}

Het laatste speciale is dat we hier de Deferred aanroepen van de FileSystemUtil gaan aanroepen. De syntax daardoor is de volgende geworden:

fsUtil.exists(myUrl).then(function(exists) {
    ...
}, function() {
    ...
});

Waarbij dus de eerste functie aangeroepen wordt indien succesvol en de tweede functie aangeroepen wordt indien niet succesvol. Dit hebben we uitgevoerd bij zowel de

exists()

,

readFile()

als

rename()

functie.

Uitvoeren met Node.js

Daarmee zijn we dan ook aan het einde gekomen van deze tutorial. Zorg voor enkele HTML documenten in je htdocs map en je bent klaar om te testen.
Het uitvoeren van dit project doen we uiteraard met Node.js en daarvoor voeren we het volgende commando uit (in de project map):

node bootstrap.js

Let dus op dat je de bootstrap uitvoert met Node.js en niet rechtstreeks je app.

Het resultaat is dat je nu de logging ook in je console zal zien.

result

Nu kan je dus alle voordelen van de Dojo toolkit IN Node.js gebruiken.

Download project

Download – web-server-dojo.tar.gz (850 kB)

g00glen00b

IT Consultant with a passion for JavaScript. Experienced in the Spring Framework and various JavaScript frameworks.