web-dev-qa-db-ja.com

XHR onProgress関数で収縮/ gzip圧縮されたコンテンツを使用するにはどうすればよいですか?

以前にこれと同様の質問がたくさん寄せられましたが、現在の問題を正確に説明するものはまだ見つかりませんでした。

クライアント側のコードで処理できるように、AJAXを介して大きな(0.5〜10 MB)JSONドキュメントを読み込むページがあります。ファイルがロードされると、予期しない問題は発生しません。ただし、ダウンロードには時間がかかるため、 XHR Progress API を使用して、ドキュメントがロードされていることをユーザーに示す進行状況バーをレンダリングしようとしました。これはうまくいきました。

次に、速度を上げるために、サーバー側で出力をgzipとdeflateで圧縮してみました。これも非常に効果的でしたが、進行状況バーが機能しなくなりました。

しばらく問題を調査しましたが、要求されたAJAXリソースとともに適切なContent-Lengthヘッダーが送信されない場合、onProgressイベントハンドラーは次のように機能できません。これは、ダウンロードの進行状況がわからないためです。これが発生すると、lengthComputableというプロパティがイベントオブジェクトのfalseに設定されます。

これは理にかなっているので、出力の圧縮されていない長さと圧縮された長さの両方でヘッダーを明示的に設定しようとしました。ヘッダーが送信されていることを確認でき、ブラウザーがコンテンツを解凍する方法を知っていることを確認できます。ただし、onProgressハンドラーは引き続きlengthComputable = falseを報告します。

したがって、私の質問は次のとおりです。 AJAX Progress APIを使用してコンテンツをgzip圧縮/デフレートする方法はありますか?その場合、今何を間違っていますか?


これは、リソースがChrome Networkパネルに表示され、圧縮が機能していることを示しています。

network panel

これらは関連する request ヘッダーで、リクエストがAJAXであり、Accept-Encodingが適切に設定されていることを示しています。

GET /dashboard/reports/ajax/load HTTP/1.1
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.99 Safari/537.22
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

関連する response ヘッダーは、Content-LengthおよびContent-Typeが正しく設定されていることを示しています。

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Encoding: deflate
Content-Type: application/json
Date: Tue, 26 Feb 2013 18:59:07 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
P3P: CP="CAO PSA OUR"
Pragma: no-cache
Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8g PHP/5.4.7
X-Powered-By: PHP/5.4.7
Content-Length: 223879
Connection: keep-alive

価値があるものとして、標準(http)接続とセキュア(https)接続の両方でこれを試してみました。違いはありません。コンテンツはブラウザーで正常に読み込まれますが、Progress APIによって処理されません。


Adamの提案 ごとに、サーバー側をgzipエンコードに切り替えてみましたが、成功も変更もありませんでした。関連する応答ヘッダーは次のとおりです。

HTTP/1.1 200 OK
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Content-Encoding: gzip
Content-Type: application/json
Date: Mon, 04 Mar 2013 22:33:19 GMT
Expires: Thu, 19 Nov 1981 08:52:00 GMT
P3P: CP="CAO PSA OUR"
Pragma: no-cache
Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8g PHP/5.4.7
X-Powered-By: PHP/5.4.7
Content-Length: 28250
Connection: keep-alive

繰り返しになりますが、コンテンツは適切にダウンロードおよびデコードされていますが、問題が発生しているのは進行状況APIだけです。


バートランドの要求 ごとに、要求は次のとおりです。

$.ajax({
    url: '<url snipped>',
    data: {},
    success: onDone,
    dataType: 'json',
    cache: true,
    progress: onProgress || function(){}
});

そして、私が使用しているonProgressイベントハンドラは次のとおりです(あまりにもクレイジーではありません)。

function(jqXHR, evt)
{
    // yes, I know this generates Infinity sometimes
    var pct = 100 * evt.position / evt.total;

    // just a method that updates some styles and javascript
    updateProgress(pct);
});
42
Jimmy Sawczuk

圧縮コンテンツ自体でonProgressを使用する問題を解決できませんでしたが、この半単純な回避策を思い付きました。 一言で言えばHEADリクエストと同時にサーバーにGETリクエストを送信し、十分な情報があればプログレスバーをレンダリングします。


function loader(onDone, onProgress, url, data)
{
    // onDone = event handler to run on successful download
    // onProgress = event handler to run during a download
    // url = url to load
    // data = extra parameters to be sent with the AJAX request
    var content_length = null;

    self.meta_xhr = $.ajax({
        url: url,
        data: data,
        dataType: 'json',
        type: 'HEAD',
        success: function(data, status, jqXHR)
        {
            content_length = jqXHR.getResponseHeader("X-Content-Length");
        }
    });

    self.xhr = $.ajax({
        url: url,
        data: data,
        success: onDone,
        dataType: 'json',
        progress: function(jqXHR, evt)
        {
            var pct = 0;
            if (evt.lengthComputable)
            {
                pct = 100 * evt.position / evt.total;
            }
            else if (self.content_length != null)
            {
                pct = 100 * evt.position / self.content_length;
            }

            onProgress(pct);
        }
    });
}

そしてそれを使用するには:

loader(function(response)
{
    console.log("Content loaded! do stuff now.");
},
function(pct)
{
    console.log("The content is " + pct + "% loaded.");
},
'<url here>', {});

サーバー側で、GETリクエストとHEADリクエストの両方にX-Content-Lengthヘッダーを設定します(これはuncompressedコンテンツの長さ)、およびHEAD要求でコンテンツの送信を中止します。

PHPでは、ヘッダーの設定は次のようになります。

header("X-Content-Length: ".strlen($payload));

そして、HEADリクエストの場合、コンテンツの送信を中止します。

if ($_SERVER['REQUEST_METHOD'] == "HEAD")
{
    exit;
}

実際の動作は次のとおりです。

screenshot

以下のスクリーンショットでHEADに時間がかかる理由は、サーバーがファイルの長さを把握するためにファイルを解析する必要があるためですが、それは間違いなく改善できるものであり、間違いなくそれはどこからでも改善されていますだった。

9
Jimmy Sawczuk

ソリューションのもう少しエレガントなバリエーションは、「x-decompressed-content-length」などのヘッダーを設定するか、HTTP応答にコンテンツの完全な解凍値をバイト単位で設定し、onProgressのxhrオブジェクトから読み取ることですハンドラ。

コードは次のようになります。

request.onProgress = function (e) {
  var contentLength;
  if (e.lengthComputable) {
    contentLength = e.total;
  } else {
    contentLength = parseInt(e.target.getResponseHeader('x-decompressed-content-length'), 10);
  }
  progressIndicator.update(e.loaded / contentLength);
};
13
Nat

ネイティブソリューションがないからといって動けなくなることはありません。 1行のハックは、Apacheの設定を混乱させることなく問題を解決できます(一部のホスティングでは禁止または非常に制限されています):

PHPによる救助:

var size = <?php echo filesize('file.json') ?>;

それだけです、あなたはおそらく残りをすでに知っていますが、ここでの参考としてだけです:

<script>
var progressBar = document.getElementById("p"),
    client = new XMLHttpRequest(),
    size = <?php echo filesize('file.json') ?>;

progressBar.max = size;

client.open("GET", "file.json")

function loadHandler () {
  var loaded = client.responseText.length;
  progressBar.value = loaded;
}

client.onprogress = loadHandler;

client.onloadend = function(pe) {
  loadHandler();
  console.log("Success, loaded: " + client.responseText.length + " of " + size)
}
client.send()
</script>

ライブの例:

別のSOユーザーは私がこのソリューションの有効性について嘘をついていると考えているので、ここではライブです: http://nyudvik.com/Zip/ 、gzip- edおよび実際のファイルの重みは8 MB



関連リンク:

4

進捗を推定し、lengthComputableを常にtrueに設定するライブラリを作成しました。

Chrome 64にはまだこの問題があります( バグ を参照)

この問題を修正するページに含めることができるjavascriptシムであり、標準のnew XMLHTTPRequest()を通常使用できます。

Javascriptライブラリは次の場所にあります。

https://github.com/AirConsole/xmlhttprequest-length-computable

サーバーのエンコードをgzipに変更してみてください。

リクエストヘッダーには3つの潜在的なエンコード(gzip、deflate、sdch)が表示されるため、サーバーはこれら3つのエンコードのいずれかを選択できます。応答ヘッダーにより、サーバーがdeflateで応答することを選択していることがわかります。

Gzipは、追加のヘッダーとフッター(元の非圧縮長を含む)および異なるチェックサムアルゴリズムに加えて、deflateペイロードを含むエンコード形式です。

ウィキペディアのGzip

Deflateにはいくつかの問題があります。不適切なデコードアルゴリズムを扱うレガシーの問題により、deflateのクライアント実装は、処理している実装を把握するために愚かなチェックを実行する必要がありますが、残念ながら、依然として間違っていることがよくあります。

Apacheが提供するテキストファイルにgzipの代わりにdeflateを使用する理由

あなたの質問の場合、ブラウザはおそらくパイプを抜ける収縮ファイルを見て、腕を上げて、「私がこのことをどのようにデコードするのか正確にわからないとき、どうすれば期待できますか?進歩を正しくすることを心配する必要がありますか?」

応答がgzip圧縮されるようにサーバー構成を切り替える(つまり、gzipがコンテンツエンコードとして表示される)場合、スクリプトが期待どおりに機能することを期待しています。

2
AdamJonR

私が考えることができる唯一の解決策は、データを手動で圧縮することです(サーバーとブラウザに残すのではなく)、通常のプログレスバーを使用でき、非圧縮バージョンよりもかなりの利益を得ることができます。たとえば、システムが最新世代のWebブラウザーでのみ動作する必要がある場合は、たとえばサーバー側(使用する言語にかかわらず、Zip関数またはライブラリがあることを確認してください)とクライアント側で使用できます Zip.js 。より多くのブラウザのサポートが必要な場合は、多くの圧縮および解凍機能について this SO answer を確認できます(サーバー側の言語でサポートされているものを選択してください '全体的には、これは実装がかなり簡単であるはずですが、ネイティブの圧縮/解凍よりもパフォーマンスは低下しますが(おそらく優れていると思います)(ところで、理論的にはネイティブよりもパフォーマンスがさらに向上する可能性があると考えた後)使用しているデータのタイプに適合する圧縮アルゴリズムを選択し、データが十分に大きい場合のバージョン

別のオプションは、websocketを使用して、ロードされると同時にすべてのパーツを解析/処理する部分にデータをロードすることです(そのためにwebsocketは必要ありませんが、お互いに10回のhttpリクエストを行うのは非常に面倒です)。これが可能かどうかは特定のシナリオによって異なりますが、レポートデータは、パーツにロードできるデータの一種であり、最初に完全にダウンロードする必要はないようです。

0
David Mulder