この記事は、私がnode.js学習プロセス中に開発した最近のアイデアであり、私はあなたとそれについて説明します。
node.js HTTPサーバー
node.jsを使用すると、HTTPサービスを非常に簡単に実装できます。最も簡単な例は、公式ウェブサイトの例のようなものです。
コードコピーは次のとおりです。
var http = require( 'http');
http.createserver(function(req、res){
res.writehead(200、{'content-type': 'text/plain'});
res.End( 'Hello World/n');
})。聞きます(1337、 '127.0.0.1');
これにより、ポート1337のすべてのHTTPリクエストを聴くWebサービスが迅速に構築されます。
ただし、実際の生産環境では、一般に、ユーザー向けのフロントエンドWebサーバーとしてNode.jsを直接使用することはめったにありません。主な理由は次のとおりです。
1。Node.jsの単一の読み取り機能に基づいて、その堅牢性保証は開発者にとって比較的高いです。
2。サーバー上の他のHTTPサービスはすでにポート80を占有している可能性があり、ポート80ではないWebサービスは明らかにユーザーフレンドリーではありません。
3.Node.jsは、ファイルIO処理でそれほど利点はありません。たとえば、通常のWebサイトとして、写真などのファイルリソースに同時に応答する必要がある場合があります。
4。分散型負荷シナリオも課題です。
したがって、node.jsをWebサービスとして使用すると、ゲームサーバーインターフェイスやその他の同様のシナリオである可能性が高く、主に直接ユーザーアクセスを必要とせず、データ交換のみを実行するサービスに対処するためです。
Node.jsフロントエンドマシンとしてのNginxに基づくWebサービス
上記の理由に基づいて、node.jsで構築されたWebサイト型の製品である場合、それを使用する従来の方法は、Node.jsのWebサービスのフロントエンドに別の成熟したHTTPサーバーを配置することです。
次に、nginxを逆プロキシとして使用して、node.jsベースのWebサービスにアクセスします。のように:
コードコピーは次のとおりです。
サーバ{
聞く80;
server_name yekai.me;
root/home/andy/wwwroot/yekai;
位置 / {
proxy_pass http://127.0.0.1:1337;
}
場所〜 /.(gif|jpg|png|swf|co|css| js){{{
root/home/andy/wwwroot/yekai/static;
}
}
これにより、上記で提起されたいくつかの問題をより適切に解決できます。
FastCGIプロトコルを使用した通信
ただし、上記のプロキシメソッドについてあまり良くないものがいくつかあります。
1つは、後で制御する必要があるnode.js Webサービスへの直接HTTPアクセスを必要とする可能性のあるシナリオです。ただし、問題を解決したい場合は、独自のサービスを使用したり、ファイアウォールをブロックしたりすることもできます。
もう1つの理由は、プロキシメソッドがネットワークアプリケーションレイヤーのソリューションであることであり、処理キープアライブ、トランク、さらにはCookieなど、クライアントHTTPと対話するデータを直接取得および処理することはあまり便利ではありません。もちろん、これはプロキシサーバー自体の機能と機能的完全性にも関連しています。
それで、私はそれに対処する別の方法を試すことを考えていました。私が最初に考えたのは、現在PHP Webアプリケーションで一般的に使用されているFastCGIメソッドです。
fastcgiとは何ですか
Fast Common Gateway Interface(FastCGI)は、インタラクティブなプログラムがWebサーバーと通信できるプロトコルです。
FastCGIによって生成された背景は、CGI Webアプリケーションの代替として使用されます。最も明白な機能の1つは、FASTCGIサービスプロセスを使用して一連のリクエストを処理できることです。 Webサーバーは、環境変数とこのページの要求を、FastCGIプロセスなどのソケットを介してWebサーバーに接続します。接続は、UNIXドメインソケットまたはTCP/IP接続によってWebサーバーに接続できます。より背景の知識については、ウィキペディアのエントリを参照してください。
node.jsのfastcgi実装
したがって、理論的には、Node.jsを使用してFastCGIプロセスを作成するだけで、Nginxのリスニングリクエストがこのプロセスに送信されることを指定する必要があります。 nginxとnode.jsはどちらもイベント駆動型のサービスモデルであるため、世界に一致する「理論的」ソリューションでなければなりません。自分でやりましょう。
node.jsでは、ネットモジュールを使用してソケットサービスを確立できます。便利なため、UNIXソケット方式を選択します。
nginx構成のわずかな変更で:
コードコピーは次のとおりです。
...
位置 / {
fastcgi_pass unix:/tmp/node_fcgi.sock;
}
...
次のコンテンツを使用して、新しいファイルnode_fcgi.jsを作成します。
コードコピーは次のとおりです。
var net = require( 'net');
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);
});
});
次に、実行されます(許可のため、Nginxとノードのスクリプトが同じユーザーまたは相互権限でアカウントで実行されることを確認してください。そうしないと、Sockファイルを読み書きするときに許可の問題が発生します):
node node_fcgi.js
ブラウザにアクセスすると、ノードスクリプトを実行する端子が通常、次のようなデータコンテンツを受信することがわかります。
コードコピーは次のとおりです。
繋がり
<バッファー01 01 00 01 00 08 00 00 00 01 00 00 00 00 00 00 01 04 00 01 01 87 01 ...>
これは、私たちの理論的基盤が最初のステップを達成したことを証明しています。次に、このバッファーのコンテンツを解析する方法を把握する必要があります。
FastCGIプロトコル財団
FastCGIレコードは、固定長のプレフィックスと、変数数のコンテンツとパッド入りバイトが続きます。レコード構造は次のとおりです。
コードコピーは次のとおりです。
typedef struct {
署名のないcharバージョン。
署名のないcharタイプ。
符号なしchar requestIdb1;
符号なしchar requestIdb0;
符号なしChar ContentLengthB1;
符号なしChar ContentLengthB0;
符号なしのchar paddinglength;
署名されていないcharが予約されています。
符号なしChar ContentData [contentlength];
符号なしのchar paddingdata [paddinglength];
} fcgi_record;
バージョン:FastCGIプロトコルバージョン、デフォルトで1を使用するようになりました
タイプ:レコードタイプは実際に別の状態と見なすことができ、後で詳細に説明します
RequestID:Request ID、返却するときは対応する必要があります。多重化の同時性の場合ではない場合は、ここで1を使用してください
ContentLength:コンテンツの長さ、ここでの最大長は65535です
パディングレングス:パディングの長さは、長いデータを8バイトの完全な整数に記入するために使用されます。主に、より効果的に整合するデータの処理には、主にパフォーマンスの考慮事項に使用されます
予約済み:その後の拡張のための予約バイト
ContentData:実際のコンテンツデータ、後で詳細に説明しましょう
PaddingData:データを入力してください。とにかく0です。直接無視してください。
特定の構造と説明については、公式Webサイトドキュメント(http://www.fastcgi.com/devkit/doc/fcgi-spec.html#s3.3)を参照してください。
部分をリクエストします
それは非常に簡単に思えます、ただ解析して一度にデータを取得します。ただし、ここにはピットがあります。つまり、ここで定義されているのは、バッファ構造全体ではなく、データユニット(レコード)の構造です。バッファー全体は、1つのレコードと1つのレコードで構成されています。最初は、フロントエンド開発に慣れている学生にとっては容易ではないかもしれませんが、これはFastCGIプロトコルを理解するための基礎であり、後でより多くの例を見ていきます。
したがって、以前に得たタイプに基づいてレコードを解析し、レコードを区別する必要があります。すべてのレコードを取得するための単純な関数は次のとおりです。
コードコピーは次のとおりです。
関数getRcds(data、cb){
var rcds = []、
start = 0、
length = data.length;
return function(){
if(start> = length){
CB && CB(RCDS);
rcds = null;
戻る;
}
var end = start + 8、
header = data.slice(start、end)、
version = header [0]、
type = header [1]、
requestId =(header [2] << 8) + header [3]、
contentlength =(header [4] << 8) + header [5]、
paddinglength = header [6];
start = end + contentlength + paddinglength;
var body = contentlength? data.slice(end、contentlength):null;
rcds.push([type、body、requestid]);
return arguments.callee();
}
}
//使用
sock.on( 'data'、function(data){
getrcds(data、function(rcds){
})();
}
これは単なるプロセスであることに注意してください。ファイルのアップロードなどの複雑な状況がある場合、この関数は最も単純なデモに適していません。同時に、RequestIDパラメーターも無視されます。多重化している場合、無視することはできず、処理ははるかに複雑になる必要があります。
次に、タイプに応じてさまざまなレコードを処理できます。タイプの定義は次のとおりです。
コードコピーは次のとおりです。
#define fcgi_begin_request 1
#define fcgi_abort_request 2
#define fcgi_end_request 3
#define fcgi_params 4
#define fcgi_stdin 5
#define fcgi_stdout 6
#define fcgi_stderr 7
#define fcgi_data 8
#define fcgi_get_values 9
#define fcgi_get_values_result 10
#define fcgi_unknown_type 11
#define fcgi_maxtype(fcgi_unknown_type)
次に、記録されたタイプに従って実際のデータを解析できます。最も一般的に使用されるFCGI_PARAMS、FCGI_GET_VALUES、およびFCGI_GET_VALUES_RESULTのみを使用して説明します。幸いなことに、それらの分析方法は一貫しています。他のタイプのレコードの解析には、独自の異なるルールがあり、仕様の定義を参照して実装できます。ここでは詳しく説明しません。
FCGI_PARAMS、FCGI_GET_VALUES、FCGI_GET_VALUES_RESULTはすべて「エンコードNAME-VALUE」タイプデータです。標準形式は次のとおりです。名前の長さの形式で送信され、次に値の長さが続き、その後に値が続き、1つのバイトで127バイト以下をエンコードできますが、長さは常に4バイトでエンコードされます。長さの最初のバイトの高いビットは、長さのエンコード方法を示します。 0の高いビットは、バイトエンコードメソッドを意味し、1は4バイトエンコードメソッドを意味します。長い名前や短い値の場合など、包括的な例を見てみましょう。
コードコピーは次のとおりです。
typedef struct {
符号なしのchar namelengthb3; / * namelengthb3 >> 7 == 1 */
符号なしのchar namelengthb2;
符号なしのchar namelengthb1;
Unsigned Char namelengthb0;
署名されていないchar valuelengthb0; / * valuelengthb0 >> 7 == 0 */
署名されていないchar namedata [namelength
((b3&0x7f)<< 24) +(b2 << 16) +(b1 << 8) + b0];
Unsigned Char valuedata [valuelength];
} fcgi_namevaluepair41;
対応する実装JSメソッドの例:
コードコピーは次のとおりです。
関数parseparams(body){
var j = 0、
params = {}、
length = body.length;
while(j <length){
var名、
価値、
namelength、
valuelength;
if(body [j] >> 7 == 1){
namelength =((body [j ++]&0x7f)<< 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} それ以外 {
namelength = body [j ++];
}
if(body [j] >> 7 == 1){
valuelength =((body [j ++]&0x7f)<< 24)+(body [j ++] << 16)+(body [j ++] << 8)+body [j ++];
} それ以外 {
valuelength = body [j ++];
}
var ret = body.asciislice(j、j + namelength + valuelength);
name = ret.substring(0、namelength);
value = Ret.Substring(namelength);
params [name] = value;
j + =(namelength + valuelength);
}
パラメージを返します。
}
これにより、さまざまなパラメーターと環境変数を取得する簡単な方法が実装されます。以前のコードを改善し、クライアントIPを取得する方法を示します。
コードコピーは次のとおりです。
sock.on( 'data'、function(data){
getrcds(data、function(rcds){
for(var i = 0、l = rcds.length; i <l; i ++){
var bodydata = rcds [i]、
type = bodydata [0]、
body = bodydata [1];
if(body &&(type === types.fcgi_params || type === types.fcgi_get_values || type === types.fcgi_get_values_result){
var params = parseparams(body);
console.log(params.remote_addr);
}
}
})();
}
これまでのところ、FastCGIリクエストパーツの基本を理解しており、応答部分を実装し、最終的に簡単なEcho Replyサービスを完了します。
応答部分
応答部分は比較的単純です。最も簡単な場合、2つのレコード、つまりFCGI_STDOUTとFCGI_END_REQUESTを送信するだけです。
エンティティの特定のコンテンツについては説明しません。コードを見てください。
コードコピーは次のとおりです。
var res =(function(){
var maxlength = math.pow(2、16);
関数buffer0(len){
新しいバッファー((new Array(len + 1))。Join( '/u0000'));
};
function writestdout(data){
var rcdstdouthd = new Buffer(8)、
contentlength = data.length、
paddinglength = 8 -contentlength%8;
rcdstdouthd [0] = 1;
rcdstdouthd [1] = types.fcgi_stdout;
rcdstdouthd [2] = 0;
rcdstdouthd [3] = 1;
rcdstdouthd [4] = contendlength >> 8;
rcdstdouthd [5] = contendlength;
rcdstdouthd [6] = paddinglength;
rcdstdouthd [7] = 0;
return buffer.concat([rcdstdouthd、data、buffer0(paddinglength)));
};
関数writehttphead(){
return writestdout(new Buffer( "http/1.1 200 ok/r/ncontent-type:text/html; charset = utf-8/r/nconnection:close/r/n/r/n"));
}
関数writehttpbody(bodystr){
var bodybuffer = []、
body = new Buffer(bodystr);
for(var i = 0、l = body.length; i <l; i + = maxlength + 1){
bodybuffer.push(writestdout(body.slice(i、i + maxlength)));
}
return buffer.concat(bodybuffer);
}
functionWriteEnd(){
var rcdendhd = new Buffer(8);
rcdendhd [0] = 1;
rcdendhd [1] = types.fcgi_end_request;
rcdendhd [2] = 0;
rcdendhd [3] = 1;
rcdendhd [4] = 0;
rcdendhd [5] = 8;
rcdendhd [6] = 0;
rcdendhd [7] = 0;
return buffer.concat([rcdendhd、buffer0(8)]);
}
return function(data){
return buffer.concat([writehttphead()、writehttpbody(data)、writeend()]);
};
})();
最も簡単な場合、これにより、完全な応答を送信できます。最終コードを変更します。
コードコピーは次のとおりです。
var visitors = 0;
server.on( 'connection'、function(sock){
訪問者++;
sock.on( 'data'、function(data){
...
var querys = querystring.parse(params.query_string);
var ret = res( 'welcome、' +(querys.name || '親愛なる友人') + '!
sock.write(ret);
ret = null;
sock.end();
...
});
ブラウザを開き、http:// domain/?name = yekaiにアクセスすると、「ようこそ、Yekai!あなたはこのサイトの7番目のユーザーです〜」のようなものを見ることができます。
この時点で、node.jsを使用して最も単純なFastCGIサービスを実装することに成功しました。実際のサービスとして使用する必要がある場合は、ロジックを改善するためにプロトコル仕様を比較するだけです。
比較テスト
最後に、私たちが考慮する必要がある質問は、このソリューションが特に実行可能かどうかです。一部の学生は問題を見たかもしれないので、私は最初に単純な圧力テスト結果を置きます:
コードコピーは次のとおりです。
// fastcgiメソッド:
500人のクライアント、10秒を実行しています。
速度= 27678ページ/分、63277バイト/秒
リクエスト:3295 SUSCEYS、1318は失敗しました。
500人のクライアント、20秒を実行しています。
速度= 22131ページ/分、63359バイト/秒
リクエスト:6523 SUSCEET、854は失敗しました。
//プロキシメソッド:
500人のクライアント、10秒を実行しています。
速度= 28752ページ/分、73191バイト/秒
リクエスト:3724 SUSCEYT、1068は失敗しました。
500人のクライアント、20秒を実行しています。
速度= 26508ページ/分、66267バイト/秒
リクエスト:6716 SUSCUED、2120は失敗しました。
// node.jsサービス方法を直接アクセス:
500人のクライアント、10秒を実行しています。
速度= 101154ページ/分、264247バイト/秒
リクエスト:15729 SUSCUED、1130は失敗しました。
500人のクライアント、20秒を実行しています。
速度= 43791ページ/分、115962バイト/秒
リクエスト:13898 SUSCEYS、699は失敗しました。
プロキシメソッドがFastCGIメソッドよりも優れているのはなぜですか?これは、プロキシスキームの下で、バックエンドサービスがnode.jsネイティブモジュールによって直接実行され、fastCGIスキームがJavaScriptを使用して自分で実装されるためです。ただし、2つのソリューション間に効率に大きなギャップがないこともわかります(もちろん、ここでの比較は単純な状況です。実際のビジネスシナリオでギャップが大きい場合)、node.jsがFastCGIサービスをネイティブにサポートする場合、効率はより良くなるはずです。
ポストスクリプト
プレイを続けることに興味がある場合は、この記事で実装した例のソースコードを確認できます。私は過去2日間でプロトコルの仕様を研究しましたが、それは難しくありません。
同時に、私は戻ってUWSGIと一緒に遊んでいますが、当局者はV8がすでに直接それをサポートする準備ができていると言いました。
私は非常に浅いゲームを持っています。間違いがある場合は、私を修正してコミュニケーションしてください。