多くの場合、
Node.JSサーバーは、他のHTTPソースから動的データを受信し、このデータに基づいて集約された応答を形成するアグリゲーターサービスとして使用されます。
受信したデータを処理するには、ファイルの元のセットを処理する外部プロセス(ImageMagickやffmpegユーティリティなど)を使用すると便利です。
nginxサーバーのバックエンドとして機能し、画像セットのCSSスプライトを生成するHTTPサーバーの例を使用して、これを考慮してください。
非同期読み取り/書き込み
クライアント接続プール
Node.JSのHTTPクライアントオブジェクトはそれぞれ1つのTCP接続で動作し、順番にリクエストを行うため、本当に高速に動作する場合は、クライアントプールを整理する必要があります(各接続を作成することと1つの接続を使用することとのトレードオフ)並行して)。
すべての初期リクエストを
example.comに送信するという仮定から、最もプリミティブなプールを作成し
ます :80。
var ClientPool = function ()
{
this .poolSize = 0;
this .freeClients = [];
};
ClientPool.prototype.needClient = function ()
{
this .freeClients.push( this .newClient());
this .poolSize++;
};
ClientPool.prototype.newClient = function ()
{
return http.createClient(80, 'example.com' );
};
ClientPool.prototype.request = function (method, url, headers)
{
if ( this .freeClients.length == 0)
{
this .needClient();
}
var client = this .freeClients.pop();
var req = client.request(method, url, headers);
return req;
};
ClientPool.prototype.returnToPool = function (client)
{
this .freeClients.push(client);
};
var clientPool = new ClientPool();
* This source code was highlighted with Source Code Highlighter .
必要に応じて、複数のホストへの接続を許可することでプールのアーキテクチャを変更できます。また、プールサイズを上から制限します(負荷が最小の接続の要求を分散します)。 宿題のままにします。
ファイルの取得と保存
HTTPリクエストを実行し、コンテンツをファイルに保存するには非同期関数が必要です。 その特徴は、非同期操作の2つのストリームが一度に実行されることです(元のHTTPストリームの読み取りとファイルへの書き込み)。 さらに、ファイルを正常に閉じて、必ずしも連続して実行されるとは限らないすべての書き込み操作が完了したときにのみコールバック関数を呼び出すことができます。
以下に実装例を示します。
var getFile = function (url, path, callback)
{
fs.open(path, 'w' , 0600, function (err, fd)
{
if (err)
{
callback(err);
return ;
}
var request = clientPool.request( 'GET' , url, { 'Host' : 'example.com' });
request.on( 'response' , function (sourceResponse)
{
var statusCode = parseInt(sourceResponse.statusCode);
if (statusCode < 200 || statusCode > 299)
{
sourceResponse.on( 'end' , function ()
{
clientPool.returnToPool(sourceResponse.client);
});
callback( 'Bad status code' );
return ;
}
var writeErr = null ;
var writesPending = 0;
var sourceEnded = false ;
var checkPendingCallback = function ()
{
if (!sourceEnded || writesPending > 0)
{
return ;
}
fs.close(fd, function (err)
{
err = err ? err : writeErr;
if (err)
{
removeFile(path);
callback(err);
return ;
}
// No errors and all written
callback( null );
});
};
var position = 0;
sourceResponse.on( 'data' , function (chunk)
{
writesPending++;
fs.write(fd, chunk, 0, chunk.length, position, function (err, written)
{
writesPending--;
if (err)
{
writeErr = err;
}
checkPendingCallback();
});
position += chunk.length;
});
sourceResponse.on( 'end' , function ()
{
sourceEnded = true ;
checkPendingCallback();
clientPool.returnToPool(sourceResponse.client);
});
});
request.end();
});
};
* This source code was highlighted with Source Code Highlighter .
nginxとサーバー間の相互作用のメカニズム
各リクエストに対してスプライトを生成しないように、出力スプライトを保存します。たとえば、クラウンによって最も古いものを削除します。 ファイルが既に存在する場合、nginxはtry_filesルールに従ってそれを返します。 それ以外の場合、リクエストはバックエンドにリダイレクトされ、目的のファイルが作成されます。X-Accel-Redirectを使用すると、nginxに内部の場所からファイルを返すように要求され、同じ物理スペースにつながります。
この場合、nginx設定は次のようになります。
アップストリームsprite_gen {
サーバー127.0.0.1:14239;
}
場所/ out_folder / {
エイリアス/ var / sprite-gen / out_folder /;
内部;
}
場所/ {
エイリアス/ var / sprite-gen / out_folder /;
try_files $ uri @transcoder;
}
ロケーション@transcoder {
proxy_pass http:// sprite_gen;
}
この例は完全であるとは主張していませんが、その助けを借りて、キャッシュを使用して、一部を含む大きなファイルを提供するのが良いです。
ファイルが小さく、画像が欠落しているスプライトの再生成を制御する方が適切な場合は、proxy_no_cache $ http_pragmaの形式のルールでnginx側にキャッシュする方が適切です。
いくつかのファイルを取得します
以下は、一連のファイルを取得し、スプライトを作成し、nginxの結果を返すHTTPサーバーのフラグメントです。
var outPath = '' ; //
var imageUrls = []; // .
var images = []; // .
var waitCounter = images.length;
var needCache = true ; // , ,
var handlePart = function (url, pth)
{
getFile(url, pth, function (err)
{
waitCounter--;
if (err)
{
removeFile(pth);
var pth = placeholder_path;
needCache = false ;
}
if (waitCounter == 0)
{
makeSprite(images, outPath, function (err)
{
if (err)
{
response.writeHead(500, {
'Content-Type' : 'text/plain' ,
});
response.end( 'Trouble' );
return ;
}
var headers = {
'Content-Type' : 'image/png' ,
'X-Accel-Redirect' : outUrl
};
if (needCache)
{
headers[ 'Cache-Control' ] = 'max-age:315360000, public' ;
headers[ 'Expires' ] = 'Thu, 31 Dec 2037 23:55:55 GMT' ;
}
else
{
headers[ 'Cache-Control' ] = 'no-cache, no-store' ;
headers[ 'Pragma' ] = 'no-cache' ;
}
response.writeHead(200, headers);
response.end();
});
}
});
};
for ( var i = 0; i < imageUrls.length)
{
handlePart(imageUrls[i], images[i]);
}
* This source code was highlighted with Source Code Highlighter .
外部プロセスを介して出力ファイルを生成します
Node.JSで外部プロセスを制御するのは簡単で便利です。 デバッグの便宜上、外部プロセスで生成された出力をコンソールにコピーします。 スプライトを作成するには、GraphicsMagickパッケージ(安定したAPIと良好なパフォーマンスを備えたImageMagickフォーク)を選択します。
var spriteScript = '/usr/bin/gm' ;
var placeholder = path.join(__dirname, 'placeholder.jpg' );
var getParams = function (count)
{
return ( 'montage +frame +shadow +label -background #000000 -tile ' + count + 'x1 -geometry +0+0' ).split( ' ' );
};
var removeFile = function (path)
{
fs.unlink(path, function (err)
{
if (err)
{
console.log( 'Cannot remove ' + path);
}
});
};
var cleanup = function (inPaths, placeholder)
{
for ( var i = 0; i < inPaths.length; i++)
{
if (inPaths[i] == placeholder)
{
continue ;
}
removeFile(inPaths[i]);
}
};
var makeSprite = function (inPaths, outPath, placeholder, callback)
{
var para = getParams(inPaths.length).concat(inPaths, outPath);
console.log([ 'run' , spriteScript, para.join( ' ' )].join( ' ' ));
var spriter = child_process.spawn(spriteScript, para);
spriter.stderr.addListener( 'data' , function (data)
{
console.log(data);
});
spriter.stdout.addListener( 'data' , function (data)
{
console.log(data);
});
spriter.addListener( 'exit' , function (code, signal)
{
if (signal != null )
{
callback( 'Internal Server Error - Interrupted by signal' + signal.toString());
return ;
}
if (code != 0)
{
callback( 'Internal Server Error - Code is ' + code.toString());
return ;
}
cleanup(inPaths, placeholder);
callback( null );
});
};
* This source code was highlighted with Source Code Highlighter .
小さなニュアンス
一時ファイルの名前を作成します
Process.pidとリクエストカウンター(たとえば、path.join( '/ tmp'、['source-file'、Process.pid、requestCounter] .join( '-'))を使用してファイル名を生成することをお勧めします。この場合、関数次の要求の処理は、現在の要求のすべてのステップが完了する前に開始される可能性があるため、要求処理は引数として要求カウンターを受け取る必要があります。
過去のプロセスから一時的なデータを消去します
すべての一時ファイルにsource-pid ...またはsprite-pid- ...という名前を付けます。
var fileExpr = /^(?:source|sprite)\-(\d+)\b/;
var storagePath = '/tmp/' ;
var cleanupOldFiles = function ()
{
fs.readdir(storagePath, function (err, files)
{
if (err)
{
console.log( 'Cannot read ' + storagePath + ' directory.' ;
return ;
}
for ( var i = 0; i < files.length; i++)
{
var fn = files[i];
m = fileExpr.exec(fn);
if (!m)
{
continue ;
}
var pid = parseInt(m[1]);
if (pid == process.pid)
{
continue ;
}
removeFile(path.join(storagePath, fn));
}
});
};
* This source code was highlighted with Source Code Highlighter .
リクエスト処理スケルトン
ある時点(timespec)からフォトアルバムスプライトを取得するとします。
#!/usr/bin/env node
var child_process = require( 'child_process' );
var http = require( 'http' );
var path = require( 'path' );
var fs = require( 'fs' );
var routeExpr = /^\/?(\w)\/([^\/]+)\/(\d+)\/(\d+)x(\d+)\.png$/;
var fileCounter = 0;
http.createServer( function (request, response)
{
if (request.method != 'GET' )
{
response.writeHead(405, { 'Content-Type' : 'text/plain' });
response.end( 'Method Not Allowed' );
return ;
}
var m = routeExpr.exec(request.url);
if (!m)
{
response.writeHead(400, { 'Content-Type' : 'text/plain' });
response.end( 'Bad Request' );
return ;
}
var mode = m[1];
var chapter = m[2];
var timespec = parseInt(m[3]);
var width = parseInt(m[4]);
var height = parseInt(m[5]);
fileCounter++;
var moments = [timespec];
addWantedMoments(moments, mode)
var runner = function (moments, fileCounter, width, height)
{
var waitCounter = moments.length;
var outPath = path.join(storagePath, [ 'sprite' , process.pid, fileCounter].join( '-' ) + '.png' );
var needCache = true ;
for ( var i = 0; i < moments.length; i++)
{
handlePart(i, placeholder);
}
};
request.connection.setTimeout(0);
runner([].concat(moments), fileCounter, width, height);
}).listen(8080, '127.0.0.1' );
console.log( 'Server running at 127.0.0.1:8080' );
cleanupOldFiles();
* This source code was highlighted with Source Code Highlighter .
実際、今では、他のサイトへの一連のリクエストの集約結果として、スプライトを形成する既製のアプリケーションがあります。
仕様(ソースイメージへのリンクを取得するアルゴリズム、プレースホルダーを形成するアルゴリズム、サイズが絶えず変化する場合)を追加することは残っており、これを使用できます。
実際、私のガジェットの1つは動的スプライトジェネレーターとして機能します。