如何在XHR onProgress函数中使用缩减/ gzipped内容?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何在XHR onProgress函数中使用缩减/ gzipped内容?相关的知识,希望对你有一定的参考价值。
我之前看到过一些类似的问题,但我还没有找到一个完全描述我当前问题的问题,所以这里有:
我有一个页面,通过AJAX加载一个大的(介于0.5和10 MB之间)JSON文档,以便客户端代码可以处理它。加载文件后,我没有任何我不期望的问题。但是,下载需要很长时间,因此我尝试利用XHR Progress API呈现进度条以向用户指示文档正在加载。这很好用。
然后,为了加快速度,我尝试通过gzip和deflate压缩服务器端的输出。这也很有效,但是,我的进度条停止了工作。
我已经研究了一段时间的问题并发现如果没有使用请求的AJAX资源发送正确的Content-Length
头,则onProgress
事件处理程序无法按预期运行,因为它不知道下载的距离是多少。发生这种情况时,在事件对象上将名为lengthComputable
的属性设置为false
。
这是有道理的,所以我尝试使用未压缩和压缩的输出长度显式设置标头。我可以验证是否正在发送标头,我可以验证我的浏览器知道如何解压缩内容。但onProgress
处理程序仍然报告lengthComputable = false
。
所以我的问题是:有没有办法使用AJAX Progress API来压缩/缩小内容?如果是这样,我现在做错了什么?
这是资源在Chrome网络面板中的显示方式,表明压缩正在运行:
这些是相关的请求标头,显示请求是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-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's suggestion,我尝试将服务器端切换到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。
根据Bertrand's request,这是请求:
$.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);
});
您的解决方案稍微更优雅的变化是设置一个标题,如'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);
};
我无法解决在压缩内容本身使用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>', {});
在服务器端,在X-Content-Length
和GET
请求(应该代表未压缩的内容长度)上设置HEAD
标头,并中止在HEAD
请求上发送内容。
在PHP中,设置标题如下:
header("X-Content-Length: ".strlen($payload));
如果是HEAD
请求,则中止发送内容:
if ($_SERVER['REQUEST_METHOD'] == "HEAD")
{
exit;
}
这是它在行动中的样子:
HEAD
在下面的屏幕截图中花了这么长时间的原因是因为服务器仍然需要解析文件才能知道它有多长,但这是我可以肯定改进的东西,它绝对是一个改进的地方。
不要因为没有原生解决方案而陷入困境;一行黑客可以解决您的问题而不会搞乱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
相关链接:
- SO: Content-Length not sent when gzip compression enabled in Apache?
- Apache Module mod_deflate doc
- PHP filsize function doc
尝试将服务器编码更改为gzip。
您的请求标头显示三种可能的编码(gzip,deflate,sdch),因此服务器可以选择这三种中的任何一种。通过响应标头,我们可以看到您的服务器选择使用deflate进行响应。
Gzip是一种编码格式,除了包含额外的页眉和页脚(包括原始未压缩长度)和不同的校验和算法外,还包括deflate有效负载:
Deflate有一些问题。由于处理不正确的解码算法的遗留问题,deflate的客户端实现必须通过愚蠢的检查才能确定他们正在处理哪些实现,不幸的是,他们经常仍然弄错:
Why use deflate instead of gzip for text files served by Apache?
在你的问题的情况下,浏览器可能会看到一个deflate文件从管道下来,只是扔了它的胳膊说:“当我甚至不知道我将如何最终解码这个东西,你怎么能期望我担心让人的进步正确吗?“
如果您切换服务器配置以便响应被gzip压缩(即,gzip显示为内容编码),我希望您的脚本能够按照您希望/期望的方式工作。
我们创建了一个估算进度的库,并始终将lengthComputable
设置为true。
Chrome 64仍有此问题(请参阅Bug)
这是一个javascript shim,你可以在你的页面中包含修复这个问题,你可以正常使用标准的new XMLHTTPRequest()
。
可以在这里找到javascript库:
https://github.com/AirConsole/xmlhttprequest-length-computable
我能想到的唯一解决方案是手动压缩数据(而不是将其留给服务器和浏览器),因为这允许您使用正常的进度条,并且仍然可以比未压缩的版本获得相当大的收益。例如,如果系统只需要在最新一代Web浏览器中工作,您可以在服务器端拉链(无论您使用何种语言,我确信有拉链功能或库),在客户端,您可以使用zip.js。如果需要更多浏览器支持,您可以检查this SO answer以获取许多压缩和解压缩功能(只需选择您正在使用的服务器端语言支持的功能)。总的来说,这应该是相当简单的实现,虽然它会比本机压缩/解压缩表现更差(尽管可能仍然很好)。 (顺便说一下,在给它多一点之后认为理论上它可以比原生版本更好,以防你选择适合你正在使用的数据类型并且数据足够大的压缩算法)
另一个选择是使用websocket并将数据加载到您在加载的同时解析/处理每个部分的部分(您不需要websockets,但是在每个部分之后执行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函数不支持progress事件处理程序,或者它不是jQuery文档的一部分(请参阅下面的注释)。
这是一种让这个处理程序工作的方法,但我自己从未尝试过:http://www.dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5/
*更新3 *
该解决方案使用tierce第三方库扩展(?)jQuery ajax功能,我的建议不适用
以上是关于如何在XHR onProgress函数中使用缩减/ gzipped内容?的主要内容,如果未能解决你的问题,请参考以下文章
给 layui upload 带每个文件的进度条, .net 后台代码