Я делаю веб-сайт, используя структуру Symfony, на сервере ovh. Когда пользователь меняет страницу, я создаю новое XMLHttpRequest
чтобы избежать перезагрузки всей страницы и улучшения пользовательского интерфейса.
Все работает хорошо, но я хочу добавить панель загрузки, а следующая страница загружается асинхронно. К сожалению параметр lengthComputable
был ложным. Чтобы пройти эту проблему, я установил заголовок на Symfony3 перед отправкой ответа с помощью строк тезисов:
$length = strlen($event->getResponse()->getContent()); $event->getResponse()->headers->set('Content-Length', $length); $event->getResponse()->headers->set('Accept-Ranges', "bytes"); $event->getResponse()->sendHeaders();
Этот трюк работает на моем локальном сервере разработки, lengthComputable
установлен в true, и я могу рассчитать текущий процент нагрузки. Но когда я помещаю все на свой сервер удаленных разработчиков, lengthComputable
снова является ложным.
Вот заголовок ответа, используя тезисы четырех строк:
Accept-Ranges:bytes Accept-Ranges:bytes Cache-Control:no-cache Cache-Control:no-cache Content-Encoding:gzip Content-Length:3100 Content-Length:3100 Content-Type:text/html; charset=UTF-8 Date:Sat, 28 Jan 2017 12:34:08 GMT Server:Apache Set-Cookie:PHPSESSID=***; path=/; HttpOnly Vary:Accept-Encoding
(И да, некоторые параметры присутствуют два раза)
Я думаю, что это связано с политикой заголовка перекрестного происхождения, но я не могу найти решение.
Я попытался установить другие параметры, такие как
$responseHeaders->set('Access-Control-Allow-Headers', 'content-type');
и даже
$responseHeaders->set('Access-Control-Allow-Origin', '*');
Я следил и пробовал тезисы ссылок
Symfony2. Я не могу установить заголовок Content-Length
Как я могу получить доступ к заголовку Content-Length из запроса Ajax для перекрестного домена?
CORS с заголовками php
РЕДАКТИРОВАТЬ
Я не использую прокси, и мой сервер размещен на OVH.
Вот мой код javascript (ничего исключительного)
ajax_oReq = new XMLHttpRequest(); ajax_oReq.addEventListener("progress", progress, false); ajax_oReq.addEventListener("load", complete, false); ajax_oReq.addEventListener("error", error, false); ajax_oReq.addEventListener("abort", error, false); params = "ajax=1"; if(url.indexOf('?')!=-1){ params = "&"+params; }else{ params = "?"+params; } ajax_oReq.open("GET", url+params, true); ajax_oReq.send();
В моей функции progress(evt)
я вижу, что lengthComputable
является ложным, регистрируя параметр evt
.
В моей complete(evt)
функции я evt.target.response
в свой соответствующий div
.
Я просто хочу добавить, что, когда я использую этот код, я получаю последнее событие прогресса примерно через 500 мс с загруженной длиной, равной общей длине страницы (так что 100% документа, но lengthComputable
– false, а total
равно 0, поэтому я знаю это только потому, что я смотрю свои заголовки, когда загрузка заканчивается). Но для complete
всей функции требуется еще 5 секунд. Я думаю, это потому, что длина контента неизвестна. Во всяком случае, когда я удаляю свои 4 строки кода (первые), этот вопрос исчезает, но у меня всегда нет lengthComputable=true
…
Спасибо за ваше время !
Поэтому проблема заключалась в том, что mod_deflate Apache сжимал ответ в GZIP, в результате длина была не вычислимой клиентской стороной (потому что Apache отбирает ответ от того, что я прочитал).
Быстрое и грязное решение – отключить сжатие для этого URL-адреса в настройках Apache. Если вы не можете или не хотите, чтобы он был оптимизирован, есть более сложное решение, взятое здесь
$event->getResponse()->headers->set('x-decompressed-content-length', $length);
то ваша функция прогресса должна выглядеть примерно так:
var progress = function (e) { var contentLength; if (e.lengthComputable) { contentLength = e.total; } else { contentLength = e.target.getResponseHeader('x-decompressed-content-length'); } var progress = (e.loaded / contentLength) * 100; };
Я не уверен, что он будет работать так хорошо, это зависит от того, будет ли e.loaded
основан на сжатом или декомпрессированном ответе, но есть другие возможные варианты для вас на этой странице
Вы также можете попробовать это, взятое отсюда , я позволю вам перевести его на Symfony, потому что я не уверен в том, как вы настраиваете свой контент, но в основном речь идет о том, чтобы вручную загружать данные, чтобы Apache этого не делал.
// checks if gzip is supported by client $pack = true; if(empty($_SERVER["HTTP_ACCEPT_ENCODING"]) || strpos($_SERVER["HTTP_ACCEPT_ENCODING"], 'gzip') === false) //replace $_SERVER part with SF method { $pack = false; } // if supported, gzips data if($pack) { $replyBody = gzencode($replyBody, 9, FORCE_GZIP); // Watch out 9 is strong compression // Set SF's Response content with gzipped content here header("Content-Encoding: gzip"); // replace that with appropriate SF method } else { // Set SF's Response content with uncompressed content here } // compressed or not, sets the Content-Length header("Content-Length: " . strlen($replyBody)); // replace that with appropriate SF methods
Так что добавьте первые два, if
в вашем контроллере, или где бы вы не установили контент Response и не сохранили свой приемник событий как есть.
Это два наименее «хакерских» решения, которые я мог найти. Кроме того, ваша догадка так же хороша, как и моя, я раньше этого не пробовал.