Как использовать дефлированный /gzipped контент с помощью функции XHR onProgress?
Я видел кучу подобных вопросов, о которых вы спрашивали раньше, но я не нашел тот, который точно описывает мою текущую проблему, поэтому:
У меня есть страница, которая загружает большой (от 0,5 до 10 МБ) JSON-документ через AJAX, чтобы клиентский код мог его обработать. Как только файл загружен, у меня нет никаких проблем, которые я не ожидаю. Однако загрузка занимает много времени, поэтому я попытался использовать XHR Progress API, чтобы отобразить индикатор выполнения, чтобы указать пользователю, что Документ загружается. Это хорошо работает.
Затем, чтобы ускорить работу, я попытался сжать вывод на серверной стороне через gzip и сфотографировать. Это тоже сработало, но с огромным успехом мой индикатор прогресса перестает работать.
Я некоторое время изучал эту проблему и обнаружил, что если надлежащий заголовок Content-Length
не отправляется с запрошенным ресурсом AJAX, обработчик события onProgress
не может функционировать должным образом, поскольку он не знает, как далеко в процессе загрузки. Когда это произойдет, свойство lengthComputable
устанавливается на false
объекта события.
Это имело смысл, поэтому я попытался настроить заголовок явно как с несжатой, так и с сжатой длиной вывода. Я могу проверить, что отправляется заголовок, и я могу проверить, что мой браузер знает, как распаковывать содержимое. Но обработчик onProgress
все еще сообщает lengthComputable = false
.
Итак, мой вопрос: есть способ gzipped/deflated content с API AJAX Progress API? И если да, то что я делаю неправильно прямо сейчас?
Так отображается ресурс на панели "Сеть Chrome", показывая, что сжатие работает:
![network panel]()
Это соответствующие заголовки запрос, показывающие, что запрос 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) соединении без каких-либо различий: контент загружается в браузере, но не обрабатывается API-интерфейсом Progress.
Per предложение 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 прогресса, с которым у меня возникают проблемы.
Per запрос Бертран, здесь запрос:
$.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);
});
Ответы
Ответ 1
Мне не удалось решить проблему использования 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;
}
Вот как это выглядит в действии:
![screenshot]()
Причина, по которой HEAD
занимает так много времени в приведенном ниже скриншоте, заключается в том, что сервер все еще должен разбирать файл, чтобы знать, как долго это происходит, но что-то, что я могу определенно улучшить, и это определенно улучшение от того, где это был.
Ответ 2
Несколько более элегантным вариантом для вашего решения было бы установить заголовок, такой как "x-decpressed-content-length" или что-то еще в вашем ответе HTTP с полным распакованным значением содержимого в байтах и прочитать его с объекта xhr в обработчике onProgress.
Ваш код может выглядеть примерно так:
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);
};
Ответ 3
Не застревайте только потому, что нет собственного решения; взлома одной строки может решить вашу проблему, не испортив конфигурацию 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 МБ
Ссылки по теме:
Ответ 4
Попробуйте изменить кодировку сервера на gzip.
В заголовке запроса отображаются три возможных кодировки (gzip, deflate, sdch), поэтому сервер может выбрать любой из этих трех. По заголовку ответа мы видим, что ваш сервер выбирает ответ с дефлятом.
Gzip - это формат кодирования, который включает в себя полезную нагрузку дефляции в дополнение к дополнительным верхним и нижним колонтитулам (включая исходную несжатую длину) и другой алгоритм контрольной суммы:
Gzip в Википедии
У Deflate есть некоторые проблемы. Из-за устаревших проблем, связанных с неправильными алгоритмами декодирования, клиентские реализации deflate должны проходить через глупые проверки, чтобы выяснить, с какой реализацией они работают, и, к сожалению, они часто все еще ошибаются:
Зачем использовать deflate вместо gzip для текстовых файлов, обслуживаемых Apache?
В случае вашего вопроса браузер, вероятно, видит файл дефляции, спускающийся по трубе, и просто подбрасывает руки и говорит: "Когда я даже не знаю точно, как я закончу расшифровку этой вещи, как можете ли вы ожидать, что я буду беспокоиться о том, чтобы добиться успеха, человек?"
Если вы переключите конфигурацию своего сервера, чтобы ответ был gzipped (т.е. gzip отображается как кодировка содержимого), я надеюсь, что ваш script работает так, как вы надеялись/ожидали.
Ответ 5
Единственное решение, о котором я могу думать, - это сжатие данных вручную (вместо того, чтобы оставлять его на сервере и в браузере), поскольку это позволяет вам использовать нормальный индикатор выполнения и все равно приносить вам значительный выигрыш по несжатой версии. Если, например, система требуется только для работы в веб-браузерах последнего поколения, вы можете, например, закрепить ее на стороне сервера (любой язык, который вы используете, я уверен, что есть zip-функция или библиотека), а на стороне клиента вы можете использовать zip.js. Если требуется дополнительная поддержка браузера, вы можете проверить этот ответ SO для ряда функций сжатия и декомпрессии (просто выберите тот, который поддерживается на используемом вами языке на стороне сервера). В целом это должно быть достаточно простым в реализации, хотя оно будет работать хуже (хотя все еще хорошо), чем собственное сжатие/декомпрессия. (Кстати, после того, как он немного подумал, что теоретически может работать даже лучше, чем родная версия, если вы выберете алгоритм сжатия, который будет соответствовать типу данных, которые вы используете, и данные достаточно большие)
Другим вариантом будет использование веб-раскладки и загрузка данных в тех частях, где вы будете разбирать/обрабатывать каждую часть в то же время, когда она загружается (для этого вам не нужны веб-сайты, но выполнение 10 http-запросов после каждого из них может быть довольно хлопот). Возможно ли это, зависит от конкретного сценария, но для меня это звучит так, как данные отчета - это вид данных, которые могут быть загружены по частям и не требуются для полной загрузки.
Ответ 6
Я не понимаю эту проблему, это не должно происходить, так как декомпрессия должна выполняться браузером.
Вы можете попытаться отойти от 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
Вы можете попытаться распаковать json содержимое javascript (см. ресурсы в комментариях).
* ОБНОВЛЕНИЕ 2 *
функция $.ajax не поддерживает обработчик события прогресса или не является частью документации jQuery (см. комментарий ниже).
вот способ получить этот обработчик, но я никогда не пробовал его сам:
http://www.dave-bond.com/blog/2010/01/JQuery-ajax-progress-HMTL5/
* ОБНОВЛЕНИЕ 3 *
Решение использует tierce стороннюю библиотеку для расширения (?) jQuery ajax functionnality, поэтому мое предложение не применяется