2013年11月4日 星期一

CouchDB session store : connect-couchdb

connect-couchdb是一套Express的CouchDB session store,可以讓Express web server的session persistance在CouchDB中

套件資訊

Installation

    npm install connect-couchdb

設定與使用

設定CouchDB Session Store

var connect = require('connect')    
  //step1. enable couchdb store    
  , ConnectCouchDB = require('connect-couchdb')(connect)    
  //step2. connection setting to couchdb
  , store = new ConnectCouchDB({
      name: 'session',
      host: "db_server_ip_address",
      port: 5984,
      username: 'username',
      password: 'password',
      ssl: false
    });
var app = express();
app.set('port', process.env.PORT || 3000);  
app.set('views', __dirname + '/views');  
app.set('view engine', 'jade');  
app.use(express.favicon());  
app.use(express.logger('dev'));  
app.use(express.bodyParser());  
//step3. setup session to specific store  
app.use(connect.cookieParser());  
app.use(connect.session({secret: 'YourSecretKey', store: store }));  
//step4. end of session setting  
app.use(express.methodOverride());  
app.use(app.router);  
app.use(express.static(path.join(__dirname, 'public')));

使用session

使用session的部份,原則上與其他persistance的使用方式相同,都是針對req.session這個物件做操作..
//Sample of get session  
app.get('/session/:key', function(req, res){
    console.log(req.session);
    res.end(util.format('key:%s --> value:%s', 
           req.params.key, req.session[req.params.key]));  
});
//Sample of set session
app.get('/session/:key/:value', function(req, res){
    req.session[req.params.key] = req.params.value;
    res.end('OK');  
});

2013年10月24日 星期四

Read a BIG file in Node.js

寫程式的人常有需要處理到大型的檔案讀取,在Node.js中如果直接使用原生的fs.readFile()來讀檔,則會發生"RangeError: length > kMaxLength"之類的Exception,範例程式如下:

//test-fs.js
var fs = require('fs');
fs.readFile('../1G-File.txt', function(e,d){
  if(e) console.log(e);
  console.log(d);
});

執行的結果如下:

$ node test-fs.js
buffer.js:194
      this.parent = new SlowBuffer(this.length);
                    ^
RangeError: length > kMaxLength
    at new Buffer (buffer.js:194:21)
    at fs.js:220:16
    at Object.oncomplete (fs.js:107:15)


這時候借用第三方套件"lazy"+"fs.createReadStream()"的話,將可以很順利的解決讀取大檔案的問題喔∼

var lazy = require("lazy")
  , fs  = require("fs");
new lazy(fs.createReadStream('../1G-File.txt'))
   .lines
   .forEach(function(line){
       console.log(line.toString());
   }
);

執行上將會很順利...這邊不秀了∼給大家參考






2013年10月22日 星期二

Node.js銜接Google的第一步....

gapitoken

gapitoken是使用Node.js實作Oauth2認證的一個模組,透過API console中申請一個service account,並使用api console產生的p12 key所轉換的pem key,就可以快速的與API進行認證。此種(service account)與API認證的方式適用於提供查詢服務的應用程式,不需要與user的Auth做互動,與API的互動完全是透過應用程式owner所申請的service account之全縣。

套件資訊

Installation

npm install gapitoken

使用說明

準備連線需要的資訊,其中iss為當初申請service account時候的所給的Email address,如下圖:
而Scope是Google控制存取API的呼叫權限,不同的API或不同的request有不同的scope要給予,才可以做呼叫動作,最後,key.pem是創立service account時候所給予的p12檔案,透過ssl轉換後的檔案...
var opts = {
      iss: '860835...6s1@developer.gserviceaccount.com',
      scope: 'https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/cloud-platform',
      keyFile: __dirname + '/key.pem'  
};
準備API pem key
從API console下載的key為副檔名p12的key,需要透過openssl轉換為pem key
$ openssl pkcs12 -in privatekey.p12 -out privatekey.pem -nocerts  
$ openssl rsa -in privatekey.pem -out key.pem

透過GAPI物件,進行Auth連線動作,實際token可以gapi.getToken()的callback中取得...
var gapi = new GAPI(opts, function(err) {
    if (err) return console.log(err);
    gapi.getToken(function(err, token) {
      if (err)  return console.log(err);
      //這邊已經取到token了
      console.log(token);
    });
  });
呼叫API,這邊透過request模組做API的呼叫,其中auth token需要放在header的Authorization中...
var bqurl = 'https://www.googleapis.com/bigquery/v2/projects/%s/queries';  
request({
    url: util.format(bqurl,'your-project-name'),
    method: 'POST',
    headers: {
      "Authorization": "Bearer " + token
    }, 
   json: {
      query: 'select * from cp300.GetAnimals'
    }
  }, function(e,r,d){
    if(e) console.log(e);    //Print the query result
    console.log(JSON.stringify(d));
  });

使用範例 - 連線BigQuery

See:/examples/gapitoken/bq-sample.js

var GAPI = require('gapitoken')
    , request = require('request')
    , fs = require('fs')
    , util = require('util')

    //From admin console, create a service account, save the client_secrets.json and it's key
  var client_secrets = JSON.parse(fs.readFileSync(__dirname + '/client_secrets.json','utf8'))

    //Project setting
  var iss = client_secrets.web.client_email;
  var bq_scope = 'https://www.googleapis.com/auth/bigquery https://www.googleapis.com/auth/cloud-platform';
  var project = 'your-project-name';
  var opts = {
      iss: iss,
      scope: bq_scope,
       keyFile: __dirname + '/key.pem'  };    
  var _token = '';
  var gapi = new GAPI(opts, function(err) {
    if (err) return console.log(err);
     gapi.getToken(function(err, token) {
      if (err)  return console.log(err);
      _token = token;
      var bqurl = 'https://www.googleapis.com/bigquery/v2/projects/%s/queries';      request({
        url: util.format(bqurl,project),
        method: 'POST',
        headers: {
          "Authorization": "Bearer " + _token
        },
        json: {
          query: 'select * from cp300.GetAnimals'
        }
      }, function(e,r,d){
        if(e) console.log(e);        //Print the query result
        console.log(JSON.stringify(d));
      });
    });
  });  

2013年8月18日 星期日

Npm Repository Info of you package

透過下面網址可以從npm取回某個到見的相關資訊
並且render成一張圖片喔∼

https://nodei.co/npm/[套件名稱].png

範例如下:



看起來這張圖片是排成去產生的
如果可以做成自動產生,那就更炫了 :D

2013年8月4日 星期日

GitLab one click installer

GitLab是一套提供類似Private Github服務的軟體,開放且因為使用上親近Github,所以是套非常建議每個開發Team都安裝的服務... 實際上看過GitLab的手動安裝方式,其實相當複雜,但是因為有Bitnami這個熱心的服務者打包GitLab的服務,讓Linux上安裝非常的方便...

目前我常試過CentOS的安裝方式,原則上是下載下來後,直接執行:
執行安裝:
# chmod u+x bitnami-gitlab-5.4.0-0-linux-x64-installer.run
# ./bitnami-gitlab-5.4.0-0-linux-x64-installer.run


----------------------------------------------------------------------------
Welcome to the BitNami GitLab Stack Setup Wizard.

----------------------------------------------------------------------------
Select the components you want to install; clear the components you do not want
to install. Click Next when you are ready to continue.

GitLab : Y (Cannot be edited)

GitLab CI [Y/n] :
Is the selection above correct? [Y/n]:
----------------------------------------------------------------------------
Installation folder

Please, choose a folder to install BitNami GitLab Stack

Select a folder [/opt/gitlab-5.4.0-0]:
----------------------------------------------------------------------------
Create Admin account

BitNami GitLab Stack admin user creation

Login [user]: root

Password :
Please confirm your password :

----------------------------------------------------------------------------
Hostname that will be used to create internal URLs. If this value is incorrect,
you may be unable to access your Gitlab installation from other computers. It is
advisable to use a Domain instead of an IP address for compatibility with
different browsers.

Domain [127.0.0.1]: xxx.xxx.xxx.xxx

Do you want to configure mail support? [y/N]: y

----------------------------------------------------------------------------
Configure SMTP Settings

This is required so your application can send notifications via email.

Default email provider:

[1] GMail
[2] Custom
Please choose an option [1] : 1

----------------------------------------------------------------------------
Configure SMTP Settings

Default mail server configuration.

GMail address []: simonsu@mitac.com.tw

GMail password :
Re-enter :
----------------------------------------------------------------------------
Setup is now ready to begin installing BitNami GitLab Stack on your computer.

Do you want to continue? [Y/n]: Y

----------------------------------------------------------------------------
Please wait while Setup installs BitNami GitLab Stack on your computer.

 Installing
 0% ______________ 50% ______________ 100%
 #########################################

----------------------------------------------------------------------------
Setup has finished installing BitNami GitLab Stack on your computer.

Info: To access the BitNami GitLab Stack, go to
http://xxx.xxx.xxx.xxx:80 from your browser.
Press [Enter] to continue :

完成安裝之後,就可以透過80 port連線進入該主機,畫面與操作上與Github真的頗為相像,後續有趣的地方就留給有興趣的網友去發現了 :D



2013年7月30日 星期二

Ejs page scope直接使用session物件的方式

在ExpressJS中,使用Ejs view engine時,發現session不能再ejs page中直接取用,所有參數都必須靠render時候傳遞過去...跟asp, jsp等scriptlet language操作上有明顯的不同...@@

為了達到可以在ejs中直接取用,這邊可以使用ejs page的locals變數,locals變數在route設定時候可以使用res.locals來取出,這時候只要將它與req.session做串連(res.locals.session = req.session)即可讓前端的ejs page直接透過<%=locals.session%>或是<%=session%>的方式來操作session...

而如果要在每一個route中設定res.locals.session,這樣也太麻煩... 我們可以透過app.use的方式來設定:

app.use(function(req, res, next){
  res.locals.session = req.session;
  next();
});

這樣所有的route在執行的時候都會先跑過一次這個動作,所以在使用時候可以這樣:

[app.js]
app.get('/', function(req, res) {
  req.session.user = {name: 'simon'}
  ...(skip)
});

[view/index.ejs]
...(skip)
<%=session.user? 'got user: '+session.user.name : 'no user...'%>
...(skip)

這時候從ejs取用session的物件就單純多了 :D

在ExpressJS中進階使用Ejs view engine

在操作ExpressJS的Ejs View Engine時候
發現針對頁面上的操作並不是那麼的順心
舉個例子:
app.js中做一個route希望能夠將值往前端(.ejs)帶,但是前端implement了express-partials的模組,希望把頁面用template的方式組織起來,並且在template page中會用到一部分的參數... 假設有時個route用到這個template,則每個route都必須把參數設定進去,否則後端會接收到"not defined"的錯誤訊息...

上面例子的片段程式碼如下:

[app.js]
app.get('/', function(req, res){
  res.render('index', { title: 'my express page' });
});

[index.ejs]
<% if(user) { %>
<%= user %>
<% } %>

執行時候會有Exception:
...(skip)
user is not defined
...(skip)

這部份的錯誤應該是Ejs在render page時候造成的,它直接throw Exception而會造成page終止render,在遍尋不著比較好的方法時,從某篇文章找到一些蛛絲馬跡...

原來Ejs使用"locals"這個物件來包裝頁面上會用到的所有參數,而經過ejs的scriptlet tag包裝起來的部分,可以直接使用locals裡面的參數,也就是說上面的app.js做page render時候:

res.render('index', { title: 'my express page' })

相當於把title這個參數與ejs頁面的locals變數做整合
亦即ejs頁面操作:

<%=title%>


<%=locals.title%>

是相等的,但是直接操作title屬性時候,會被ejs compile成runtime exception,這導致如果在ejs中執行下面判斷會出錯;

<% if(user) { .... %>

因為實際上locals.user不存在,且ejs compiler會將不存在的狀況throw exception...

解決方式,直接使用locals來判斷裡面是否有user這個變數...,因此改寫上面的判斷後:

<% if(locales['user']) { .... %>

應該就可以正常運作...

2013年6月22日 星期六

About HTTP delete method error response

記錄一個比較不常用到的操作方式...
以client使用request module為例...
http delete method中,某些client遇到error時,解讀回覆部分不是直接取用body
而是去解析error物件來做回覆
此時如何從server端製作一個錯誤來讓client操作呢...

下面是client端的模擬code,主要client端想要取出callback function中的error物件...

Client side:
var opts = {url:'http://localhost:1337/test/123', method:'DELETE'};
request(
  opts, 
  function(error,res,body) {
    console.log(e);
  }
);

此時server side的實作,以express為例,只需要在res.send()中依序帶入:
  • 第一個欄位設定錯誤代碼
  • 第二個欄位為錯誤物件描述,建議以JSON為格式(預設格式為{code: xxx, message:xxx},若符合此格式,回覆時候匯自動取message內文做為error的message欄位,並會在error物件中多一個code的欄位}
下面模擬del協定,於呼叫時候丟錯:

Server side:
app.del('/test/:id', function(req, res) {
  res.send(400, {code:"test error", message:"the tested error"});
});

此時client的console pring匯出現下面訊息:

Client result: 
{ message: 'the tested error',
  body: { code: 'test error', message: 'the tested error' },
  httpCode: 400,
  statusCode: 400,
  restCode: 'test error',
  code: 'test error' }

如上描述,如果server改寫response回傳物件(不使用預設格式)...

Server side:
app.del('/test/:id', function(req, res) {
  res.send(400, {coder:"test error", msg:"the tested error"});
});

則,error物件會長這樣子:

{ message: '{\n  "coder": "test error",\n  "msg": "the tested error"\n}',
  body: { coder: 'test error', msg: 'the tested error' },
  httpCode: 400,
  statusCode: 400 }

Log4js support print object inline

log4js套件是我常使用的log管理工具
最近看別人的程式才發現,原來它有支援物件的列印
透過 log.info('......%o....', jsonObject) 的方式
可以直接把物件解析成String...
真是方便的功能∼

範例:

執行結果:


更正,不是%o造成的
應該是log4js將最後一個參數使用像console.log(object)的方式
解些json物件成為string...

2013年6月11日 星期二

Access-Control-Allow-Origin範例

在JS的世界裡,Ajax的頭號天敵就是為了避免不安全,Browser所設限的coss site not allow的限制,為了避免cross site javascript的問題,傳統作法是透過script tag來嵌入非同個網站的資源,或是使用jsonp來做site to site的呼叫,但是...

  • script tag內文必須要是script,否則無法順利讀取,甚至會弄壞網頁中的js...
  • jsonp已經被chrome排擠... firefox可以正常使用
有了Node.js的request套件後,我們大可以一行解決... 讓遠方資源變成我發資源:
request.get('http://mysite.com/doodle.png').pipe(resp)
上面可以讓原本需要透過http://mysite.com/doodle.png 來呼叫的資源,透過你制定的鏈結呼叫該資源...
而,根本的解決方式可以使用Header中加入Access-Control-Allow-Origin參數來設定你的js,允許讓其他網站來呼叫...
Server端實作:

See:/examples/basic/http/cross-site-server.js

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200,{"Access-Control-Allow-Origin": "http://10-20-0-31.my.micloud.tw"});
  res.end(JSON.stringify({result:'yes'}));
}).listen(8080);
console.log('Server running at port 8080');
上面設定資源可以讓site:http://10-20-0-31.my.micloud.tw 下面的網頁來呼叫,如果想要讓所有網站都可以呼叫,可以使用"*"來讓所有網站可以呼叫...
Client端實作:

See:/examples/basic/http/cross-site.html

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
$(document).ready(function(){
  $.getJSON('http://211.78.254.38:8080/', function(data){
    $('#r').html(data.result);
  });
});
</script>
<h1>Server response: <span id="r"></span></h1>

2013年5月27日 星期一

在你的主機中使用tty.js

tty.js是Node.js實作的一套將主機的Console直接用Web方式顯示的工具
背景透過SocketIO持續的跟前端主機資料同步
讓使用者感覺真的跟開啟終端機一樣!

使用方法:

    npm install tty.js -g
tty.js提供簡單的指令來開起服務,下面展示如何透過指定port的方式來做登入的動作:
    # tty.js --port 8080
鍵入上面指令之後,就可以使用http://localhost:8080連線
會看到下面的畫面:



如果想要客製一些登入參數與SSL等,可以自己寫幾行程式來做到...
(關於SSL Key部分,可以參考:http://peihsinsu.blogspot.tw/2012/12/smartosself-gen-ssl.html,而下面是採用MiCloud SmartOS,已經內建有一組SSL Key: "/opt/local/etc/openssl/private/selfsigned.pem")
關於更多的參數設定,可以參考:https://github.com/chjj/tty.js.git

    var tty = require('tty.js');    var app = tty.createServer({    shell: 'bash',    users: {      foo: 'bar'    },    "https": {      "key": "/opt/local/etc/openssl/private/selfsigned.pem",      "cert": "/opt/local/etc/openssl/private/selfsigned.pem"    },    port: 10000  });    app.listen();

2013年4月23日 星期二

Upload File

上傳一直是寫Web程式的開發人員不可或缺的一個口袋工具
Node.js針對上傳部分,比較普遍的是使用ExpressJS的上傳設定
而原生的http模組,也一樣可以做到上傳...
下面是網路上找到的兩筆不同上傳的範例:

2013年4月2日 星期二

NodeJS ssh sign and verify : ssh-signer


Ssh-signer(GitHub: https://github.com/peihsinsu/ssh-signer),開發的目的是希望提供一個使用crypto與http-signature的簽章與驗證工具...讓client端與server端可以直接安裝一個套件就可以做到簽章跟認證的動作...

安裝

npm install ssh-signer

使用

準備...載入相關modules並且準備opt參數(選用,不一定要放,沒放會使用預設 alg=RSA-SHA256, hash=base64)
var signer = require('ssh-signer')
  , fs = require('fs');

var opt = {
  alg:'RSA-SHA256',
  hash:'base64'
}
SSH key簽章 (opt選用)
//sign a key from public key path
var a = signer.signPrivateKey( 'Test123', '/root/.ssh/id_rsa', opt);

//sign a key from public key string
var privKeyStr = fs.readFileSync('/root/.ssh/id_rsa', 'UTF-8');
var a = signer.signPrivateKeyStr( 'Test123', privKeyStr, opt);
驗證 (opt選用)
//verify a key from public key path
var b = signer.verify(a, 'Test123', '/root/.ssh/id_rsa.pub', opt);

//verify a key from public key string
var pubKeyStr = fs.readFileSync('/root/.ssh/id_rsa.pub', 'UTF-8');
var b = signer.verifyStr(a, 'Test123', pubKeyStr, opt );
console.log('Verify result ==> ' + b); //will show true or false

SSH key sign & verify...

繼Node.js收錄crypto之後,還沒有機會實作相關功能
透過SSH key來做client與server間的認證會是各不錯的sample...

下面展示將ssh key的privare key跟public key做sign與verify的流程
屆時將private key放在client端,public key可以load在server端(目前MiCloud上管理ssh key的方式)
中間透過時間字串做key的hash減少傳輸中間key背破解的風險...

//STEP1: 載入crypto, http-signature兩各主要使用模組
var crypto=require("crypto");
var sys=require("sys");
var fs=require("fs");
var hs = require('http-signature');

//STEP2: 此處預計採用utf-8讀ssh key,另外,key是採用SHA256的加密方式
//產生key的指令:ssh-keygen -t rsa
var ENCODE= 'UTF-8';
var alg = "RSA-SHA256";
var n = new Date().toUTCString();
//讀取public key與private key
//public key
var pubKey = fs.readFileSync("/root/.ssh/id_rsa.pub", ENCODE);
//private key
var privKey = fs.readFileSync("/root/.ssh/id_rsa", ENCODE);

//STEP3: 使用base64 sign ssh private key
var signer = crypto.createSign(alg);
signer.update(n);
var secrit = signer.sign(privKey, 'base64');
console.log('SSH key singed...............................................');
console.log(secrit);

//以下部分可以用在server side
//STEP4: 透過http-signature來將public key轉換成pem格式
console.log('Compute SSH key to PEM.......................................');
var pem = hs.sshKeyToPEM(pubKey);
console.log(pem);

//STEP5: 轉換過的pem檔案,就可以透過crypto進行verify
console.log('Start to verify..............................................');
var ver = crypto.createVerify(alg);
ver.update(n);
var v = ver.verify(pem, secrit, 'base64');
console.log(v); //這邊會顯示verify的結果

以上,可以供有需要的人做參考~


2013年3月28日 星期四

15 Lines NodeJS File Server...

Node.js來初始化HTTP協定是相當簡單的事情
下面展示一個簡單的http server範例
可以把執行路徑下的任何檔案當做http資源來瀏覽

    
var fs = require('fs')
  , http = require('http');
http.createServer(function (req, res) {   var path = req.url;
  try{
    var f = fs.readFileSync('.' + path);
    console.log(f);
    console.log(f);
    res.end(f.toString());   } catch(e) {
    console.log(e);
    res.end('');
  }
  }
}).listen(1337); console.log('Server running at http://127.0.0.1:1337/');
目前作者已經包裝成NPM套件
可以透過下面方式安裝與使用(以下擷取自:https://github.com/peihsinsu/noder)

Noder - Simple HTTP Server 

Install

Install using npm...
    # npm install noder -g  

Usage

    # noder  Server running at http://127.0.0.1:1337/  

View

Browser View http://127.0.0.1:1337/test.html (Assump that the test.html exist in the current folder)
    <Buffer 23 21 2f 62 69 6e 2f 62 61 73 68 0a 63 64 20 2f 6f 70 74 0a 23 77 67 65 74 20 68 74 74 70 3a 2f 2f 6e 6f 64 65 6a 73 2e 6f 72 67 2f 64 69 73 74 2f 76 30 ...>  { [Error: ENOENT, no such file or directory './favicon.ico']    errno: 34,    code: 'ENOENT',    path: './favicon.ico',    syscall: 'open' }  <Buffer 1f 8b 08 00 ed 18 e5 50 00 03 ec 3d 6b 77 db 36 b2 f9 5a fd 0a 44 67 cf 4a 4a 25 ea 2d 77 ed 3a 8d 9b b8 4d 77 f3 ba b1 fb b8 27 37 c7 a2 48 c8 62 4c 91 ...>

2013年3月21日 星期四

透過node.js取得request上有用的資訊

身在網路的世界打滾∼有些事情不得不知道...
在node.js中,他把request做了某個程度的wrapper
我們可以透過下面的方法取得一些常用的連線參數
  • 取得server side full url:
    req.url
  • 取得request client的IP位置:
    req.connection.remoteAddress
  • 取得referer page(從哪個網址連線進入)
    req.headers['referer']
  • 取得user agent information:
    req.headers['user-agent']
實際上程式碼可以向這樣:


http.createServer(function (req, res) {
   console.log(req.connection);
   var result = 'req.url:' + req.url + '\r\n';
   result += 'req.connection.remoteAddres:' + req.connection.remoteAddress + '\r\n';
   if(req.headers) {
       result += 'req.headers[\'referer\']:'+ req.headers['referer'] + '\r\n';
       result += 'req.headers[\'user-agent\']:' + req.headers['user-agent'] + '\r\n';
   }
   res.end(result);
}).listen(port, server);


大家可以參考看看∼

2013年2月6日 星期三

Call by value and call by reference of Node.js

Node.js中的變數,屬於call by value的範圍(我猜是原生型別的關係)
下面是範例:

var a = 1;
var b = a;
console.log(a + '::' + b);
> 1::1
b = b + 1;
console.log(a + '::' + b);
> 1::2

上面的b值改變時候,不影響a值,因此a與b無reference關係...

而物件的話,看起來屬於call by reference...

var a = {aa: 111};
var b = a;
console.log(a);
console.log(b);
console.log('===========');
b.aa = 222;
console.log(a);
console.log(b);
>{ aa: 111 }
>{ aa: 111 }
>============
>{ aa: 222 }  //這是a
>{ aa: 222 }  //這是b

上面的b物件改變aa的值時候,實際上影響到a物件的aa值,屬於call by reference
因此在操作時候需要特別小心...
一個不小心....!@#!$!@#


2013年1月20日 星期日

ExpressJS三兩事

ExpressJS應該是學Node.js的開發者必碰的框架,一些基本的說明收錄如下:

專案之初

專案採用ExpressJS之框架建置,建置過程減列如下:

    # mkdir ~/project  # express ~/project/ProjectName  # cd ~/project/ProjectName  # npm install  

目錄結構

  • app.js : 程式進入點
  • views/ : 放置*.ejs檔案,為view module的程式碼位置,亦即與java中的jsp相同地位的程式
  • routes/ : 放置route相關處理的程式碼,預設ExpressJS將app.js中的routing處理都拉到routes/下面
  • public/ : 放置靜態資源的位置
  • node_modules : 透過npm install後,npm會將定義域package.json中的所需函式庫都下載安裝至此

Configure

Igongsha將view engine由預設jade修改為ejs,設定部分主要如下:

    var express = require('express')    , partials = require('express-partials')    ....;    var app = express();    // Initial the layout system in expressjs 3.x  app.use(partials());    app.configure(function(){    //Set listen port     app.set('port', process.env.PORT || 7800);    //Set views' folder    app.set('views', __dirname + '/views');    //Set view engine, we choide ejs here    app.set('view engine', 'ejs');    ...    //Set public folder    app.use(express.static(path.join(__dirname, 'public')));  });    //Set running mode  app.configure('development', function(){    app.use(express.errorHandler());  });    //Set routing (this use the routes middleware, that default use the index.js under $project/routes)  app.get('/', routes.index);  

Routing

Routing簡單的說就是URL存取位置,你可以直接在app.js中寫處理,也可以拉出到middleware中

Under app.js:

    //That can access using http://host:port/test  app.get('/test', function(req, res){    res.end('...');  })  

Or move to middleware:

    //app.js  app.get('/test', routes.test);    //routes/index.js  exports.test = function(req, res){    res.end('...');  }  

Routing Types

ExpressJS依據HTTP method wrapper routing成get, post, del, put, all,說明如下:

  • app.get: HTTP GET傳遞方式,此方式傳遞時,無法使用req.body取值,通常可以作為讀取資料之用
  • app.post: HTTP POST傳遞方式,可以使用req.body取出Form中參數的傳遞,通常可以作為新增資料之用
  • app.del: HTTP DELETE傳遞方式,通常可以作為刪除資料之用
  • app.put: HTTP PUT傳遞方式,通常可以作為更新資料之域
  • app.all: 接受所有HTTP協定,內部可用req.method判斷傳入的Method為何

Middleware

舉凡非view的,通常nodejs稱作middleware,而我習慣說是lib...,一個存放商業邏輯的地方...

  • lib/*.js : 存放moddleware或函式庫或工具的地方
  • routes/*.js : 存放app.js中抽出的routing處理的地方,也可以說是route的middleware
  • node_modules/* : 定義於package.json中的dependency,在npm install後都會被安裝於此

View

頁面程式的產生工廠,類似java中的jsp,可以透過scriptlet的語法'<%- ... %>', '<%= ... %>', '<% ... %>'來嵌入後台程式碼,其中差別說明如下: '<%- ... %>', 用來嵌入不被跳脫(tag的<或>會被轉譯)的程式碼 '<%= ... %>', 用來嵌入需要被跳脫的程式碼 * '<% ... %>', 用來放置運算 views/layout.ejs

    <h1>This is from view/layout.ejs</h1>    <%-body%>    <footer>    <p align="center" style="width:100%">&copy; MiCloud 2013</p>  </footer>  

sample.ejs

    <title><%=title%></title>  Hello <%=user%>  <ul>  <%  for(var i=0; i<10; i++) {  %>  <li>This is ... <%=i%></li>  <%  }  %>  </ul>  

URL Access

已上面app.get('/test', routes.test)為例,伺服器啟動後,就可以使用http://server_ip:port/test來存取routes.test裡面的運算,而參數的存取,可以透過下面幾種方式:

  • req.params.xxx或req.params[xxx]: 如果routing的定義中有使用到:xxx的路徑,例如app.get('/test/:xxx', routes.test)的話,那就可以透過req.params來取xxx的值
  • req.body.xxx: 如果有使用到form的post時候,可以透過req.body.xxx來存取form中id/name=xxx的欄位傳送過來的值
  • query parameters: 如果參數的傳遞是使用url query string來傳遞時候,則可以使用req.url取出url,並透過url.parse()解析url之後,再取出該參數,範例如下:
    var url_parts = url.parse(req.url, true);  var query = url_parts.query;  console.log(query.xx);  

Static Pages

Express中的靜態資源位置,是在一開始的app.configure()中設定的,通常是寫這樣:

      app.use(express.static(path.join(__dirname, 'public')));  

因此,專案Code Gen後的預設目錄一般為public目錄,這些目錄下的檔案會忠實的呈現在routing上,例如$project/public/test.html檔案,會呈現在url:http://server_ip:port/test.html上,而其他靜態資源(image, video, music...)均可以透過這樣的規則放置與存取。

2013年1月19日 星期六

String as a Function

寫程式的時候,常會有需要從原始碼直接執行的需要
例如,需要把程式碼片段藏在DB中,依照不同的需求取出不同的程式碼來執行
這時候就非常需要能夠從文字重組回物件的方式
在Java裡面,是透過Class.forName(className)的方式,從原始碼中創建一個類別來執行
在Node.js中,有著JavaScript的許多特性
其中一個好用的funciton:eval(),可幫助執行一個純文字輸入的javascript片段
範例:

/tmp/fn.txt
{
  "name":"test",
  "fn":"fn = function(v){ console.log('Hello...' + v); }"
}


/tmp/test.js
var fs = require('fs')
fs.readFile('/tmp/fn.txt', 'UTF-8', function(e, d){ 
  var obj = JSON.parse(d);
  var fnc = eval(obj.fn)
  fn("test");
});

# node /tmp/test.js
Hello...test

上面範例,其中fn.txt中的fn=,測試下是必須要給定
似乎是因為eval會實際執行輸入字串中的語句
而function的語法中,如果是匿名function的話
則必須給定給一個變數
這樣也方便讓eval後的程式片段可以用一個名稱直接呼叫
如此,不論是來自file或是db的程式片段,都可以用類似方式動態載入來使用喔∼


2013年1月2日 星期三

Json to HTML Table

Json to HTML Table是擷取自afshinm的專案,目的是讓所有的Json物件可以輕易的在javascript中轉換成為Html Table的字串
當中也支援http(s)://, ftp://, file://, javascript:()等輸出的轉換,可以方便顯示json物件中的不同屬性文字...
原先開發nodeutil的目的在蒐集各種常用的套件,當中引用該模組作為Node.js中處理Json物件轉換成Html Table的功能
在撰寫Mail或是Render Web時候,可以派上用場...

用法:
var j2t = require('nodeutil').json2table;
app.get('/test', function(req, res){
  var json = [
    {Desc:'Sample of using javascript', Code:'javascript:(alert(\'TEST\'));'},
    {Desc:'Sample of using http', Code:'http://www.google.tw'},
    {Desc:'Sample of using https', Code:'https://www.google.tw'},
    {Desc:'Sample of using ftp', Code:'ftp://www.ntu.edu.tw'}
  ];
  res.end(
    j2t.ConvertJsonToTable(
      json, 
      'jsonTable', 
      'tbclass', 
      '<img src="http://cdn1.iconfinder.com/data/icons/Map-Markers-Icons-Demo-PNG/48/Map-Marker-Push-Pin--Right-Azure.png"/>'
    )
  );
}); 

其中json物件的key部分會變成table title,並且以第一列的為主要顯示,其下有未填寫的部份會補上{0}
value的部份如有http(s)://, ftp://, file://, javascript:()等開頭的文字,將會自動轉變成link輸出
而主要轉換的function是ConvertJsonToTable((objectArray, tableId, tableClass, linkReplace)),帶入四個參數:
  • objectArray: json array物件,主要輸出內容部分
  • tableId: 將帶入到輸出的table id屬性
  • tableClass: 將帶入到輸出的table class屬性
  • linkReplace: 將帶入到所有被轉換成link的顯示文字(或物件)

輸出畫面: 



輸出的HTML原始碼:
<table border="1" cellpadding="1" cellspacing="1" id="jsonTable" class="tbclass"><thead><tr><th>Desc</th><th>Code</th></tr></thead><tbody><tr><td>Sample of using javascript</td><td><a href="javascript:(alert('TEST'));"><img src="http://cdn1.iconfinder.com/data/icons/Map-Markers-Icons-Demo-PNG/48/Map-Marker-Push-Pin--Right-Azure.png"/></a></td></tr><tr><td>Sample of using http</td><td><a href="http://www.google.tw"><img src="http://cdn1.iconfinder.com/data/icons/Map-Markers-Icons-Demo-PNG/48/Map-Marker-Push-Pin--Right-Azure.png"/></a></td></tr><tr><td>Sample of using https</td><td><a href="https://www.google.tw"><img src="http://cdn1.iconfinder.com/data/icons/Map-Markers-Icons-Demo-PNG/48/Map-Marker-Push-Pin--Right-Azure.png"/></a></td></tr><tr><td>Sample of using ftp</td><td><a href="ftp://www.ntu.edu.tw"><img src="http://cdn1.iconfinder.com/data/icons/Map-Markers-Icons-Demo-PNG/48/Map-Marker-Push-Pin--Right-Azure.png"/></a></td></tr></tbody></table>