web-dev-qa-db-ja.com

NodeJS-CoreNodeJSと元のNodeソリューションを使用したプログレスバー付きのファイルアップロード

Ryan Dahlは、ファイルアップロードのプログレスバーの問題を解決するためにNodeJSを発明したと述べています( https://youtu.be/SAc0vQCC6UQ )。 Nodeが導入された2009年に利用可能なテクノロジーを使用して、Expressおよび進行状況の更新を自動的に通知するより高度なクライアント側JavaScriptライブラリの前に、NodeJSはこの問題をどのように正確に解決しましたか?

ここでCoreNodeJSだけを使用しようとすると、リクエストストリームを使用して、ヘッダーを確認し、合計ファイルサイズを取得してから、データの各チャンクのサイズを取得して、完了率を確認できることがわかります。しかし、ブラウザーはrequest.end()まで更新されないように見えるため、これらの進行状況の更新をブラウザーにストリーミングする方法がわかりません。

もう一度、NodeJSがこの進行状況の更新の問題を最初にどのように解決したかについて説明したいと思います。 WebSocketはまだ存在していなかったため、クライアントへのWebSocket接続を開いて、進行状況の更新をブラウザーにストリーミングすることはできませんでした。使用された別のクライアント側のJavaScriptテクノロジーはありましたか?

これが私のこれまでの試みです。進行状況の更新はサーバー側のコンソールにストリーミングされますが、ブラウザーは応答ストリームがresponse.end()を受信したときにのみ更新されます。

var http = require('http');
var fs = require('fs');

var server = http.createServer(function(request, response){
    response.writeHead(200);
    if(request.method === 'GET'){
        fs.createReadStream('filechooser.html').pipe(response);     
    }
    else if(request.method === 'POST'){
        var outputFile = fs.createWriteStream('output');
        var total = request.headers['content-length'];
        var progress = 0;

        request.on('data', function(chunk){
            progress += chunk.length;
            var perc = parseInt((progress/total)*100);
            console.log('percent complete: '+perc+'%\n');
            response.write('percent complete: '+perc+'%\n');
        });

        request.pipe(outputFile);

        request.on('end', function(){
            response.end('\nArchived File\n\n');
        });
    }

});

server.listen(8080, function(){
    console.log('Server is listening on 8080');
});

filechooser.html:

<!DOCTYPE html>
<html>
<body>
<form id="uploadForm" enctype="multipart/form-data" action="/" method="post">
    <input type="file" id="upload" name="upload" />
    <input type="submit" value="Submit">
</form>
</body>
</html>

これが更新された試みです。ブラウザーに進行状況の更新が表示されるようになりましたが、これはRyanDahlが最初に思いついた実際の解決策ではないと確信しています。実稼働シナリオの場合。彼は長いポーリングを使用しましたか?そのソリューションはどのようになりますか?

var http = require('http');
var fs = require('fs');

var server = http.createServer(function(request, response){
    response.setHeader('Content-Type', 'text/html; charset=UTF-8');
    response.writeHead(200);

    if(request.method === 'GET'){
        fs.createReadStream('filechooser.html').pipe(response);     
    }
    else if(request.method === 'POST'){
        var outputFile = fs.createWriteStream('UPLOADED_FILE');
        var total = request.headers['content-length'];
        var progress = 0;

        response.write('STARTING UPLOAD');
        console.log('\nSTARTING UPLOAD\n');

        request.on('data', function(chunk){
            fakeNetworkLatency(function() {
                outputFile.write(chunk);
                progress += chunk.length;
                var perc = parseInt((progress/total)*100);
                console.log('percent complete: '+perc+'%\n');
                response.write('<p>percent complete: '+perc+'%');
            });
        });

        request.on('end', function(){
            fakeNetworkLatency(function() {
                outputFile.end();
                response.end('<p>FILE UPLOADED!');
                console.log('FILE UPLOADED\n');
            });
        });
    }

});

server.listen(8080, function(){
    console.log('Server is listening on 8080');
});

var delay = 100; //delay of 100 ms per chunk
var count =0;
var fakeNetworkLatency = function(callback){
    setTimeout(function() {
        callback();
    }, delay*count++);
};

まず、コードは実際に機能しています。ノードはチャンク化された応答を送信しますが、ブラウザーはそれを表示する前に、単にさらに待機しています。

詳細については ノードドキュメント

Response.write()が最初に呼び出されると、バッファリングされたヘッダー情報と最初の本文がクライアントに送信されます。 2回目のresponse.write()が呼び出されると、Nodeは、データをストリーミングすることを想定し、それを個別に送信します。つまり、応答は本文の最初のチャンクまでバッファリングされます。 。

Content-typeをresponse.setHeader('Content-Type', 'text/html; charset=UTF-8');のようにhtmlに設定すると、chromeコンテンツがレンダリングされますが、応答付きの一連のタイムアウト呼び出しを設定した場合にのみうまくいきました。 .write呼び出しを内部に入れます;コードを試してもまだdomが更新されなかったので、さらに深く掘り下げました...

問題は、コンテンツが適切であると判断したときにコンテンツをレンダリングするのは実際にはブラウザー次第であるため、代わりにステータスを確認するためにajaxリクエストを送信するコードを設定しました。

まず、サーバーを更新して、ステータスをグローバル変数に格納し、「checkstatus」エンドポイントを開いて読み取ります。

var http = require('http');
var fs = require('fs');
var status = 0;

var server = http.createServer(function (request, response) {
    response.writeHead(200);
    if (request.method === 'GET') {
        if (request.url === '/checkstatus') {
            response.end(status.toString());
            return;
        }
        fs.createReadStream('filechooser.html').pipe(response);
    }
    else if (request.method === 'POST') {
        status = 0;
        var outputFile = fs.createWriteStream('output');
        var total = request.headers['content-length'];
        var progress = 0;

        request.on('data', function (chunk) {
            progress += chunk.length;
            var perc = parseInt((progress / total) * 100);
            console.log('percent complete: ' + perc + '%\n');
            status = perc;
        });

        request.pipe(outputFile);

        request.on('end', function () {
            response.end('\nArchived File\n\n');
        });
    }

});

server.listen(8080, function () {
    console.log('Server is listening on 8080');
});

次に、filechooser.htmlを更新して、ajaxリクエストでステータスを確認しました。

<!DOCTYPE html>
<html>
<body>
<form id="uploadForm" enctype="multipart/form-data" action="/" method="post">
    <input type="file" id="upload" name="upload"/>
    <input type="submit" value="Submit">
</form>

Percent Complete: <span id="status">0</span>%

</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script>
    var $status = $('#status');
    /**
     * When the form is submitted, begin checking status periodically.
     * Note that this is NOT long-polling--that's when the server waits to respond until something changed. 
     * In a prod env, I recommend using a websockets library with a long-polling fall-back for older broswers--socket.io is a gentleman's choice)
     */
    $('form').on('submit', function() {
        var longPoll = setInterval(function () {
            $.get('/checkstatus').then(function (status) {
                $status.text(status);

                //when it's done, stop annoying the server
                if (parseInt(status) === 100) {
                    clearInterval(longPoll);
                }
            });
        }, 500);
    });
</script>
</html>

応答を終了しなくても、サーバーは着信ステータス要求を処理できることに注意してください。

それで、あなたの質問に答えるために、ダールはファイルをアップロードし、そのステータスをチェックするために長い間ポーリングした彼が見たflickrアプリに魅了されました。彼が魅了された理由は、サーバーがアップロードの作業を続けている間、サーバーがそれらのajaxリクエストを処理できたためです。それはマルチタスクでした。彼がちょうど14分後にそれについて話すのを見てください このビデオ -「それでそれがどのように機能するかです...」とさえ言います。数分後、彼はiframeテクニックについて言及し、ロングポーリングと単純なajaxリクエストを区別します。彼は、これらのタイプの動作に最適化されたサーバーを作成したいと述べています。

とにかく、これは当時は珍しいことでした。ほとんどのWebサーバーソフトウェアは、一度に1つの要求のみを処理します。また、データベースにアクセスしたり、Webサービスに呼び出されたり、ファイルシステムとやり取りしたりした場合、プロセスは待機中に他のリクエストを処理するのではなく、ただ座って終了するのを待ちます。

複数のリクエストを同時に処理したい場合は、別のスレッドを起動するか、ロードバランサーを使用してサーバーを追加する必要があります。

一方、Nodejsは、非ブロッキングIOを実行することにより、メインプロセスを非常に効率的に使用します。 Nodeはこれを最初に行ったわけではありませんが、ノンブロッキングの領域でそれを際立たせているのはIOレルムでは、すべてのデフォルトメソッドが非同期であり、 wrongを実行するには、「sync」メソッドを呼び出す必要があります。これにより、ユーザーはrightを実行する必要があります。 もの。

また、JavaScriptが選択された理由は、JavaScriptがすでにイベントループで実行されている言語であるためであることに注意してください。非同期コードを処理するのはmadeでした。匿名関数とクロージャを使用できるため、非同期アクションの保守がはるかに簡単になります。

また、promiseライブラリを使用すると、非同期コードの記述がはるかにクリーンになることにも言及したいと思います。たとえば、チェックアウト bluebirdjs -コールバックシグネチャ(function(error、params){})を持つオブジェクトのプロトタイプの関数を変換して代わりに返す素敵な「promisify」メソッドがあります約束する。

9
JohnnyFun