【Node.js 小密技】使用 crypto 模組亂數產生字串

2014年6月26日星期四
已非常熟悉 Node.js 的人都知道,crypto 模組是一個無中生有的神器。我們可以用它的 API 加工加密資料,也可以解開加密後的包裹。既然和密碼處理相關,當然也有亂數產生器,可供開發者產生一大堆的亂數資料。

如果你需要一個 32Bytes 長度的亂數資料,可以直接呼叫 crypto.randomBytes() 並帶入長度後生成:
var crypto = require('crypto');
var buf = crypto.randomBytes(32);
註:回傳的結果 buf ,是一個 binary buffer,而每一個 Bytes 都會是範圍在 0 ~ 255 (0x00 ~ 0xff)的數字。

不過,在多數應用中,我們更常需要一個亂數字串,而非二進位的亂數資料,這時就需要做一些進一步的加工處理。方法其實不只一種,如建表、取代不可見字元碼等方式都可以,沒有標準做法。但懶惰的開發者們,多半是直接利用 Buffer 內建的 API 來將資料轉換成字串,並指定編碼方式來達成。

下面兩種方法都可行:
buf.toString('hex');
buf.toString('base64');

只是,使用十六進位(hex),會讓你的最後的字串單純只有 0-9 和 a-f 的字元存在,所以使用 base64 是比較好的選擇,讓字串多變並複雜些。

註:base64 編碼是以 6 bits 為單位來進行處理,而 1 Byte 有 8bits。如果你的資料長度非 6 bits 的整數倍長度,base64 會在結尾補上『=』。有些人覺得多了一些『=』符號很醜,就會用 replace() 替代掉或者是一開始就取 6 bits 整數倍長度的亂數資料(如:3 bytes、6 bytes 等等)。

有些情況,我們需要特定長度的亂數字串,也可以搭配使用 substr(),只是要確定生成的字串長度,比我們想要的字串長度還要長。下面的範例,就是取八個字元長度的字串:
buf.toString('base64').substr(0, 8);

當然,若你想要一次搞定,也可以將之前所說的,全部串在一起寫:
var randomString = crypto.randomBytes(32).toString('base64').substr(0, 8);

後記

最近在開發的 Project 中,一直在處理 Token 以及密碼相關的東西,常用到亂數字串。之前用過不少方法,每次要寫不少 code 相當讓人煩燥。所以記錄下來,以後自己直接 copy & paste 即可,也讓有相同需求的人可以直接參考使用。

我的創業原點:科技和藝術的結合

2014年6月19日星期四
義大利咖啡館-科技和人文的結合

看到時不時有新聞在報 POS,一時興起,就去把舊照片翻出來,沒想到癮科技還保留了快十年前的照片和報導『義大利咖啡館-科技和人文的結合』。

這套 POS,是在當時在經營 Coffee Shop (以現在的角度來看,其實是半個 Working Space)時所開發的,店在板橋江子翠捷運站旁(建築物前還有一座三層樓高的羅馬柱),共三層樓約四五百坪左右,店裡沒有對外掛招牌並擺滿了各種收藏品和古董家具,不知道的人看起來像間古董家俱店,不太敢進來,所以來的都是慕名或是口耳相傳,以及喜歡我們咖啡的人,因此也有人以『神秘咖啡廳』稱呼。

不過,Coffee Shop 其實一直不是主業,只是公司的招待、展示中心和團隊休閒區,而應用、工程、軟體開發和軟硬體整合的產品開發才是我們主要的工作。所以我永遠窩在店裡的某一角,寫著我喜歡寫的程式,周遭來來去去的客人並不真的清楚我是誰。

還記得,點餐系統軟體、平板硬體和後面印單系統整套做出來並實際上線,大概是在 2004 年(大概是高二時),報導則是 2006 年的事了。而且因為這套系統和管理流程設計,讓幾百坪的店面,只需要一兩個人就可以完全掌控、經營和服務,讓我們著實省下不少功夫。雖然很懷念當時的日子,但一想到惡房東後來給我帶來的多年痛苦,以及官司上的問題,至今我仍然還是恨的牙癢癢,因而有很長很長一段時間,我絕口不提這段往事。

所以這也是為什麼,多年後看到這標題,感觸極深,當時『科技與人文的結合』和『科技與藝術的結合』這個標語,一直都掛在店裡的展示大螢幕上,因為這是我們自己對設計的最高期許,也是一直身體力行的方向。許多進進出出拜訪我們的人和國內外大佬們,應該多少都看過。只是後來大多數人再次使用這段話時,是在多年後 Apple 的產品身上,這也算是種台灣的悲哀吧。

很多人不知道,我其實非常喜歡喝咖啡,甚至會燒,只不過後來若不是真的跟我很熟的人,可能從來都沒看過我喝。當時店裡的紀念款 La Marzocco 機器,現在還好好保存著,期盼哪一天能夠在我比較穩定的時候,再次拿出來使用或是與朋友分享。

不曉得還有多少人記得我們這家店?但無論如何,總有一天,我會讓他復業。不為別的,就為了從小所抱著的夢,這是我一頭栽進創業和改變世界的原點。

Express 4 來了!好用的 Router 機制

2014年6月15日星期日
還記得前陣子,就在書將出版之際,半路殺出來最新的 Express 4,使得筆者手忙腳亂,緊急通知出版社修改書的內容。將所有使用到 Express 的範例,都特別加上版本號的指定,讓讀者在照著範例做時,能夠使用舊的 Express 3.0,而不會發生任何問題。最新的 Express 4 雖然效能提升不少,也有一些好用的新東西,但和舊版有些微 API 的不相容,此外,也因為是新東西,可能很多東西的支援會發生問題,如果舊版本在上線的產品中運行相當穩定,建議先不要急著升級。

Express 4 做了一些改進,有不少新東西,其中相當值得提的一項就是路由機制(Router)的翻新。如果你以前在使用 Node.js + Express 開發網站服務時,因為 Router 很難管理而痛苦不已,這將是一個你會愛上的功能改進。

傳統的使用方法

傳統的 Router 設定方法依然可以運作,我們可以放心使用。

var express = require('express');

// Create express application
var app = express();

// Apply this router on (/apis/user)
app.get('/', function(req, res) {
    // Home
});
app.listen(8000);


新 Router 物件的使用方法範例

除了舊的方法之外,你可以用 Router() 去建立一個 Express 的 Router 物件,然後管理你一系列的路由規則。

var express = require('express');

// Create express application
var app = express();

// Create a router to handle routes for a set of  user API
var userAPI = express.Router();

// Sign in (/apis/user/signin)
userAPI.post('/signin', function(req, res) {
    // ...
});

// Sign out (/apis/user/signout)
userAPI.get('/signout', function(req, res) {
    // ...
});

// Sign up (/apis/user/signup)
userAPI.post('/signup', function(req, res) {
    // ...
});

// Apply this router on (/apis/user)
app.use('/apis/user', userAPI);
app.listen(8000);

一次指定多種方法到同個路徑

有時候,同一個路徑我們想賦予他不同的方法(如:GET 或 POST),讓它有不同的功能。這尤其是在開發 Restful API 時,非常常用。像是如果我們要設計一套管理使用者帳號(User)的 API 時。

var userMgrAPI = express.Router();
// A set of API to manage users (/apis/admin/user/:username)
userMgrAPI.route('user/:username')
    .get(function(req, res) {
        // Getting user information
    })
    .post(function(req, res) {
        // Create a new user
    })
    .delete(function(req, res) {
        // Delete specific user
    })
    .put(function(req, res) {
        // Update
    });

app.use('/apis/admin', userMgrAPI);

檢查 Parameter 的 Middleware

前面的例子,在路由上設計了一個 username 的 Parameter,每個方法的 Handler 都會用到它。但有一個需求是,既然每個 Handler 都會用到這個 Parameter,也需要檢查這個使用者帳號是否存在,那麼是否可以用類似 Middleware 的做法,寫一次程式套用在所有方法上?

答案是可以的,你可以直接使用 param() 設計一個 Middleware,去安插一個檢查程式在前面,並指定他要檢查什麼 Parameter 欄位。

userMgrAPI.param('username', function(req, res, next, username) {
    if (username == 'fred') {
        next();
    }else {
        res.send(404);
    }
});

// userMgrAPI.route('user/:username') ...

設計一個其他的 Middleware

如果你過去已經熟悉 Express 的開發,應該對開發一個 Middleware 不陌生,在 Express 4 的 Router 上,你也可以引用。通常我們會設計一個 Middleware 去檢查使用者登入與否,或是前面例子所提及的,檢查使用者是否存在,而不需要在每個路徑的 Handler 中都寫一次。

下面的例子和前一個例子有相同功能:
userMgrAPI.use(function(req, res, next) {
    if (req.params.username == 'fred') {
        next();
    }else {
        res.send(404);
    }
});

// userMgrAPI.route('user/:username') ...

後記


Express 4 帶來了新的方法,讓我們可以更好的管理 Router,這讓開發者們可以輕鬆許多,如果你有新的專案要開跑,不妨快來嘗試一下全新的 Express!

Feature-oriented Programming with Node.js

2014年6月8日星期日
Feature-oriented Programming(FOP)這個名詞對很多人來說應該相當陌生,這是一個鮮少人討論的開發模式,尋找了一下,也找不太到中文的說明和翻譯,所以我就暫時稱它為『特色導向程式開發』。如果你有興趣,可以去搜尋它,或使用關鍵字『Feature-oriented Software Development』找到更多資源。也有一個以 FOP 概念所改良的 C++ 語言『FeatureC++』,可以參考。

因為筆者眼前專注於 Node.js 的發展,所以也用 Node.js 實作了一非常個簡單的 Framework 『wag.js』,讓我們可以在 JavaScript 語言的環境下,引用 FOP 的開發概念來設計軟體。你可以用 npm 直接安裝他:

npm install wag.js

之所以會一時興起研究 FOP,是因為多年以來,一直為了重覆開發所苦,很多相同的東西一直不停重覆做了又做,總覺得自己一直在浪費時間,所以想試著尋找個方法來改善眼前的窘境。最麻煩的問題在於,雖然早就應用了許多模組化方法,我們也能把很多功能事先做好打包好,但仍然像個有數不清節點的連連看遊戲,讓人花不少時間在上面編織程式軟體。重點是,當其中有絕大多數的過程是一樣的時候,總讓人做得很無力。

你可以想一想,當你想要寫一個新的專案時,你通常最大的阻力是什麼?又有多少繁雜的事,每次開新專案時都重覆在做?而最令人煩燥的是,因為專案類型的不同,有些微的差異,所以難以用一個標準自動化的方式去完成。

這也是一般人和工程師最大的差別,甚至是造成溝通不良。一般人總是想著是我要什麼功能、再增加哪些功能;工程師則是一直在想著,我要挑選使用哪些功能模組,又該怎麼從數不清的可能中,選一個可行的方法組裝他們?如果想成捏黏土來比喻,一般人會捏個大概形狀,再往上慢慢堆想要的東西,慢慢趨近成果,而工程師會捏好各個部位,再把他一一拼湊起來。

我想,當你能回憶起客戶或老闆跟你說:『這功能很簡單,你應該能馬上做好』,你應該就能更明白我所說的,一般人與工程師的思維差異。不過,事實上,工程師思維和一般人沒兩樣,也是把東西堆起來,只是,工程師所設計出來的各種部位的元件,總是很難拼湊在一起,必需要花些功夫。

到底,有沒有什麼辦法,可以減少各種元件和功能模組的拼湊困難呢?讓我們可以更專心的做一個有更多功能的產品?或是更快將產品捏出來呢?

經過一些試驗後發現,Feature-oriented Programming 是個很值得嘗試的開發模式,概念如其名特色功能導向(Feature-oriented),選擇需要或想實作的產品特色後,再做細節修正。你可以到 wag.js 的 examples 目錄找到一些範例,會更瞭解其概念。


Hello World

首先看 helloworld 這支範例程式:
var Wag = require('wag.js');
var wag = new Wag();

// Create an app with web engine
var app = wag.mixin('web');

// Run app
app.run(function() {
        app.engine.listen(8000);
});
這支程式執行後,會運行一支 Web Server 在 8000 port,若使用瀏覽器去開,則會得到一個 『Hello world!』的訊息。

程式的執行邏輯很簡單:建立一個 wag.js 的物件,然後用這個物件去建立一個 app,並為其引入 web 引擎,最後將這個 app 跑起來初始化,待初使化完成後就去跑 app 裡的 listen() ,開始監聽 8000 port。

比較特別要說明的部份,是在建立 app 時,便要使用 mixin() 選擇需要引入哪些引擎功能。因為大多數 Node.js 開發者對 Express 非常熟悉,且有高需求量,所以筆者已經設計了一個名為『web』的功能模組來包裝了 Express ,以便示範和供多數開發者使用。為了與 Node.js 本身的『模組』一詞區隔,在 wag.js 裡面便把它叫做引擎(Engine)。

更複雜的應用程式

若是要開發更複雜的應用程式,我們除了引入 wag.js 已經內建的 web 引擎外,還可以以 web 引擎為基礎,做擴充、修改或是引伸設計。接下來的另一個範例 guestbook (留言板)就可以說明:
var Wag = require('wag.js');

var app = new Wag();
app.addPath('engineDirs', __dirname + '/engines');
app.mixin([
    'web',
    'guestbook'
]);

app.run(function() {
   app.engine.listen();
});
和前一個 helloworld 相較之下,我們多指定一個路徑,讓 wag.js 去找到我們自定的引擎(Engine)載入。然後除了 web 引擎之外,我們也另外增加了一個自定的 guestbook 引擎去擴充 app 的功能,讓 app 可以顯示留言的內容。

如前文所提到,開發概念上,就是在一開始選擇並引入想要的功能,創造一個合成好的程式,最後再去執行和啟動這個程式。

wag.js 尋找並載入引擎的順序是:

  1. wag.js 內建的引擎
  2. 開發者指定的路徑


在這個範例中, wag.js 會載入三個引擎的程式碼然後合成:

  1. wag.js 內建的 web 引擎
  2. 開發者自定的 web 引擎
  3. 開發者自定的 guestbook 引擎

基於 web 引擎的擴充

你可以在 engines 裡面找到一個自定的 web 引擎,事實上,如果你比對了 wag.js 內建的 web 引擎後就會發現,我們並非是在重寫一個新的,而是繼承了 wag.js 內建的 web 引擎後,修改並加上了一些功能。

wag.js 內建原生的 web 引擎長下面這模樣:
var express = require('express');

var Web = module.exports = function() {
        var self = this;

        self.express = express();
        self.server = null;
};

Web.prototype.configure = function(callback) {
        var self = this;

        callback();
};

Web.prototype.routers = function(callback) {
        var self = this;

        self.express.get('/', function(req, res) {
                res.send('Hello! Wag Web!');
        });

        callback();
};

Web.prototype.listen = function(port) {
        var self = this;

        var _port = port || Web.Wag.settings.web.port;

        self.configure(function() {
                self.routers(function() {
                        self.server = self.express.listen(_port);
                });
        });
};

我們在 app 中自定的引擎:
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');

var Web = module.exports = function() {
        var self = this;

        Web.super_.call(this);
};

Web.prototype.configure = function(callback) {
        var self = this;

        self.express.set('view engine', 'jade');
        self.express.set('views', path.join(__dirname, '..', '..', 'views'));
        self.express.use(express.static(path.join(__dirname, '..', '..', 'public')));
        self.express.use(bodyParser());

        callback();
};

Web.prototype.routers = function(callback) {

        // Clear route settings
        callback();
};
當這兩支引擎合成後,會和下面的程式有相同功能:
var express = require('express');

var Web = module.exports = function() {
        var self = this;

        self.express = express();
        self.server = null;
};

Web.prototype.configure = function(callback) {
        var self = this;
        self.express.set('view engine', 'jade');
        self.express.set('views', path.join(__dirname, '..', '..', 'views'));
        self.express.use(express.static(path.join(__dirname, '..', '..', 'public')));
        self.express.use(bodyParser());
        callback();
};

Web.prototype.routers = function(callback) {
        var self = this;


        callback();
};

Web.prototype.listen = function(port) {
        var self = this;

        var _port = port || Web.Wag.settings.web.port;

        self.configure(function() {
                self.routers(function() {
                        self.server = self.express.listen(_port);
                });
        });
};

這支自定的 web 引擎程式,大概有三個部份要說明:


首先,運用 Web.super_.call(),讓引擎被初始化時,可以引用 wag.js 內建的建構子(Constructor),而不是直接取代 wag.js 內建的 web 引擎。

接著,取代原生的 configure 方法,為其加上 Jade 和 view 的支援,也讓 express 可以存取 public 路徑底下的靜態文件,也支援 body 的處理。

將原生的 routers 方法給取代並不做任何事,讓原生引擎中對應『/』路徑的 hello world 訊息失效。(我們會在稍後的 guestbook 引擎內設計新的路徑和頁面內容)

建立一個新的引擎,為應用程式增加功能

既然要做留言板,就少不了顯示留言和張貼留言的功能,為了增加這些功能,我們可以設計一個新的引擎『guestbook』來達成。

var Guestbook = module.exports = function() {
        var self = this;

        Guestbook.super_.call(this);

        self.articles = [
                {
                        title: 'What\'s Wag.js',
                        text: 'Wag.js is a feature-oriented based application framework.'
                },
                {
                        title: 'What\'s News',
                        text: 'Guestbook example was added.'
                }
        ];
};

Guestbook.prototype.routers = function(callback) {
        var self = this;

        function routeInit() {

                self.express.get('/', function(req, res) {
                        res.render('index', {
                                articles: self.articles
                        });
                });

                self.express.get('/post', function(req, res) {
                        res.render('post');
                });

                self.express.post('/post', function(req, res) {

                        if (!req.body.title || !req.body.text) {
                                res.end('Please enter title and message!');
                                return;
                        }

                        // Save to article database
                        self.articles.push({
                                title: req.body.title,
                                text: req.body.text
                        });

                        res.redirect('/');
                });

                callback();
        }

        Guestbook.super_.prototype.routers.apply(this, [ routeInit ]);
};

簡單來說,guestbook 引擎,也是繼承了之前的 web 引擎再做擴充,所以可以看到在一開始的建構子(Constructor)中,也用到了 super_.call()。然後我們定義了一個名為『articles』的 Member,做為存放留言使用。

為了要顯示網頁,想當然爾要定義路徑和將回傳的網頁內容,分別為『/』、『/post』。而你可以看到的 Guestbook.super_.prototype.routers.apply(),只是為了繼承之前 web 引擎的 routers 方法,避免覆概舊有的功能(雖然之前的 web 引擎內的 routers 已經沒有任何功能,所以要省略也可以)。

總結


p1 = j • f       -- program p1 has features j and f

在 FOP 的概念下,應用程式就是許多元件(在 Wag.js 之下是以引擎 Engine 為單位)所拼成的一個大集合體,如果你有 OOP (物件導向的概念)你可以想像它是一個不斷串接並繼承下去的物件,此外,引入的先後順續是有差別的,這會牽涉到相依性的問題。

FOP 能讓開發者如預期的慢慢把功能疊上去,如果設計得當,同一個功能可以拿到各種專案上去使用,只需要讓應用程式引入便馬上可以運作。如筆者最近正在嘗試用 wag.js 開發數個完全不同用途的網站專案,都會用到會員註冊和管理的功能。但藉由引擎這樣的設計,只需要將直接將功能搬移到不同專案中,就可以讓會員系統在不同專案中運作,而且具有擴充的彈性,而不需要為不同專案重新開發。

後記


wag.js 尚未完全實做 FOP 的所有概念,只是粗淺的起頭而已。

未來應該要讓引擎本身,也能像 app 那樣,可以用數個引擎合成出來。

若是 wag.js 這樣的開發概念可行,之後也許可以推出像 npm 這樣的線上模組系統也說不定。

不一樣的 Node.js!終於完成的新書前來報到!

2014年5月4日星期日
這陣子,最怕的就是有人問:『書什麼時候要發行?』,參加社群聚會或活動時,有人會問我,平常周圍朋友也會問我,工作上隊友和客戶都會問我,只有一些親人根本不知道不知道我在寫書,所以不會問我。如果你問我,寫書這件事上,你最怕的是什麼?我會跟你說,出版社截稿日期的死線(Deadline)並不是最可怕的,最可怕的是,它是一種詛咒,讓周圍人都會特別關心你的一種魔法。而這個關心的壓力,讓人喘不過氣來。

還好,書總算從初稿、校稿到排版完成,直到出版社編輯傳來最後的封面設計,才讓人鬆了一口氣。可以告訴大家,書總算要真正出來了!



事實上,國內 Node.js 的書其實並不多,其中幾乎多為翻譯書,或是從簡體直接翻成繁體中文的書。這讓這幾年一直在到處推廣 Node.js 的我,相當困擾。每當有人問起 Node.js 有什麼書可以看時,我總是無法推薦出很多本給大家參考。所以,當出版社的編輯跟我說,希望寫一本 Node.js 的中文書時,我其實相當開心。

還記得當時接下這個任務後,我便開始思考和回想,在推廣和使用 Node.js 時,所遭遇到的困難是什麼?我能否藉這本書來幫助讀者排除這些困難?甚至給讀者一個更大的視野,一個 Node.js 可以發揮在各種領域的可能性。

此外,網路時代來臨後,有太多的新資料都可以從網路上取得,這本書要提供什麼內容,更是需要被探討的問題。尤其是 Node.js 這樣版本號快速上升的新技術,可能書還沒寫完,內容就已經過時。在種種思索下,期望本書的定位,將帶給大家的不是絕對的技術新知,而是一本學習 Node.js 的參考書和技術指南,讓想入門的讀者可以學會怎麼使用 Node.js,讓已經懂的人可以知道 Node.js 還能做些什麼不一樣的事,更進一步知道從哪裡去得到更多 Node.js 的新知。如果這本書在幾年後,仍有六成的資料值得參考,那就算完美達成我賦予它的任務了。

從引領初學者的角度來看,並參考了一些市上的 Node.js 書籍,也從過去教育訓練和推廣的場合發現一個現象。Node.js 不是什麼很難的新東西,如果有人已經很熟悉 JavaScript 語言,而且能寫的很好,Node.js 根本是小菜一碟。所以,多半不得其門而入的人,是對 JavaScript 不這麼熟悉,或是想使用 Node.js 技術進行第一次接觸程式開發的人。只不過,往往市面上的 Node.js 書籍,多半假設讀者已經相當了解 JavaScript 語言,所以對 JavaScript 不會多加說明,這的確會造成一些人的困擾。

所以,在寫這本『不一樣的 Node.js』時,就希望從 JavaScript 語言開始講起,除了先破除許多人對 JavaScript 的長久的迷思,也讓從來沒接觸過程式語言或是半路出家來寫 JavaScript 程式的人,可以從語言的角度入門,再去一步步學習 Node.js 的應用程式開發。又因為在使用 Node.js 開發時,有很多的問題是出現在 JavaScript 語言的概念上,所以在書的最後,也會特別提到一些 JavaScript 語言的一些問題和陷阱。因此,稍微著重或是從 JavaScript 語言的角度出發,是本書比較大的特色之一。

當然,介紹 Node.js 基本知識是肯定有的,只是與一些快速直接切入開發網站系統的教學書不一樣,這本書在進入 MVC Web Framework 章節前,會先說明如何撰寫『純』的 Node.js 應用程式,包括了開發傳檔程式和後門程式等。

前面說到這本書也期望給讀者更大的視野,讓 Node.js 不只是聚焦在 Web 上。所以,在本書的後段章節,除了會提及和整理桌面應用程式的開發,也會討論如何在嵌入式(Embedded System)上使用 Node.js。

無論如何,書總算是寫完了,雖然校稿了很多次,但難免有遺漏,還請包涵。只希望這本書能夠讓更多人喜愛使用 Node.js!

後記

世界上已經有太多人在探討如何使用各種 Web Framework 來開發網站應用程式,所以其實如果可以,想花多點章節在討論使用純的 Node.js,以及利用第三方模組來快速實作許多強大的功能。例如,教讀者使用 Node.js 寫 IRC Bot,然後讓大家可以自己寫 Twitch 的聊天室 Bot。

不過,這些想法,也許要等收集完讀者回饋後,或有要寫下本書後,再來看怎麼改進吧!不過至少有機會的話,肯定會在 Blog 持續討論相關的議題。:-)
Copyright @ 2013 Fred's blog. Designed by Templateism | MyBloggerLab
載入中…

誰在追蹤 Fred

您可以贊助 Fred 持續寫作

廣告與公益


Blog Archive