2016年8月26日 星期五

Streaming http response

Streaming http response

某些時候,我們會希望在Server端執行比較久的回應結果可以持續得回傳回來。在express中,可以透過res.write()來持續回傳結果,直到回傳完成,再結束這個連線... 下面是express中的片段範例... 其中"/exec"這個route是在本地端執行child_process.exec(),然後將結果回傳...

範例

app.use('/exec', function(req, res){
  exec(function(d, t){
    res.write(d);
    if(t && t == 'end') res.end('\ndone...');
  });
});

function exec(fn){
  const spawn = require('child_process').spawn;
  const ls = spawn('bash', ['/tmp/test.sh']);
  ls.stdout.on('data', (data) => {
      fn(`stdout: ${data}`, 'stdo-data');
  });

  ls.stderr.on('data', (data) => {
      fn(`stderr: ${data}`, 'stde-data');
  });

  ls.on('close', (code) => {
      fn(`child process exited with code ${code}`, 'end');
  });
}
下面是express所欲執行的shell...
# File: /tmp/test.sh
echo 1
sleep 1

echo 2
sleep 2

echo 3
sleep 3
啟動Server後,再透過curl測試...
curl localhost:3000/exec
stdout: 1
stdout: 2
stdout: 3
child process exited with code 0
done...
將會發現,上面的執行結果將是一行一行回覆回來,在結束之前connection將不會中斷...

細看執行重點

上面的程式之所以可以block住connection並且一段一段回傳結果,其中重點在express routing中使用res.write()來做data的回傳,另外也因為child_process.spawn()提供event driven的方式,在每筆data回傳時候呼叫對應的event執行方式...

2016年4月10日 星期日

找到了個不錯的command line輔助工具~inquirer

inquirer是一套讓您可以製作互動指令的工具,透過inquirer可以達到像是node interpreter的功能。一般在做互動式安裝系統時,需要多次設定參數與環境,或選擇不同的安裝項目時,非常有用ㄛ。

Github repository

Installation

npm install inquirer

Sample Usae

怎麼使用inquirer呢?最簡單就是來個echo的程式,也就是執行後,你打什麼,他就回覆什麼 :D
File: echo.js
#!/usr/bin/env node
var inquirer = require('inquirer');

function it() {
  inquirer.prompt([{
    type: 'input',
    name: 'cmd',
    message: ' →',
  }]).then(function (a) {
    if(a.cmd == 'exit') process.exit(0);
    console.log(a.cmd);
    it();
  });
}

it();
執行狀況:
$ node examples/inquirer/echo.js
?  → hello
hello
?  → Hello Simon!
Hello Simon!
?  → exit
上面的程式中,prompt的輸入參數為需要帶入互動模式的指令,可以指定input, confirm, list...等方法,也可以帶入一些判斷讓指令模式更加聰明。
在then的callback中,所帶入的數值會以input裡面的name為名稱附加在input參數"a"中,例如上方input的name為cmd,則下方then callback的"a"所接收到的參數會帶入在a中...
a = { "cmd":"your-input-text" }
透過callback再呼叫原函式,則可以持續會到interactive模式,只有接收到'exit'時候會直接離開程式。

Reference

2016年2月1日 星期一

用Node.js搭配Google Vision API與Canvas套件,做到人臉辨識功能!

Canvas是HTML5上處理2D影像的常用套件,而在node.js下,可以透過node-canvas來針對影像做一些特殊處理,例如:針對給定的座標畫各式幾何圖形、圖片縮圖等等。
Google Vision API目前是Trust Preview的階段,如果有想要試用,可以在Google的官網(http://cloud.google.com/vision/)申請唷!本篇還是以node-canvas的繪圖為主~ :D

Github

Installation

npm install node-canvas
node-canvas是需要搭配canvas相關函式庫來啟動,相關的安裝方式可以透過官方的github位置來安裝相關套件,以Mac來說,安裝如下:
$ brew install pkg-config cairo libpng jpeg giflib

範例程式

這邊搭配google-vision-api-client套件來查詢Vision API做人臉偵測的動作,並在人的臉部做方型框框標示。
#!/usr/bin/env node
var vision = require('google-vision-api-client');
var requtil = vision.requtil;
var request = require('request');
var Canvas = require('canvas')
  , Image = Canvas.Image
  , fs = require('fs')
  , sizeOf = require('image-size');

var tmpfile = '/tmp/mytmpfile';

var canvas = new Canvas(200, 200)
  , ctx = canvas.getContext('2d')

var jsonfile = '/Users/peihsinsu/.gcpkeys/itri-smart-home/itri-smart-home-33f5a755a360.json';
vision.init(jsonfile);

var outfile = process.argv[3] || '/tmp/test.jpg';

function getColor(i) {
  var list = ['red', 'yellow', 'pink', 'green', 'blue'];
  return list[i%list.length];
}

exports.draw = draw;
function draw(imgfile, outfile, opts, cb){
  fs.readFile(imgfile, function(err, squid){
    if (err) throw err;
    img = new Image;
    img.src = squid;

    if (opts && opts.filter) {
      opts.filter(ctx);
    }
    ctx.drawImage(img , 0, 0, img.width , img.height );

    ctx.globalAlpha = .5;

    var i = 0;
    function doit(vec) {
      var color = Math.random()*1000%255;
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.moveTo(vec[0].x, vec[0].y);
      ctx.lineTo(vec[1].x, vec[1].y);
      ctx.lineTo(vec[2].x, vec[2].y);
      ctx.lineTo(vec[3].x, vec[3].y);
      ctx.closePath();
      ctx.strokeStyle = getColor(i);//'rgb(255,color,0)';
      i++;
      ctx.stroke();
      //ctx.fill();
    }

    opts.vec.forEach(function(v) {
      doit(v)
    });
    fs.writeFileSync(outfile, canvas.toBuffer());
    cb();
  });
}

function main(imgfile) {
  var d = requtil.createRequests().addRequest(
    requtil.createRequest(imgfile)
      .withFeature('FACE_DETECTION', 50)
      //.withFeature('LABEL_DETECTION', 20)
      .build());

  var imgSize = sizeOf(imgfile);
  console.log('Got the image size: %sx%s', imgSize.width, imgSize.height);

  vision.query(d, function(e, r, d){
    if(e) return console.log('ERROR:', e);
    console.log(JSON.stringify(d));

    if(!d.responses[0].faceAnnotations) return;

    //var v = d.responses[0].faceAnnotations[0].boundingPoly.vertices;
    var v = [];
    d.responses[0].faceAnnotations.forEach(function(o){
      v.push(o.boundingPoly.vertices);
    })
    console.log('-->', v);
    canvas.width = imgSize.width;
    canvas.height = imgSize.height;

    draw(imgfile, outfile, {
        vec: v,
        filter: function(ctx) {
          //ctx.strokeStyle = 'rgba(255,0,0,.5)';
        }
      },
      function(err) {
        if(err) console.log('ERROR:', err);
    });
  });
}

//Execute the process
if(process.argv[2].indexOf('http') == 0) { //from http resource
  var url = process.argv[2];
  var req = request.get(url);
  req.pipe(fs.createWriteStream(tmpfile));
  req.on('end', function(){
    main(tmpfile);
  });
} else { //from localhost
  main(process.argv[2]);
}
上面程式碼儲存成test.js並給予執行權限後,即可測試...
測試http網站圖片:
$ node test http://www.kevinparker.com.au/people/p7IGM_images/fullsize/shutterstock_48737587_fs.jpg
測試本地圖片:
$ node test /Users/peihsinsu/Pictures/Simon/simon01.jpg
執行完後,會在/tmp/test.jpg產生圖片檔案
$ open /tmp/test.jpg

Notes

Node.js搭配/dev/watchdog做監控

Linux上的Watchdog是一個滿常用來監控系統的服務,可以透過掛載模組後產生硬體的watchdog裝置(/dev/watchdog),掛載方式可以參考本文最下面的鏈結。

其中需要:
1. 設定watchdog的module掛載
2. 啟用module

而在Node.js中需要連結/dev/watchdog的方法,是open該file後,持續的在所規定的時間內丟一個特定字串給它(/dev/watchdog),下面是node.js實作的方式:

var fs = require('fs');
var data = 0;
var WDIOC_KEEPALIVE = 2147768069;
var WDIOC_SETTIMEOUT = 3221509894;
var WDIOC_GETTIMEOUT = 2147768071;

var buf = new Buffer(4);

fs.open('/dev/watchdog', 'r+', function(err, fd){
  if(err) console.log('error:', err);
  setInterval(function() {
    console.log('write file...');
          fs.write(fd, WDIOC_KEEPALIVE, buf, function(err){
            if(err) console.log('error:', err);
          })
  }, 2000);
});

當程式執行起來後,將會每2秒鐘到該裝置寫一塊資料,當成是關閉後,則會觸動系統重新開機的動作... 這個用途在當系統發生問題時候,需要重新開機時,非常有用唷 :D

參考:http://www.switchdoc.com/2014/11/reliable-projects-using-internal-watchdog-timer-raspberry-pi/