С появлением online, offline событий многие разработчики, особенно мобильных веб-севисов возложили на них большие надежды. Казалось бы online, offline говорят нам когда у пользователя есть доступ к интернету, но на самом деле это далеко не так. Подробности их поведения когда-то давно описал Резиг в своем блоге.
Кратко — online, offline сигнализирует нам, что пользователь вручную переключился в оффлайн либо у него нет ни одного соединения с сетью. Фактически эти 2 события бесполезны в том виде в котором они представлены — я не знаю кто будет вручную переключать таб в режим онлайн/оффлайн, и с сетевыми подключениями тоже все плохо. Ну и, конечно, доисторические бразуеры не знают эти события.
Под катом элегантное и 100% кросбраузерное решение, позволяющее получить настоящие online, offline события.
Нам нужно получить события, проверяющие без лишних затрат наличие интерента у пользователя, точнее соединение до нашего веб-сервиса.
Алгоритм (я назвал его LIP — Long Ping Image):
1. Создаем картинку через new Image
2. Вешаем на неё onload onerror
3. Прописываем путь до long polling ресурса нашего ping сервера
4. Браузер устанавливает висящее соединение с сервером, если соединение было по каким-либо причинам сброшено — упал инет, http 50x, то сработает событие onerror. Тут мы создаем ещё одну картинку, на сей раз «быструю», чтобы удостовериться на 100% в том, что сервис оффлайн. Если у этой картинки сработал onerror значит сервис точно оффлайн. Через определенный интервал пытаемся поднять картиночное ping соединение.
Решение абсолютно кроссбраузерно и кроссдоменно. Реагирование на offline 2-4 секунды, реагирование на online 0-15 секунд.
Проблемы которые были замечены:
1. Опера как новогодняя елка всем чем можно предательски сигнализирует пользователю, что «Загрузка...» во время длинного картиночного соединения — это починить нереально (пробовал iframe, css url, sse) и вечная «Загрузка...» напрягает. Для Оперы длинное соединение не устанавливается, а просто через определенный интервал опрашивается «быстрая» картинка — не так оперативно, немного затратно, но ничего не поделать.
2. Пользователи ФФ могут убить длинное соединение, нажав Esc при загрузке страницы — было пофиксено preventDefault'ом.
3. При физическом отключении интернета (выдернул шнур) все браузеры не сбрасывают висящее соединение, поэтому оффлайн не приходит.
Достоинства:
1. Покрытие всех браузеров, даже самых древних
2. Быстрое реагирование на offline/online
3. Можно подкрутить nginx для выполнения функции ping-сервера и получить мизерные издержки на стороне сервера.
Недостатки:
1. Небольшая утечка траффика около 1Кб в минуту (с возможностью сократить издержки до 1кб за сеанс)
2. Необходимость поднятия Пинг сервера
3. Большое количество висящих соединений
4. Если пинг сервер находится на том же домене, что и основной сервер, то мы занимаем 1 из 4 возможных http соединений.
Это все можно пролистать, архив с исходниками и пример ниже.
ping.php — наш ping-сервер
Есть ещё вариант через nginx HttpEmptyGifModule
Как реализовать длинный запрос на nginx я без понятия, поэтому приложил вариант на ПХП.
Ping.js
Пример использования
Живой пример: azproduction.ru/lpip (Прошу долго не проверять)
Исходник: narod.ru/disk/6061703001/Ping.rar.html
Критика, предложения приветствуются. Буду рад если кто-нибудь напишет вариант ping-сервера под nginx.
Кратко — online, offline сигнализирует нам, что пользователь вручную переключился в оффлайн либо у него нет ни одного соединения с сетью. Фактически эти 2 события бесполезны в том виде в котором они представлены — я не знаю кто будет вручную переключать таб в режим онлайн/оффлайн, и с сетевыми подключениями тоже все плохо. Ну и, конечно, доисторические бразуеры не знают эти события.
Под катом элегантное и 100% кросбраузерное решение, позволяющее получить настоящие online, offline события.
Нам нужно получить события, проверяющие без лишних затрат наличие интерента у пользователя, точнее соединение до нашего веб-сервиса.
Алгоритм (я назвал его LIP — Long Ping Image):
1. Создаем картинку через new Image
2. Вешаем на неё onload onerror
3. Прописываем путь до long polling ресурса нашего ping сервера
4. Браузер устанавливает висящее соединение с сервером, если соединение было по каким-либо причинам сброшено — упал инет, http 50x, то сработает событие onerror. Тут мы создаем ещё одну картинку, на сей раз «быструю», чтобы удостовериться на 100% в том, что сервис оффлайн. Если у этой картинки сработал onerror значит сервис точно оффлайн. Через определенный интервал пытаемся поднять картиночное ping соединение.
Решение абсолютно кроссбраузерно и кроссдоменно. Реагирование на offline 2-4 секунды, реагирование на online 0-15 секунд.
Проблемы которые были замечены:
1. Опера как новогодняя елка всем чем можно предательски сигнализирует пользователю, что «Загрузка...» во время длинного картиночного соединения — это починить нереально (пробовал iframe, css url, sse) и вечная «Загрузка...» напрягает. Для Оперы длинное соединение не устанавливается, а просто через определенный интервал опрашивается «быстрая» картинка — не так оперативно, немного затратно, но ничего не поделать.
2. Пользователи ФФ могут убить длинное соединение, нажав Esc при загрузке страницы — было пофиксено preventDefault'ом.
3. При физическом отключении интернета (выдернул шнур) все браузеры не сбрасывают висящее соединение, поэтому оффлайн не приходит.
Достоинства:
1. Покрытие всех браузеров, даже самых древних
2. Быстрое реагирование на offline/online
3. Можно подкрутить nginx для выполнения функции ping-сервера и получить мизерные издержки на стороне сервера.
Недостатки:
1. Небольшая утечка траффика около 1Кб в минуту (с возможностью сократить издержки до 1кб за сеанс)
2. Необходимость поднятия Пинг сервера
3. Большое количество висящих соединений
4. Если пинг сервер находится на том же домене, что и основной сервер, то мы занимаем 1 из 4 возможных http соединений.
Код
Это все можно пролистать, архив с исходниками и пример ниже.
ping.php — наш ping-сервер
<?php
isset($_GET['long']) && sleep(55);
header("Content-type: image/gif");
header("Expires: Wed, 11 Nov 1998 11:11:11 GMT");
header("Cache-Control: no-cache");
header("Cache-Control: must-revalidate");
// 1x1 gif
printf("%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" .
"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c",
71,73,70,56,57,97,1,0,1,0,128,255,0,192,192,192,0,0,0,33,
249,4,1,0,0,0,0,44,0,0,0,0,1,0,1,0,0,2,2,68,1,0,59);
?>
Есть ещё вариант через nginx HttpEmptyGifModule
location = /_.gif {
empty_gif;
}
Как реализовать длинный запрос на nginx я без понятия, поэтому приложил вариант на ПХП.
Ping.js
/**
* @fileOveriew Long Polling Image Ping
*/
(function (window, Image) {
/**
* Long Polling Image Ping
*
* Way to detect user's inernet connection
*/
var Lpip = {
BASE_URL: 'ping.php',
/**
* Long polling request URL. Can be crossdomain
*/
LONG_POLLING_IMAGE_URL: '?long',
/**
* Common image request. Can be crossdomain
*/
IMAGE_URL: '',
RECONNECT_TIMEOUT: 15000,
_image: null,
_stage: 2,
_makeImage: function (url) {
var image = new Image();
image.onload = Lpip.onImageLoad;
image.onerror = Lpip.onImageError;
image.src = url + (url.match(/\?/) ? '&' : '?') + Math.random();
return Lpip._image = image;
},
/**
* @type Boolean
*/
online: false,
/**
* @type Boolean
*/
offline: false,
/**
* Long polling image request
*/
watch: function () {
// Opera fix
if (window.opera) {
window.setTimeout(function () {
Lpip._makeImage(Lpip.BASE_URL + Lpip.IMAGE_URL);
}, Lpip.RECONNECT_TIMEOUT);
return;
}
// For other browsers
Lpip._makeImage(Lpip.BASE_URL + Lpip.LONG_POLLING_IMAGE_URL);
},
/**
* Quick image request
*/
quick: function () {
Lpip._makeImage(Lpip.BASE_URL + Lpip.IMAGE_URL);
},
onImageLoad: function () {
if (!this.width) {
// Error
Lpip.onImageError.call(this);
} else {
// Image ok
if (Lpip._stage > 1) {
// Internet connection up!
Lpip.onConnectionUp();
Lpip.offline = !(Lpip.online = true);
}
// Reset errors
Lpip._stage = 0;
// Continue requesting
Lpip.watch();
}
},
onImageError: function () {
Lpip._stage += 1;
if (Lpip._stage > 1) {
if (Lpip._stage === 2) {
// User's internet connection down...
Lpip.onConnectionDown();
Lpip.offline = !(Lpip.online = false);
}
// Try reconnect
window.setTimeout(Lpip.quick, Lpip.RECONNECT_TIMEOUT);
} else {
// Maybe long polling request aborts for some resons
// Try to get "quick image"
Lpip.quick();
}
},
onConnectionUp: function () {
window.console && window.console.log('onConnectionUp');
},
onConnectionDown: function () {
window.console && window.console.log('onConnectionDown');
}
};
// Exporting Lpip
window.Ping = {
/**
* Inits Ping
*
* @param {Object} [options]
* @param {Number} [options.reconnectTimeout=15000]
* @param {String} [options.baseUrl='ping.php']
*/
init: function (options) {
Lpip.RECONNECT_TIMEOUT = options.reconnectTimeout || Lpip.RECONNECT_TIMEOUT;
Lpip.BASE_URL = options.baseUrl || Lpip.BASE_URL;
// User can cancel long polling request by pressing Esc button in Firefox or Opera
if (window.addEventListener) {
window.document.addEventListener('keypress', function (event) {
if (event.keyCode === 27) {
event.preventDefault();
}
}, false);
}
Lpip.quick();
},
/**
* Connection up event helper
*
* Supports only one listener
*
* @param {Function} callback
*/
connectionUp: function (callback) {
Lpip.onConnectionUp = callback;
},
/**
* Connection up event helper
*
* Supports only one listener
*
* @param {Function} callback
*/
connectionDown: function (callback) {
Lpip.onConnectionDown = callback;
},
/**
* @returns {Boolean}
*/
isOnline: function () {
return Lpip.online;
},
/**
* @returns {Boolean}
*/
isOffline: function () {
return Lpip.offline;
}
};
}(this, this.Image));
Пример использования
<body>
<button onclick="checkConnectionStatus()">Connection Status</button>
<pre id="log"></pre>
<script type="text/javascript" src="Ping.js"></script>
<script type="text/javascript">
(function (window, Ping, log) {
log.innerHTML += 'Lpip is watching...<br/>';
Ping.connectionUp(function () {
log.innerHTML += 'Connection Up<br/>';
});
Ping.connectionDown(function () {
log.innerHTML += 'Connection Down<br/>';
});
// call on window.onload to prevent "loading... status"
window.onload = function () {
Ping.init({
'baseUrl': '/lpip/ping.php',
'reconnectTimeout': 15000
});
};
window.checkConnectionStatus = function () {
window.alert(Ping.isOnline() ? 'Online' : 'Offline');
}
}(this, this.Ping, this.document.getElementById('log')));
</script>
</body>
Живой пример: azproduction.ru/lpip (Прошу долго не проверять)
Исходник: narod.ru/disk/6061703001/Ping.rar.html
Критика, предложения приветствуются. Буду рад если кто-нибудь напишет вариант ping-сервера под nginx.