如何使用XHR onProgress函数使用压缩/ gzip压缩的内容?

我曾经见过一堆类似的问题,但是我还没有find一个完全描述我当前问题的问题,所以这里是:

我有一个页面,通过AJAX加载一个大(0.5到10 MB)的JSON文档,以便客户端代码可以处理它。 一旦文件加载,我没有任何问题,我不指望。 但是,下载需要很长时间,所以我尝试利用XHR Progress API来呈现进度条,以向用户指示文档正在加载。 这工作得很好。

然后,为了加快速度,我尝试通过gzip和deflate压缩服务器端的输出。 这也有很大的收获,但是,我的进度条停止了工作。

我查了一下问题,发现如果一个正确的Content-Length头没有被请求的AJAX资源发送, onProgress事件处理程序不能按预期工作,因为它不知道下载有多远它是。 发生这种情况时,名为lengthComputable的属性在事件对象上设置为false

这是有道理的,所以我试图明确地设置输出的未压缩和压缩的长度。 我可以validation头文件正在发送,我可以validation我的浏览器知道如何解压缩内容。 但是onProgress处理程序仍然报告lengthComputable = false

所以我的问题是: 有没有办法使用AJAX Progress API来压缩/缩小内容? 如果是的话,我现在做错了什么?


Chromenetworking面板中显示资源的方式如下:

网络面板

这些是相关的请求标头,显示请求是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 

这些是相关的响应标头,显示正确设置了Content-LengthContent-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处理。


根据亚当的build议 ,我试图将服务器端切换到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); }); 

在你的解决scheme稍微更加优雅的变化将是在你的HTTP响应中设置一个头文件,比如“x-decompressed-content-length”或者其他什么内容,以字节为单位的内容的完整解压值,并从onProgress中的xhr对象读出处理程序。

你的代码可能看起来像这样:

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

我无法解决在压缩内容本身上使用onProgress的问题,但我提出了这个半简单的解决方法。 简而言之 :在发送GET请求的同时向服务器发送一个HEAD请求,并在有足够的信息提供进度条后再进行此操作。


 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>', {}); 

在服务器端,在GETHEAD请求(应该表示未压缩的内容长度)上设置X-Content-Length标头,并放弃在HEAD请求上发送内容。

在PHP中,设置标题如下所示:

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

如果是HEAD请求,则中止发送内容:

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

以下是它的实际情况:

截图

HEAD在下面的屏幕截图中花了这么长时间的原因是因为服务器仍然需要parsing这个文件来知道它有多长,但这是我可以改进的地方,这绝对是一个改进。

不要因为没有本地解决scheme而陷入困境; 一行黑客可以解决你的问题,而不会搞乱Apache的configuration(在某些托pipe是禁止或非常受限):

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用户认为我在说这个解决scheme的有效性,所以这里是活的: http : //nyudvik.com/zip/ ,它是gzip-ed和真正的文件权重8 MB


相关链接:

  • SO:在Apache中启用gzip压缩时不会发送Content-Length?
  • Apache模块mod_deflate文档
  • PHP的filsize函数文档

尝试将您的服务器编码更改为gzip。

你的请求头显示了三种可能的编码(gzip,deflate,sdch),所以服务器可以select其中的任何一种。 通过响应头,我们可以看到您的服务器正在select以deflate响应。

Gzip是一种编码格式,除了附加的页眉和页脚(包括原始的未压缩长度)和一个不同的校验和algorithm之外,还包括一个deflate载荷:

Gzip维基百科

放气有一些问题。 由于遗留问题涉及不正确的解码algorithm,所以deflate的客户端实现必须通过愚蠢的检查才能找出他们正在处理的实现,不幸的是,他们通常仍然错误:

为什么使用deflate而不是gzip来处理Apache提供的文本文件?

在你提出问题的时候,浏览器可能会看到一个压缩文件从pipe道中传出来,然后甩了胳膊说:“当我甚至不知道如何解码这个东西,你怎么能期望我担心取得进展的权利,人?

如果你切换你的服务器configuration,所以响应是gzip(即,gzip显示为内容编码),我希望你的脚本按照你所期望/预期的那样工作。

我能想到的唯一的解决scheme是手动压缩数据(而不是把它留给服务器和浏览器),因为这使您可以使用正常的进度条,应该仍然给你相当大的压缩版本的收益。 例如,如果系统只需要在最新一代的Web浏览器中工作,你可以在服务器端压缩(无论你使用什么语言,我确定有一个zip函数或库),在客户端可以使用zip.js。 如果需要更多的浏览器支持,你可以检查一下这个SO回答是否有一些压缩和解压缩function(只要select你正在使用的服务器端语言支持)。 总的来说,这应该是相当简单的实现,虽然它会比原生压缩/解压缩更糟糕(尽pipe可能还是很好)。 (顺便说一句,在给了它更多的思考之后,理论上它可以比原生版本performance得更好,以防你select适合你使用的数据types的压缩algorithm,并且数据足够大)

另一个select是使用websocket,并将数据加载到分析/处理每个部分的同时加载的部分(不需要websocket,但是在彼此之后做10个http请求可能相当麻烦)。 这是否可能取决于具体的情况,但对我来说,听起来报告数据是可以加载的部分数据,并不需要首先完全下载。

我不清楚这个问题,应该不会发生,因为解压缩应该由浏览器完成。

你可能试图摆脱jQuery或黑客jQuery,因为$ .ajax似乎不能很好地处理二进制数据:

参考: http : //blog.vjeux.com/2011/javascript/jquery-binary-ajax.html

你可以尝试做你自己的ajax请求的实现请参阅: https : //developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest#Handling_binary_data

你可以尝试解压JavaScript的json内容(请参阅注释资源)。

*更新2 *

$ .ajax函数不支持进度事件处理程序,或者它不是jQuery文档的一部分(参见下面的注释)。

这里有一个方法来获得这个处理程序的工作,但我从来没有尝试过自己: http : //www.dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5/

*更新3 *

该解决scheme使用tierce第三方库来扩展(?)jQuery ajaxfunction,所以我的build议不适用