本文是我最近對Node.js學習過程中產生的一個想法,提出來和大家一起探討。
Node.js的HTTP伺服器
使用Node.js可以非常容易的實作一個http服務,最簡的例子如官方網站的範例:
1.基於Node.js單執行緒特性的原因,其健壯性的保證對開發人員要求比較高。
2.伺服器上可能已有其他http服務已佔用80端口,而非80端口的web服務對用戶來說顯然不夠友好。
3.Node.js對檔案IO處理並沒太大優勢,如作為常規網站可能需要同時回應圖片等檔案資源。
所以,使用Node.js作為web服務更多可能是作為遊戲伺服器介面等類似場景,大多是處理不需使用者直接存取且僅作資料交換的服務。
基於Nginx作為前端機的Node.js web服務
基於上述原因,如果是使用Node.js搭建的網站形的產品,常規的使用方式是在Node.js的web服務前端放置另一個成熟的http伺服器,如最常使用的是Nginx。
程式碼如下:
🎜>
server{
server{
🎜>
server{
location / {
proxy_pass http://127.0.0.1:1337; }
location ~ .(gif|jpg|png|swf|ico|css|js)$ { root /home/andy/wwwroot/yekai/static; root /home/andy/wwwroot}1kai/static; root /home/andy/ > 🎜>
這樣就比較好的解決了上面提出的幾個問題。
使用FastCGI協定通訊
不過,上述代理的方式也有一些不是很好的地方。一個是有可能的場景是需要控制後面的Node.js的web服務的直接http存取。不過,要解決的話也可以使用自身的服務或依賴防火牆來阻擋。
另外一個是因為代理的方式畢竟是網路應用層上的方案,也不是很方便直接取得和處理與客戶端http互動的數據,例如對keep-alive、trunk甚至cookie等的處理。當然這也與代理伺服器本身的能力和功能完善程度有關。所以,我在想嘗試另一種處理方式,首先想到的就是現在在php web應用上普遍使用的FastCGI的方式。
什麼是FastCGI
快速通用網關介面(Fast Common Gateway Interface/FastCGI)是一種讓互動程式與Web伺服器通訊的協定。
FastCGI產生的背景是用來作為cgi web應用的替代方案,一個最明顯的特點是一個FastCGI服務進程可以用來處理一連串的請求,web伺服器會把環境變數和這個頁面請求透過一個socket例如FastCGI進程與web伺服器連接起來,連接可用Unix Domain Socket或是一個TCP/IP連線。關於更多的背景知識可以參考Wikipedia的詞條。
var server = net.createServer();
server.listen('/tmp/node_fcgi.sock');
server.on('connection', function(sock){
console.log('connection');
sock.on('data', function(data){
console.log(data);
});
});
🎜>
node node_fcgi.js
在瀏覽器訪問,我們看到運行node腳本的終端正常的接收到了資料內容,例如這樣:
這證明我們的理論基礎已經實現了第一步,接下來只需要搞清楚這個buffer的內容如何解析就行了。
FastCGI協定基礎
FastCGI記錄由一個定長前綴後面跟著可變數量的內容和填充位元組組成。記錄架構如下:
type :記錄類型,其實可以當做是不同狀態,後面具體說
requestId :請求id,返回時需對應,如果不是多路復用並發的情況,這裡直接用1就好
contentLength :內容長度,這裡最大長度是65535
paddingLength :填滿長度,作用就是長資料填滿為滿8位元組的整數倍,主要是用來更有效地處理保持對齊的數據,主要是性能考慮
reserved :保留字節,為了後續擴展
contentData :真正的內容數據,一會兒具體說
paddingData :填充數據,反正都是0,直接忽略就好。
請求部分
所以,我們需要將一個記錄一個記錄單獨解析出來,根據前面拿到的type來區分記錄。這裡是一個簡單的獲取所有記錄的函數:
}
//使用
#define FCGI_BEGIN_REQUEST 1
3
#定義FCGI_PARAMS 4
對應的實作js方法範例:
程式碼如下:
nameLength = 身體[
if(body[j] >> 7 == 1){
valueLength = ((body[j] & 0x7f) } else {
valueLength = body[
ength);
params[name] = 值;
j = (nameLength valueLength);
回傳參數;
}
到現在我們已經了解了FastCGI請求部分的基礎,下面接著將回應部分的實現,並最終完成一個簡單的echo應答服務。
回應部分
回應部分相對比較簡單,最簡單的情況只需要發送兩個記錄就行了,那就是FCGI_STDOUT和FCGI_END_REQUEST。
具體記錄實體的內容就不冗餘了,直接看程式碼吧:
return new Buffer((new Array(len 1)).join('u0000'));
function writeStdout(data){
th paddingLength = 8 - contendLength % 8;
rcdStdoutHd[0] = 1;
rcdStdoutHd[1] = TYPES.FCGI_STDOUT;
rcdStdoutHd[4] = contendLength >> 8;
rcdStdoutHd[5] = contendLength;
rcdStoo
return Buffer.concat([rcdStdoutHd, data, buffer0(paddingLength)]);
};
function writeHttpHead(){
function writeHttpBody(bodyStr){
var bodyBuffer = [],
PES.FCGI_END_REQUEST;
rcdEndHd[2] = 0;
rcdEndHd[3] = 1;
rcdEndHd[4] = 0; rcdEndHd[7] = 0;
return Buffer.concat([rcdEndHd, buffer0(8)]);
}
return Buffer.concat([writeHttpHead(), writeHttpBody(data), writeEnd()]);
; 🎜>
在最簡單的情況下,這樣就可以發送一個完整的回應了。把我們最後的程式碼修改一下:
複製程式碼
程式碼如下:
var visitors = 0 visitors = 0 visitors = 0 visitors = 0 visitors = 0 visitors = 0 visitors = 0 visitors = 0 ;
server.on('connection', function(sock){
visitors ;
sock.on('data', function(data){ querystring.parse(params.QUERY_STRING);
...
});
對比測驗
最後,我們需要考慮的問題是這個方案具體是否具有可行性?可能已經有同學看出問題,我先把簡單的壓測結果放上來:
Speed=22131 pages/min, 63359 bytes/sec.
Requests: 6523 susceed, 854 failed.
Requests: 6523 susceed, 854 failed.
//proxy方式:
500 clients, running 10 sec.
Speed=28752 pages/min, 73191 bytes/sec.
Requests: 3724 susceed, 1068 fail.
Speed=26508 pages/min, 66267 bytes/sec.
Requests: 6716 susceed, 2120 failed.
Requests: 6716 susceed, 2120 failed.
//直接存取Node.js服務方式:
500 clients, running 10 sec.
Speed=101154 pages/min, 264247 bytes/sec.
Requests: 15729 susceed. 🎜>
Speed=43791 pages/min, 115962 bytes/sec.
Requests: 13898 susceed, 699 failed.
Requests: 13898 susceed, 699 failed.優於FastCGI方式呢?那是因為在proxy方案下後端服務是直接由Node.js原生模組跑的,而FastCGI方案是我們自己使用JavaScrip實現的。不過,也可以看出兩者方案效率上並沒有很大的差距(當然,這裡對比的只是簡單的情況,如果在真正的業務場景下,差距應該會更大),並且如果Node.js原生支持FastCGI服務,那麼效率上應該會更優。
如果有興趣繼續玩的同學可以查看我本文實現的例子源碼,這兩天研究下了協議規範,其實不難。 同時,回頭準備再玩玩uWSGI,不過官方說v8已經在準備直接支援了。
玩得很淺,如有錯誤歡迎指正交流。