ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js_node.js を使用して単純な FastCGI サーバー インスタンスを実装する

Node.js_node.js を使用して単純な FastCGI サーバー インスタンスを実装する

WBOY
WBOYオリジナル
2016-05-16 16:45:181669ブラウズ

この記事は私が最近 Node.js を勉強しているときに思いついたアイデアで、皆さんと議論したいと思います。

Node.js 用の HTTP サーバー

Node.js を使用して http サービスを実装するのは非常に簡単です。最も簡単な例は、公式 Web サイトに示されているとおりです。

コードをコピー コードは次のとおりです:

var http = require('http');
http.createServer(function (req, res) {
res.writeHead( 200, {'Content -Type': 'text/plain'});
res.end('Hello Worldn');
}).listen(1337, '127.0.0.1');

この方法では、ポート 1337 ですべての http リクエストをリッスンする Web サービスをすばやくセットアップします。
しかし、実際の運用環境では、通常、ユーザーのフロントエンド Web サーバーとして Node.js を直接使用することはほとんどありません。主な理由は次のとおりです。

1. Node.js はシングルスレッドであるため、その堅牢性の保証には開発者にとって比較的高い要件があります。
2. サーバー上にはポート 80 を占有している他の http サービスが存在する可能性があり、ポート 80 以外の Web サービスは明らかにユーザーフレンドリーではありません。
3.Node.js は、ファイル IO 処理ではあまり利点がありません。たとえば、通常の Web サイトとして、画像と他のファイル リソースに同時に応答する必要がある場合があります。
4. 分散負荷シナリオも課題です。

したがって、Node.js を Web サービスとして使用することは、ゲーム サーバー インターフェイスやその他の同様のシナリオとして使用される可能性が高く、そのほとんどはユーザーによる直接アクセスを必要とせず、データ交換のみに使用されるサービスを扱います。 。

フロントエンド マシンとして Nginx に基づく Node.js Web サービス

上記の理由に基づいて、Node.js を使用して構築された Web サイト形式の製品の場合、従来の使用法は、Node.js Web サービスのフロントエンドに別の成熟した http サーバー (Nginx など) を配置することです。が最も一般的に使用されます。
次に、Nginx をリバース プロキシとして使用して、Node.js ベースの Web サービスにアクセスします。例:

コードをコピー コードは次のとおりです:

server{
listen 80;
サーバー名 yekai.me;
ルート /home/andy/wwwroot/yekai;

location / {
proxy_pass http://127.0.0.1:1337;
}

場所 ~ .(gif|jpg|png|swf|ico|css|js)$ {
root /home/andy/wwwroot/yekai/static;
}
}

これにより、上で挙げた問題がより適切に解決されます。

FastCGI プロトコルを使用して通信します

しかし、上記のプロキシ方法にはいくつかの欠点もあります。
考えられるシナリオの 1 つは、基盤となる Node.js Web サービスへの直接の http アクセスを制御する必要があるということです。ただし、それを解決したい場合は、独自のサービスを使用するか、ファイアウォールのブロックに依存することもできます。
もう 1 つの理由は、プロキシ方式は結局のところネットワーク アプリケーション層でのソリューションであり、キープアライブの処理など、クライアント http とやり取りするデータを直接取得して処理するのはあまり便利ではないことです。 、トランク、さらにはクッキーまで。もちろん、これはプロキシサーバー自体の能力や機能の完成度にも関係します。
そこで、別の処理方法を試してみようと考えたのが、現在 php Web アプリケーションで一般的に使用されている FastCGI 方法でした。

FastCGI とは

Fast Common Gateway Interface/FastCGI は、対話型プログラムが Web サーバーと通信できるようにするプロトコルです。

FastCGI の背景は、CGI Web アプリケーションの代替として機能することです。最も明白な機能の 1 つは、FastCGI サービス プロセスを使用して、Web サーバーが環境変数とFastCGI プロセスなどのソケットを介したページ リクエストは、Unix ドメイン ソケットまたは TCP/IP 接続を介して Web サーバーに接続します。背景知識の詳細については、Wikipedia のエントリを参照してください。

Node.js の FastCGI 実装

理論的には、Node.js を使用して FastCGI プロセスを作成し、このプロセスに送信される Nginx のモニタリング リクエストを指定するだけです。 Nginx と Node.js はどちらもイベント駆動型のサービス モデルに基づいているため、「理論的には」自然なソリューションとなるはずです。以下、自分で実装してみましょう。
Node.js の net モジュールを使用してソケット サービスを作成できます。便宜上、unix ソケット メソッドを選択します。
Nginx 側の設定を少し変更します:

コードをコピーします コードは次のとおりです:

..
location / {
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

ブラウザでアクセスすると、ノード スクリプトを実行している端末が次のようなデータ コンテンツを正常に受信していることがわかります:

コード コードは次のとおりです。 00 00 01 04 00 01 01 87 01...>

これは、理論的基礎が最初のステップに達したことを証明しています。次に必要なのは、このバッファーの内容を解析する方法を理解することだけです。

FastCGI プロトコルの基礎


FastCGI レコードは、固定長のプレフィックスと、それに続く可変量のコンテンツとパディング バイトで構成されます。レコード構造は次のとおりです。


コードをコピー

コードは次のとおりです。typedef struct { unsigned char バージョン;
unsigned char タイプ;
unsigned char requestIdB1;
unsigned char requestIdB0;
unsigned char contentLengthB1;
unsigned char contentLengthB0;
unsigned char paddingLength;
unsigned char 予約済み ;
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;


version: FastCGI プロトコルのバージョン。現在のデフォルトは 1 です。多重化と同時実行の場合は、ここで 1 を使用してください。
contentLength: コンテンツの長さ。ここでの最大長は 65535 です。
paddingLength: パディングの長さ、関数長いデータを 8 バイトの整数倍に埋めることです。主に、パフォーマンス上の考慮事項により、アライメントされたままのデータをより効率的に処理するために使用されます。 reserved: 後続の拡張用に予約されたバイトです。

contentData: 実際のコンテンツ データ。詳細は後ほど説明します。
paddingData: パディング データ、とにかくすべて 0 なので、無視してください。

具体的な構造と説明については、公式 Web サイトのドキュメント (http://www.fastcgi.com/devkit/doc/fcgi-spec.html#S3.3) を参照してください。



リクエストセクション

それは非常に簡単に思えます。一度解析してデータを取得するだけです。ただし、ここで落とし穴があります。ここで定義されるのは、バッファ全体の構造ではなく、データ単位 (レコード) の構造です。バッファ全体は 1 つのレコードと 1 つのレコードで構成されます。フロントエンド開発に慣れている人にとっては、最初は理解するのが難しいかもしれませんが、これは FastCGI プロトコルを理解するための基礎であり、後でさらに例を見ていきます。

したがって、各レコードを個別に解析し、前に取得したタイプに基づいてレコードを区別する必要があります。すべてのレコードを取得する簡単な関数を次に示します:


コードをコピー

コードは次のとおりです:

function getRcds(data, cb){
var rcds = [],
start = 0,
length = data.length;
return function (){
if(start >= length){
cb && cb(rcds);
rcds = null;
return;
}
var end = start 8,
ヘッダー = データ.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 contentLengthpaddingLength;

var body = contentLength ? data.slice(end, contentLength) : null;
rcds.push([type, body, requestId]);

return argument.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 はいずれも「コード名-値」タイプのデータであり、標準形式は次のとおりです。值的長さ、後続名、後続値の形式で送信され、127 文字以下の長さは 1 文字以内にコード化でき、それ以上の長さは常に 4 文字以内にコード化されます。長さを示すコード形式は、上位の 0 が 1 文字のコード形式を意味し、1 は 4 文字のコード形式を意味します。

复制代码代码如下:

typedef struct {
unsigned char nameLengthB3;  /* nameLengthB3 >> 7 == 1 */
unsigned char nameLengthB2;
unsigned char nameLengthB1;
unsigned char nameLengthB0;
unsigned char valueLengthB0; /* valueLengthB0 >> 7 == 0 */
unsigned char nameData[nameLength
((B3 & 0x7f) unsigned char valueData[valueLength];
} FCGI_NameValuePair41;

对应の实现jsメソッド例:

= 0,

params = {},

length = body.length; while(j < length){ var name, value, nameLength,値長さ; if(body[j] >> 7 == 1){
nameLength = ((body[j ] & 0x7f) << 24) (body[j ] << 16 ) (body[j ] << 8) body[j ];
} else {
nameLength = body[j ];
}

if(body[j] >> 7 == 1){
valueLength = ((body[j ] & 0x7f) << 24) (body[j ] << 16) (body[j ] << 8) body[j ];
} else {
valueLength = body[j ];
}

var ret = body.asciiSlice(j, j nameLength valueLength);
name = ret.substring(0, nameLength);
value = ret.substring(nameLength);
params[name] = 値;

j = (nameLength valueLength);

}
return params;
}

これにより、さまざまなパラメータと環境変数を簡単に取得できる方法が実現されます。


代码如下:


sock.on('data', function(data){
getRcds(data, function(rcds){
for (var i = 0, l = rcds.length; 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 リクエスト部分の基本を理解しました。次に、レスポンス部分を実装して、最終的に簡単なエコー応答サービスを完成させます。

応答セクション

応答部分は比較的単純で、最も単純なケースでは、FCGI_STDOUT と FCGI_END_REQUEST の 2 つのレコードを送信するだけです。
記録されたエンティティの特定の内容については詳しく説明しません。コードを見てください:

コードをコピー コードは次のとおりです。

var res = (function(){
var MaxLength = Math.pow(2, 16);

関数buffer0(len){
return new Buffer((new Array(len 1)).join('u0000'));
};

function writeStdout(data){
var rcdStdoutHd = new Buffer(8),
contendLength = data.length,
paddingLength = 8 - contendLength % 8;

rcdStdoutHd[0] = 1;
rcdStdoutHd[1] = TYPES.FCGI_STDOUT;
rcdStdoutHd[2] = 0;
rcdStdoutHd[3] = 1;
rcdStdoutHd[4] = contendLength >> 8;
rcdStdoutHd[5] = contendLength;
rcdStdoutHd[6] = パディング長;
rcdStdoutHd[7] = 0;

return Buffer.concat([rcdStdoutHd, data,buffer0(paddingLength)]);
};

function writeHttpHead(){
return writeStdout(new Buffer("HTTP/1.1 200 OKrnContent-Type:text/html; charset=utf-8rnConnection: Closenrn"));
}

function writeHttpBody(bodyStr){
var bodyBuffer = [],
body = new Buffer(bodyStr);
for(var i = 0, l = body.length; i
関数 writeEnd(){
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;
rcd EndHd[7] ] = 0;
return Buffer.concat([rcdEndHd,buffer0(8)]);
}

return function(data){
return Buffer.concat([writeHttpHead(), writeHttpBody(data), writeEnd()]);
};

})();




最も単純なケースでは、これにより完全な応答を送信できます。最終的なコードを変更します:


コードをコピーします

コードは次のとおりです: var Visitor = 0 ;server.on('connection', function(sock){
訪問者 ;
sock.on('data', function(data){
...
var クエリ= querystring.parse(params.QUERY_STRING);
var ret = res('ようこそ、' (querys.name || '親愛なる友人') '! あなたはこのサイトの '訪問者' ユーザーです~') ;
sock.write(ret);
ret = null;
sock.end();
...
});

ブラウザを開いて http://domain/?name=yekai にアクセスすると、「ようこそ、yekai! あなたはこのサイトの 7 人目のユーザーです ~」のようなメッセージが表示されます。
この時点で、Node.js を使用した最も単純な FastCGI サービスの実装に成功しました。実際のサービスとして使用する必要がある場合は、プロトコルの仕様に従ってロジックを改善するだけで済みます。


比較テスト

最後に、考慮する必要がある問題は、この解決策が実現可能かどうかです。この問題に気づいた学生もいるかもしれないので、最初に簡単なストレス テストの結果を投稿します。

コードをコピーします コードは次のとおりです。以下:

//FastCGI モード:
500 クライアント、10 秒実行
速度 = 27678 ページ/分、63277 バイト/秒
リクエスト: 3295 susceed、1318失敗しました。

500 クライアント、20 秒実行。
速度 = 22131 ページ/分、63359 バイト/秒。
リクエスト: 6523 成功、854 失敗。

//プロキシ モード:
500 クライアント、10 秒実行。
速度 = 28752 ページ/分、73191 バイト/秒。
リクエスト: 3724 成功、1068 失敗。

500 クライアント、20 秒実行。
速度 = 26508 ページ/分、66267 バイト/秒。
リクエスト: 6716 成功、2120 失敗。

//Node.js サービス メソッドへの直接アクセス:
500 クライアント、10 秒実行。
速度 = 101154 ページ/分、264247 バイト/秒。
リクエスト: 15729 成功、1130 失敗。

500 クライアント、20 秒で実行。
速度 = 43791 ページ/分、115962 バイト/秒。
リクエスト: 13898 成功、699 失敗。


プロキシ メソッドを使用する理由FastCGI よりも優れていますか?これは、プロキシ ソリューションではバックエンド サービスが Node.js ネイティブ モジュールによって直接実行されるのに対し、FastCGI ソリューションは JavaScript を使用して独自に実装されるためです。ただし、2 つのソリューションの効率に大きな差がないこともわかります (もちろん、ここでの比較は単純な状況にすぎません。実際のビジネス シナリオでは、その差はさらに大きくなるはずです)。 Node.js は FastCGI サービスをネイティブにサポートしているため、効率が向上するはずです。

追記

引き続きプレイしたい場合は、この記事で実装したサンプルのソース コードを確認してください。ここ 2 日間でプロトコルの仕様を勉強しましたが、それは難しいことではありません。
同時に、私は再び uWSGI をプレイする予定ですが、公式によると、v8 はすでにそれを直接サポートする準備ができているとのことです。
ゲームはとても簡単なので、間違いがあれば修正してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。