Thursday, April 14, 2016

node.js virtual hosts websites/apps on http and https

In this example I make 2 Express websites/apps (app1 & app2) and just give them some basic routing for "/".

"use strict";
var express = require("express");

/////////////////////////  
// certificates & apps //  
/////////////////////////  
var tls = require('tls');
var fs = require('fs');

var app1 = express();
app1.all("/", function(req, res) {
  res.send('Hello World, site #1');
});

var app2 = express();
app2.all("/", function(req, res) {
  res.send('Hello World, site #2');
});

In a next step I read their SSL keys from disk (app 1&2 .key and .crt) and store them each in a literal object together with references to the 2 apps.


const site1 = {
  app: app1,
  context: tls.createCredentials({
    key: fs.readFileSync('app1.key').toString(),
    cert: fs.readFileSync('app1.crt').toString()
  }).context  
};

const site2 = {
  app: app2,
  context: tls.createCredentials({
    key: fs.readFileSync('app2.key').toString(),
    cert: fs.readFileSync('app2.crt').toString()
  }).context  
};

I make life easy and connect these app/ssl pairs with the domain names in an object. Easy setup / config.

var sites = { "www.site1.com": site1, "site1.com": site1, "www.site2.com": site2, "site2.com": site2
};

I then make a global Express app caled "exp", to which I add the domain names (in this example with and without www) and connect them to the correct apps.
If you want to test this example out, put these 4 domain names (www.)site(1/2).com in your /etc/hosts file.

////////////////////////
// Global express app //
////////////////////////
var vhost = require("vhost");

var exp = express();
for (let s in sites) {
  console.log("add app for " + s);
  exp.use(vhost(s, sites[s].app));
}
Finally I create a http and https server with the global Express app as request listener.
//////////
// http //
//////////
var http = require('http');
var httpServer = http.createServer(exp);
httpServer.listen(8080, function () {
   console.log("Listening http on port: " + this.address().port);
});

For the https server I pass in an option object with a SNICallback to return for each domain the correct SSL certificate (as we are serving more than 1 https server on the same IP/port)

/////////// // https // /////////// var secureOpts = { SNICallback: function (domain, cb) { if (typeof sites[domain] === "undefined") { cb(new Error("domain not found"), null); console.log("Error: domain not found: " + domain); } else { cb(null, sites[domain].context); } }, key: fs.readFileSync('ws.key').toString(), cert: fs.readFileSync('ws.crt').toString() }; var https = require('https');
var httpsServer = https.createServer(secureOpts, exp);
httpsServer.listen(4433, function () {
   console.log("Listening https on port: " +  + this.address().port);
});

Tuesday, July 07, 2015

Blinky in JavaScript / HTML

Een deel aspect van het laatste examen Web UI was een vlakje laten "blinken" als de ingestelde tijd voorbij was. Veel leuke oplossingen gezien. Jammer genoeg ook een aantal foute, niet werkende, oplossingen met for-lussen en wait-toestanden.

Niet mogelijk in web toestanden. Wordt vaak gebruikt in microcontrollers, maar ook daar vind ik dit niet kunnen, dan nog steeds beter een timer opzetten dan gans de cpu te blokkeren.

Hieronder een paar oplossingen voor de Blink toestand. Studenten zullen de weg wel vinden en in 2de zit een beter resultaat neerzetten.

<!DOCTYPE html>
<html lang="nl">
<head>
  <meta charset="utf-8"/>
  <title>Blinky</title>
  
  <style>
     div { display: block; width: 200px; height: 200px; font-size: 50px
           text-align: center; border: 1px solid black; margin: 20px }
  </style>  
  
  <script>
    // dom tree is geladen, we kunnen we onze klik handler aan de button hangen
    $(document).ready( start );
    
    // variable moet globaal zijn
    var counter;
    
    function start() {
    
      $("button").on("click", function(e) {
        // standaard gedrag van de button voorkomen
        e.preventDefault();
        counter = 0;
        blink();
      });
    }
  
    function blink() {
      // rood of blauw <- counter even of oneven
      var color =  (counter % 2 == 0) ? "red" : "blue";
      $("div").css("background-color", color);
      
      // counter verhogen, uiteindelijk moeten we stoppen
      counter++;
      if (counter < 8) {
        // de "blink" functie oproepen binnen 200mS.
        setTimeout(blink, 200);
      } else {
        // terug op wit zetten, niet nodig, maar hier gewoon leuk
        $("div").css("background-color", "white");
        // aangezien we hier geen setTimeout meer oproepen, stopt alles hier.
      }
    }
  </script>
</head>  
<body>
  <h1>Blinky</h1>
  <div>Yoehoe</div>
  <button>Doe nog maar eens</button>
</body>
</html> 


Een alternatief script, deze keer met een timer.

    // dom tree is geladen, we kunnen we onze klik handler aan de button hangen
    $(document).ready( start );
    
    // variabelen moeten globaal zijn
    var counter;
    var timer;
    
    function start() {
    
      $("button").on("click", function(e) {
        // standaard gedrag van de button voorkomen
        e.preventDefault();
        counter = 0;
        // de functie blink wordt om de 200mS opgeroepen
        //  tot in de eeuwigheid (of tot clearInterval)
        timer = setInterval(blink, 200);
      });
    }
  
    function blink() {
      // rood of blauw <- counter even of oneven
      var color = (counter % 2 == 0) ? "red" : "blue";
      $("div").css("background-color", color);
      
      // counter verhogen, uiteindelijk moeten we stoppen
      counter++;
      if (counter > 80) {
        // stop onze timer
        clearInterval(timer);
        
        // terug op wit zetten, niet nodig, maar hier gewoon leuk
        $("div").css("background-color", "white");
      }
    }

Wednesday, October 22, 2014

Tessel.io voting / rating system

First tessel.io experiment

As a lecturer I can imaging that sometimes you go to fast for the students, sometimes it's too slow, etc... During conferences the audiance wants to give feedback... and so on... so why not build a rating display with a tessel.io and 2 servo's?

The hardware part


1) more info on the microcontroller
- Tessel.io
- Runs JavaScript on the bare hardware with wifi onboard
- Has many plug-in IO boards (relay, gps, servo, ambient, cellular data, ...)

2) Save a power supply / need for usb cable:
- The servo board needs its own power supply anyway (adaptor included)
- I soldered a 2 pin header onto the tessel board
- Connected the power for a servo to this Vin tessel (black&red wire)

3) The dials:
- I've made 2 holes in a cardboard
- put in the servo's
- made 2 dials from hard white plastic


The software part




1) The voting system - pure prototype object - nothing to do with the tessel.

// Param prototype object with a Question, a Yes and No option
function Param(Q, Y, N, servo) {
  this.Q = Q; this.Y = Y; this.N = N;

  // clear Y and N counters
  this.YCnt = 0this.NCnt = 0;

  // remember my servo for rendering the score
  this.servo = servo;
}

Param.prototype.reset = function() {
  // clear Y and N counters
  this.YCnt = 0this.NCnt = 0;
  return this;
}

Param.prototype.read = function(obj, name) {
  // read ourselfs from a http query object
  this.Q = obj[name]; this.Y = obj[name+"Y"]; this.N = obj[name+"N"];
  return this;
}

Param.prototype.add = function(choice) {
  if (choice == "Y"this.YCnt++;
  if (choice == "N"this.NCnt++;
  return this;
};

Param.prototype.render = function() {
  // set the servo to a position 0..1
  //   0.5 if there are no votes received yet, avoid div0
  var total = this.YCnt+this.NCnt;


  // move the servo's see (2)
  servoSystem.move(this.servo, ((total == 0) ? 0.5 : this.YCnt / total));
  return this;
};

// make 2 param objects
var A = new Param("Question 1""Yes""No"1);
var B = new Param("Question 2""Good""Bad"2);


Example usage from the http server using our objects

// user entered a vote
if (cmd == 'Vote') {
  A.add(urlObj.query.A).render();
  B.add(urlObj.query.B).render();
  res.write(homepage('Thanks for voting'), 'utf8');        
}



2) Controlling the dials / servo's:

var tessel = require('tessel');

var servolib = require('servo-pca9685');
var servoSystem = servolib.use(tessel.port['A']);

...

// method from our Param object
Param.prototype.render = function() {
  // set the servo to a position 0..1 
  // set to 0.5 if there are no votes received yet, prevent div0
  var total = this.YCnt+this.NCnt;
  servoSystem.move(this.servo, ((total == 0) ? 0.5 : this.YCnt / total));
  return this;
};

..

servoSystem.on('ready', function () {
  
  // configure servo 1 & 2 - max and min
  servoSystem.configure(A.servo, 0.04, 0.13, function () {
  servoSystem.configure(B.servo, 0.04, 0.13, function () {

    
  // setup voting system [ see above in (1) ]
  ...
    
  // setup http server [ see below in (3) ]
  ...

  });
  });
});


3) The http server

This is quick and dirty stuff, because in the end, we don't want this on the tessel.

The http stuff will go to a bigger webserver
- this will keep the score and handle the http pages
- the tessel will send http json requests to get the current score (or we could keep a websocket open between the tessel and the webserver)  

    var server = http.createServer(function (req, res) {
  
      // parse url into and object,
      // parse also query string (true as 2nd param)
      var urlObj = url.parse(req.url, true);
      var pathname = urlObj.pathname;
      
      // serving a home page
      if (pathname == '/') {
        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(homepage(), 'utf8');
        res.end();
        return;
        
        
      // serving an admin page
      } else if (pathname == '/admin') {

        res.writeHead(200, {'Content-Type': 'text/html'});
        res.write(adminpage(), 'utf8');
        res.end();
        return;
        
      // accepting commands
      } else if (typeof urlObj.query != "undefined") {
        var cmd = urlObj.query.request;
        res.writeHead(200, {'Content-Type': 'text/html'});
        
        // user entered a vote
        if (cmd == 'Vote') {
          A.add(urlObj.query.A).render();
          B.add(urlObj.query.B).render();
          res.write(homepage('Thanks for voting'), 'utf8');
        
        // administrator changed the question + reset the counters
        } else if (cmd == 'Save') {
          A.read(urlObj.query, "A").reset().render();
          B.read(urlObj.query, "B").reset().render();
          res.write(homepage('Parameters saved'), 'utf8');

          
        } else {
          res.write('<!DOCTYPE html><html><h1>Illegal command</h1>' +
                    ' -- Go play somewhere else!</html>', 'utf8');
        }
        res.end();
        return;
      }
    });

    // have the server listen for incoming requests
    server.listen(80);



Tuesday, February 18, 2014

Waarom enkel nog Web Applicaties

Ik weet het een veel te oud topic, war niemand meer zou mogen twijfelen, maar toch hier even archiveren voor het collectief geheugen... 

Laatst bekeek een vriend een offerte en die viel achterover omdat hij jaarlijkse hosting moest betalen. Waarom kan ik dat niet gewoon op mijn PC zetten, waarom moet dat persé "in the cloud"?  Hij kwam wanhopig bij mij aankloppen... Verkeerde adres dus, dit was mijn antwoord:

Jongen toch, Ik heb ooit op een congres waar quasi uitsluitend managers zaten met een pc park en massa's programma daarop, gezegd dat mensen die zo'n beslissingen namen zeker geen degelijke IT managers konden genoemd worden, dat ze onverantwoord met het geld van hun bedrijf omsprongen en op zijn minst incompetent moesten verklaard worden. Dat was meer dan 10 jaar geleden, misschien iets te vroeg, maar ondertussen heb ik toch veel mensen zien veranderen. Jammer genoeg verre van allemaal.


Behalve dat software op een pc installeren en lokaal gebruiken iets uit de jaren 90 is, is het ook totaal onverantwoord.

- die installatie is werk en kost dus geld, als je al iemand vindt die dat wil doen. Daarenboven verandert de klant van pc en je kunt opnieuw beginnen, je komt daar dan toe en vraagt: heb je daar nog de installatie cd's van? ..... Tuurlijk niet....

- de pc is stuk en je kan alles weer opniew installeren, als dat dan al lukt, want vaak zie je dat de software niet meer gesupporteerd wordt, dat er drivers niet meer te vinden zijn, enz... Eens meegemaakt bij een bevriende aannemer, (ventilator stuk gegaan, oververhitting, hard disk vastgelopen, puur door ouderdom, 5j.) dat heeft hem toen 10.000€ gekost bij zijn leverancier eer hij weer offertes kon maken. Om nog maar te zwijgen van de dikke week dat hij geen toegang tot zijn gegevens had en geen offerte kon maken. En dan mocht hij van geluk spreken dat hij elke dag backups maakte op tape (ook al stond daar niet alles op).

- Maar het hoeft ook zo erg niet te zijn, recent kwam ik in een artsenpraktijk, daar draaien ze nog 3 pakketten waar ze niet rond kunnen (ekg, sis kaarten lezer, microsoft office). Ze hebben recent nieuwe macs geinstalleerd, de installatie daarvan was 50€ per machine voor configuratie van mails, dropbox, printer, e.d. + 1250€ om die 3 pakketten terug draaiende te krijgen.

- Daarnaast zie ik vaak mensen die wel een backup hebben, maar die niet meer kunnen lezen of niet meer kunnen gebruiken, omdat de software niet meer ondersteund wordt op nieuwe toestellen. 

- Vaak hebben ze ook geen backup, want het was toch niet belangrijk, maar of je hun programma toch terug kunt doen draaien... Uiteraard hebben ze geen backup van de software ook, of toch nog de originele versie, maar die is al 10 keer geupgrade... Als je daar dan een dikke halve dag mee bezig bent, vraag je 325€ voor die halve dag (een vriendenprijsje), je rekent uiteraard de vorig avond niet mee dat je het internet afgeschuimt hebt om de juiste tools te vinden en dan vinden ze je factuur toch wel hoog voor "zo eens binnen te springen" (en dan hebben ze nog niet eens een koffie gegeven). 

- Als je software ergens in de cloud draait, rij je desnoods snel naar de Carrefour, koopt een laptop van 600€ (als je slimmer bent een MacBook) en je kan weer verder.

Ik heb het allemaal al eens gehad, voor mij nog enkel software die je via je webbrowser kan gebruiken.

Friday, September 27, 2013

Eyes in canvas + javascript

The students web/javascript needed a new small exercise to learn how to play with a canvas.

What would be better than the old X eyes? The eyes following your cursor...



How to calculate where to put the inner eye



// pos1X,Y is the middle of the eye in global coordinates
var dx = cursorX - this.pos1X; 
var dy = cursorY - this.pos1Y;

var distance = Math.sqrt(dx*dx+dy*dy);

var x = (distance < this.radius)? dx : dx*this.radius/distance;
var y = (distance < this.radius)? dy : dy*this.radius/distance;

// eye1X,Y is the middle of the eye in canvas coordinates
this.drawEye(this.eye1X+x, this.eye1Y+y);



The complete code, have fun:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset=utf-8>
  <script src="../lib/jquery.js"></script>
  <style> body { text-align: center }</style>
  <title>HTML5 Demo: Canvas with Eyes</title>
  <script>
    window.onload = function() {

      var eyes = {

        drawEye: function (x, y) {
          this.ctx.beginPath();
          this.ctx.arc(x, y, this.radius, 0, 2*Math.PI, false);
          this.ctx.fill();
          this.ctx.closePath();
        },

        drawSmiley: function () {
          // clear the canvas
          this.canvas.width = this.canvas.width;

          // The large circle
          this.ctx.beginPath();
          this.ctx.arc(this.midX,this.midY,this.midX-2,0,Math.PI*2,true);   

          // The mount drawn clockwise
          var mouthR = Math.floor(this.midX * 2 / 3);
          this.ctx.moveTo(this.midX+mouthR,this.midY);
          this.ctx.arc(this.midX,this.midY,mouthR,0,Math.PI,false);

          // first eye
          this.ctx.moveTo(this.eye1X+this.outline,this.eye1Y);
          this.ctx.arc(this.eye1X,this.eye1Y,this.outline,0,Math.PI*2,true);  

          // second eye
          this.ctx.moveTo(this.eye2X+this.outline,this.eye2Y);
          this.ctx.arc(this.eye2X,this.eye2Y,this.outline,0,Math.PI*2,true);

          this.ctx.stroke();
        },

        lookAt: function(cursorXcursorY) {
          // calc global position for comparing with the mouse position
          var pos1X = this.eye1X + this.canvas.offsetLeft;
          var pos1Y = this.eye1Y + this.canvas.offsetTop;

          var pos2X = this.eye2X + this.canvas.offsetLeft;
          var pos2Y = this.eye2Y + this.canvas.offsetTop;

          // first eye
          var dx = cursorX - pos1X;
          var dy = cursorY - pos1Y;
          var distance = Math.sqrt(dx*dx+dy*dy);
          var x = (distance < this.radius)? dx : dx*this.radius/distance;
          var y = (distance < this.radius)? dy : dy*this.radius/distance;
          this.drawEye(this.eye1X+x, this.eye1Y+y);

          // second eye
          dx = cursorX - pos2X;
          dy = cursorY - pos2Y;
          distance = Math.sqrt(dx*dx+dy*dy);
          x = (distance < this.radius)? dx : dx*this.radius/distance;
          y = (distance < this.radius)? dy : dy*this.radius/distance;
          this.drawEye(this.eye2X+x, this.eye2Y+y);
        },

        init: function(theCanvas) {
          // get a drawing context
          this.canvas = theCanvas;
          this.ctx = theCanvas.getContext("2d");

          // calculate the middle of the canvas
          this.midX = Math.floor(this.canvas.width / 2);
          this.midY = Math.floor(this.canvas.height / 2);

          // calculate the middle of the 2 eyes  on 1/3 and 2/3 of the canvas
          this.eye1X = Math.floor(this.canvas.width * 1 / 3);
          this.eye1Y = Math.floor(this.canvas.height * 2 / 5);
          this.eye2X = Math.floor(this.canvas.width * 2 / 3);
          this.eye2Y = this.eye1Y;

          // radius = iris, outline = total eye
          this.radius = this.canvas.width / 20;
          this.outline = this.radius * 2;
        }
      };

      eyes.init( document.getElementById("eyes") );
      eyes.drawSmiley();
      document.onmousemove = function(evt) {
        eyes.drawSmiley();
        eyes.lookAt(evt.clientX, evt.clientY);
      };
    };

  </script>
</head>
<body>
    <header>
      <h1>Canvas</h1>
    </header>

    <canvas width=200 height=200 id=eyes></canvas>

  <p>Wie kijkt naar waar?</p>
</body>
</html>

Sunday, March 17, 2013

Iterator for async nodejs operations


Problems in paradise... fixed !

Code from the Javascript CMS:


var nr = 0;
  for (var x in aPage.children) { 
    var cp = aPage.children[x];
    nr += 10;
    if (cp.item.sortorder != nr) {
      cp.item.sortorder = nr;
      cp.item.doUpdate(this, function() {});
      // todo: either trust in the Force or daisy chain them
    }
  }
  final();

The Force didn't work, because somewhere in "final" we closed the database connection of the current http request...

So this could have been a solution, daisy chaining:


var nr = 0, max= aPage.children.length;
  function one() {
    if (x < max) {
     var cp = aPage.children[x++];
     nr += 10;
     if (cp.item.sortorder != nr) {
        cp.item.sortorder = nr;
        cp.item.doUpdate(this, one); 
     }
    } else {
      final();
    }
  }
  one();

But one can do better no?

Why not make an iterator for this kind of stuff
Application.each = function(list, iterator, finished) {
    var nr = list.length;
    function one(current) {
     if (current >= nr) {
       finished();
     
     } else {
       iterator.call(list[current], function(err) {
         if (err) {
           finished(err);
         }
         one(current+1);
       });
     }
    }
    one(0);
  };

Daisy chain operator

  • list should be an array
  • iterator is a function that should the passed function when done

    if it passes an error to the function the loop end here
  • finished is a function that is called when everything is done with no parameter

    or that is called when the first error occurs

An example

var list = [1, 2, 3, 4, 5];
   var sum = 0;

   Application.each(list, function(done) { 
    sum += this; 
    done(); // pass an error if something went wrong

   }, function(err) { 
     if (err) 
       console.log("error: " + err);
     else
       console.log("sum = " + sum); 
  
   });