Pull to refresh

кроссдоменный UPLOAD файлов с прогресс баром, без перезагрузки страницы, и без использования флеш

Reading time 5 min
Views 2.3K
Драсте всем.
Спасибо что решили почитать этот пост.

Задача состояла в следующем, в связи с тем что широко известный загрузчик файлов FancyUploader, не всегда, но довольно часто глючит, особенно если использовать прокси, нужно было разработать систему, по которой при загрузке файла показывался прогресс бар… Всё бы ничего, таких систем много, но все либо используют другие флеш загрузчики или иные Апи, что в данном случии являеться негативным результатом разработки.

После некоторого времени поиска была найдена система основанная на модуле apc habrahabr.ru/blogs/webdev/17620. Всё бы хорошо, но вешать дополнительный модуль кеша на сервер где кеш технологии уже используеться было безсмысленно. Тогда мне в руки попалась информация о NGINX upload progress module (http://wiki.nginx.org/NginxHttpUploadProgressModule) и это действительно работает… Тем более что на наших серверах установлен именно nginx в качестве fronе-end сервера.

Но самое интересное оказалось впереди.

Обычно загрузка файлов осуществяеться в то место где и лежит сам сайт (на тот же домен), но у нашей компании это не так. Все картинки и видео лежат на одельном сервере, и соответсвенно хорошо если upload был бы сразу на него, что бы не загружать канал сайта.

После конфигурирования nginx с upload progress module и использование примера с wiki.nginx.org/NginxHttpUploadProgressModule прогресс бар загрузки файла заработал.

Для пересылки самого файла на нужный сервер было изменено не много, и сам же сконфигурированных nginx естественно должен находиться на h_t_t_p://STORAGE-SERVER.com.

<code>
function openProgressBar() {
/* generate random progress-id */
uuid = "";
for (i = 0; i < 32; i++) {
uuid += Math.floor(Math.random() * 16).toString(16);
}
/* patch the form-action tag to include the progress-id */
document.getElementById(«upload»).action="h_t_t_p://STORAGE-SERVER.com/upload.php?X-Progress-ID=" + uuid;

/* call the progress-updater every 1000ms */
interval = window.setInterval( function () { fetch(uuid); }, 1000 );
}

Для обратной связи и считывания переданого объема используеться запрос
req = new XMLHttpRequest();
req.open("GET", "/progress", 1);

и проблема номер 1, запрос должен быть на h_t_t_p://STORAGE-SERVER.com, а не на сервер на котором находиться сам сайт, а как известно кросс-доменные ajax запросы запрещены политикой безопастности. Решение оказалось довольно простым и заключалось в перенаправленни запроса с текущего домена на нужный при поможи нескольких строк в конфиг файле nginx который расположен на отправляющей стороне.
location ^~ /progress {
# proxy to upstream server
proxy_pass h_t_t_p://STORAGE-SERVER.com;
proxy_redirect default;
}

Ура… Всё получилось… Дело осталось за малым, получить инфо о том куда был реально записан файл на h_t_t_p://STORAGE-SERVER.com, с каким именем. Т.е. обработка h_t_t_p://STORAGE-SERVER.com/upload.php должна отправлять результат записи, например в таком формате {'filepath':'/user_upload/photo/2/232/23456/23456.jpg', 'filetype':'image', 'error':'0'}. Формат даных, которые возвращаются после обработки может быть конечно другим, любой заданый вами. У меня такой как указал.

Результат после выполнения в исходном коде будет выглядеть
<iframe id="uploadframe" name="uploadframe" width="0" height="0" frameborder="0" border="0" src="about:blank">
<html>
<head>
</head>
<body>
{'filepath':'/user_upload/photo/2/232/23456/23456.jpg', 'filetype':'image', 'error':'0'}
</body>
</html>
</iframe>

Т.е. после передачи последнего байта выполняеться action формы в указанный фрейм.

И как оказалось извлечь его от туда обычными способами невозможно, ведь это было бы большой дыркой в безопасности. Получить данные можно только с фрейма который нахордиться в одном доменном пространстве с самим документом иначе Permission denied to mysite.com to get some property from otherdomain.com.

Методом научного и ненаучного тыка были перепробованны масы способов обойти это, но единственным и как выяснилось очень простым оказалось использование якорей в ссылке сайта. Но для кросбраузерности нужно использовать не parent.location.hash, а просто parent.location (иначе работать будеть только в FireFox.)

Для этого файле обработчике не просто формируеться строка с результатом, а формируеться скрипт после которого получаем
<iframe id="uploadframe" name="uploadframe" width="0" height="0" frameborder="0" border="0" src="about:blank">
<html>
<head>

<script>
parent.location="h_t_t_p://mysite.com/uploadfile/index.php#{'filepath':'/user_upload/photo/2/232/23456/23456.jpg', 'filetype':'image', 'error':'0'}";
</script>

</head>
<body>
</body>
</html>
</iframe>

После чего осталось считать якорь и обработать его
function fetch(uuid) {
req = new XMLHttpRequest();
req.open("GET", "/progress", 1);
req.setRequestHeader("X-Progress-ID", uuid);
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) {
/* poor-man JSON parser */
var upload = eval(req.responseText);

document.getElementById('tp').innerHTML = upload.state;

/* change the width if the inner progress-bar */
if (upload.state == 'uploading') {
bar = document.getElementById('progressbar');
w = 400 * upload.received / upload.size;
bar.style.width = w + 'px';
}else if (upload.state == 'done') {
//лучше перенести эту стоку сюда, что бы в случии ошибки дальше, запросы прогресса
//не выполнялись
window.clearTimeout(interval);

bar = document.getElementById('progressbar');
bar.style.width = '401px';

//**************ОБРАБОТКА РЕЗУЛЬТАТА *********************************
var result = window.location.hash;
location.hash='progress'; //убираем данные со строки браузера
result = result.slice(1); //удаляем символ '#'
//дальше обрабатываем строку результата как нам нравиться и при помощи
//javascript сообщаем пользователю о результате загузки (показываем предпросмотр загруженного файла)
//**********************************************************************
}
/* we are done, stop the interval */
if (upload.state == 'done' || upload.state == 'error') {
window.clearTimeout(interval);
}
}
}
}
req.send(null);
}

ну и конечно сама форма для выбора файла загрузки

<h2>Загрузка файлов</h2><br/>
<form id="upload" enctype="multipart/form-data"
action="/upload.php" target="uploadframe" method="post"
onsubmit="openProgressBar(); return true;">
<input type="hidden" name="MAX_FILE_SIZE" value="300000000" />
<input name="Filedata" type="file" label="fileupload" />
<input type="submit" value="Загрузить" />
</form>
<iframe id="uploadframe" name="uploadframe" width="0" height="0" frameborder="0" border="0" src="about:blank"></iframe>

<div id="progress" style="width: 400px; border: 1px solid black; height:10px; display: none;">
<div id="progressbar"
style="width: 0px; background-color: black; margin-left:-1px; border: 0px solid black; height:10px;">
</div>
</div>

<div id="tp">(progress)</div>


Вот и всё господа… В конечном итоге получилась хорошая и достаточно униварсальная ситема (особонне если обработчику пересылать текущий url) для загрузки файлов напрямую на storage сервер с обображением статуса загрузки на сайте (которой находиться не на storage сервере).

Преимущества такой системы — НИКАКОГО ФЛЕША или других аплетов, универсальность решения, и самое главное кросс-доменная загрузка файла без нагрузки на сайт (загрузка на storage напрямую)

Недостакоки — не работает в IE6, нету возможности одновременной загрузки нескольких фалов (как в FancyUploader3)

Спасибо за внимание.
Tags:
Hubs:
+9
Comments 9
Comments Comments 9

Articles