Последнее обновление:
Как проверить на какой сервер посылается веб-запрос?
При разработке веб-приложений, сайтов нередко возникает задача: определить, на какой сервер происходит веб-запрос: в сеть интернет или жена локальный сервер? Пользователю, конечно, в этом убедиться достаточно просто: надо всего лишь выключить соединение с интернетом и затем обновить вебстраницу. Если после этого она вновь будет, как ни в чем ни бывало, показана в браузере, значит, браузер взаимодействует с локальным сайтом. А вот как сделать подобную проверку программно, как вебстраница может сама определить с каким именно сервером происходит взаимодействие, на какой именно сервер отправляются запросы? На тот, который находится на компьютере пользователя (т.е. на локальный сервер) или же в сеть интернет?
Т.е. вебстраница как-то сама, без вмешательства пользователя, должна определять, на какой именно сервер направляется запрос. Здесь могут использоваться, в целом, два основных подхода.
1. Подход
Использование соответствующих сервисов в интернет-сети. Например, это - такие сервисы, как ip-address.ru, 2ip.ru и многие другие. Аналогичный сайт можно сделать и запустить на локальном сервере, т.е. на своем компьютере. И если вебстраница сделает AJAX-запрос на подобный сервис, то он вернет ей ее IP-адрес. И тут зависит от того, куда будет направлен запрос. Если у пользователя на компьютере подключен интернет и одновременно запущен локальный сервер, то есть два возможных варианта, куда пойдет запрос. Если он пойдет в интернет, то соответствующий сервер вернет браузеру, собственно, его IP-адрес в интернете. Который, напомним, может меняться, если пользователь не обладает так называемым "белым", т.е. фиксированным адресом. Обычно фиксированные IP-адреса предоставляются пользователям провайдерами за дополнительную оплату.
А если же запрос пойдет на локальный сервер, то браузер в качестве IP-адреса получит что-то вроде 127.0.0.1. Т.е. локальный адрес. Анализируя ответ сервера, можно легко отличить, куда был направлен запрос.
Отметим, что направление веб-запроса задается на уровне сетевых протоколов модели OSI; браузер, если не делать специальных настроек, полагается на сетевые настройки операционной системы (в том числе, с учетом настроек VPN), в которой он запущен.
Этот подход является универсальным, с одной стороны. Но, с другой, он требует наличия сайта, одноименного с аналогичным сервисом, у пользователя на локальном компьютере. Это, как правило, легко для профессионального разработчика, а вот для обычного пользователя, не связанного с вебразработкой и не имеющего понятия о серверах, такой подход неприменим. Да и едва ли он станет выполнять чьи-то рекомендации и устанавливать у себя некий сервер, одноименный с сервисом определения IP-адресов.
2. Подход
Использование веб-запроса на ресурс, достоверно имеющийся на сервере в интернете и достоверно отсутствующим у пользователя на компьютере. Ну, или, наоборот. Например, это может быть небольшой, буквально объемом в несколько байт, рисунок. Контролируя результат его загрузки (успех или ошибка) можно легко выяснить, на какой сервер (сетевой или локальный) был произведен запрос.
Рассмотрим такой код на javascript:
// Функция устанавливает, на локальном ли сервере производится обработка запросов
function check_LOCAL_SERVER() {
if(document.body.hasAttribute('data-local_server')){
return result(1);
}else {
var test_img = document.createElement('div');
test_img.innerHTML = "<img src = '/pict.png' />"; // Этот рисунок имеется только на локальном сервере, его нет в интернете
// document.body.appendChild(test_img)
test_img.getElementsByTagName('img')[0].onload = function () {
document.body.setAttribute('data-local_server', 'yes');
result(0);
};
test_img.getElementsByTagName('img')[0].onerror = function () {
document.body.setAttribute('data-local_server', 'no');
result(0);
};
console.log('Unknown...');
return null;
}
function result(flag){
if(!flag){
test_img.getElementsByTagName('img')[0].onload = null; // Удаляем обработчики событий (чтобы не загромождать
test_img.getElementsByTagName('img')[0].onerror = null;// оперативную память), т.к. они более не нужны
}
console.log('Is server local, not internet? ' + document.body.getAttribute('data-local_server'));
return document.body.getAttribute('data-local_server');
}
}
document.body.onclick = function () {
alert('Is server local, not internet? '+ check_LOCAL_SERVER());
};
Контрольным рисунком является, в данном случае, pict.png
. Который отсутствует в корневом каталоге соответствующего сайта (вебстраница которого просматривается пользователем), но который достоверно есть у пользователя на компьютере. Понятно, что можно сделать и наоборот: напротив, разместить этот рисунок на сервере в интернете, зная точно, что на компьютере пользователя такого рисунка нет. Это может быть рисунок с неким "сложным", уникальным именем.
Немного пояснений
Суть в том, что функция check_LOCAL_SERVER
создает атрибут с именем data-local_server
в теге <body> вебстраницы. Вы можете, разумеется, взять какое-то другое имя этого атрибута, но главное, чтобы его имя было уникальным, чтобы оно не использовалось другими скриптами, работающими на этой вебстранице.
Изначально, после запуска и отображения страницы браузером, эта функция проверяет наличие указанного атрибута. Его, естественно, пока еще нет, поэтому создается временный div, цель которого - попытаться загрузить в себя рисунок с указанным именем. Если загрузка будет успешной (это будет, в частности, когда рисунок присутствует на сервере), то сработает событие onload
. В случае же отсутствия рисунка, сервер вернет ошибку 404 и сработает событие onerror
. В зависимости от этого, будет установлен атрибут data-local_server
в document.body
с соответствующим значением и будет вызвана функция result
. После этого уже любой скрипт, обратившийся к данному атрибуту и прочитавший его значение, будет знать, получен ли был рисунок с сервера или нет. Следовательно, скрипт будет знать, на какой сервер (локальный или интернетный) был направлен запрос на получение этого рисунка и, следовательно, и все другие запросы в рамках текущей сессии.
hosts
!Для наглядности, нижние строчки приведенного JS-кода задают обработчик клика на теге <body>. Для тестирования данного скрипта. В реальном проекте это, скорее всего, не понадобится. Кликая мышью где-нибудь на странице, можно будет видеть ответ (в окошке alert), с локального ли сервера произведен был запрос или нет.
Отметим, что после самого первого клика ответ будет неопределенным, т.е. null. Это вызвано тем фактом, что ответ выводится практически сразу после клика, не дожидаясь ответа сервера. Т.е. окно alert с сообщением возникнет ДО момента ответа сервера. И в тег <body> еще ничего не успеет записаться, параметра data-local_server
там еще не будет, т.е. его значение пока - не определено.
Также стоит посмотреть сообщения в консоли браузера.
Однако, через короткое время сервер таки ответит и, в зависимости от результата такого ответа, обработчик запишет в тег <body> параметр data-local_server
с тем или иным значением ("yes" или "no"). Поэтому все последующие срабатывания обработчика, в результате кликов мыши, будут использовать уже это значение.
Пример - демонстрационный, но его вполне можно приспособить конкретно для своих нужд. Однако, он имеет недостаток. Дело в том, что если рисунка нет на сервере в интернете, то в ответ сервер вернет ошибку 404. И, попутно, вместе с этим - и соответствующую страницу, типа 404.html. Такая страница может занимать немалый объем. Это означает, что при не слишком быстром интернет-соединении могут быть определенные задержки при идентификации сервера - локального или в интернете. Особенно это важно в случаях, когда вебстраница периодически и часто отправляет AJAX-запросы. Перед отправкой которых важно знать, куда именно они направляются - на локальный сервер (т.е. на компьютер пользователя) или же в интернет. Поэтому лучше использовать усовершенствованный 2-й подход. Назовем его 2.1.
2.1. Подход
В его основе - измерение размеров изображения pict.png
, пришедшего либо с локального сервера (т.е. с того же самого компьютера), либо из интернета. И если изображение с указанным именем есть и там, и там, но имеет разные размеры, то в обоих случаях при запросах на это изображение не будет никакой ошибки 404, а будет возвращено изображение. И по его размерам можно будет быстро сделать вывод, откуда оно пришло.
Допустим, рисунок с указанным именем, имеющийся на сервере в интернете, имеет ширину 10px, а тот, что на локальном сервере - ширину 2px.
// Функция устанавливает, на локальном ли сервере производится обработка запросов
function check_LOCAL_SERVER() {
var server;
var test_img = document.createElement('div');
test_img.innerHTML = "<img src = 'http://example.com/pict.png' />"; // Этот рисунок имеется и на локальном сервере, и в интернете
// document.body.appendChild(test_img)
test_img.getElementsByTagName('img')[0].onload = function () {
var width = this.width;
server = (parseInt(width) === 2)? 'local server' : 'internet server';
// alert(server+ ' '+ this.width);
sessionStorage.setItem('server', server);
};
test_img.getElementsByTagName('img')[0].onerror = function () {
server = 'Невозможно загрузить тестовый рисунок!';
sessionStorage.setItem('server', server);
};
}
document.body.onclick = function () {
check_LOCAL_SERVER();
setTimeout(function () {
alert(sessionStorage.getItem('server'));
}, 10);
};
После клика на странице (точнее, на ее области, которую занимает тег <body>
) скрипт через короткое время выдаст результат, на какой сервер был направлен запрос.
Отметим, что вместо свойства width
предпочтительнее использовать также naturalWidth
. Это дает значение ширины непосредственно самого изображения, а не тега <img />
, его содержащего.
Здесь тип сервера (local server
или internet server
), в отличие от предыдущего примера, сохраняем не в каком-либо теге, а в сессии браузера.
3. Подход
Предыдущие подходы, в целом, работоспособны. Но, у них есть недостаток: они применимы, как правило, только при очередной загрузке или обновлении страницы. В таких случаях, в самом деле, они дают хорошую возможность определить, на какой из серверов (локальный или в интернете) были посланы запросы.
Однако, в реальности ситуация бывает сложнее. Например, в ходе работы пользователя со страницей в браузере локальный сервер может вдруг отключиться или, напротив, запуститься снова. То же самое, теоретически, может быть и с сервером, находящемся в интернете. И в таких случаях, к сожалению, указанные выше подходы неработоспособны.
Есть такие понятие, как кэш браузера. Причем, кэшируются не только ресурсы, полученные с вебсерверов (например, картинки, видео, JS-скрипты), но и IP-адреса.
Предположим, пользователь работал с вебстраницей того или иного домена на локальном сервере. В ходе работы эта вебстраница посылала AJAX-запросы к серверу, принимает от него ответы. И вот, в определенное время локальный сервер прекратил работать...
А вебстраница продолжает посылать AJAX-запросы без ее перезагрузки. Куда они будут направляться?
Разумеется, в сеть интернет, на сервер с одноименным доменным именем. При условии, конечно, что сеть интернет - доступна и браузеру разрешен выход в интернет. Причем, ни браузер, ни, тем более, пользователь вначале не обнаружат подвоха. Что запрос-то пошел вовсе не туда, куда предназначался... Подвох будет обнаружен, возможно, лишь через несколько секунд, после того, как браузер получит от интернетного сервера ответ, который, скажем так, вовсе не ожидался при работе с локальным сервером. Это, как минимум, неудобно и вносит определенную неразбериху.
Хорошо, пользователь может сообразить, что что-то пошло не так. И - запустить локальный сервер снова. Однако, если вебстраница будет посылать AJAX-запросы на тот же домен, то браузер некоторое время будет продолжать использовать тот же самый IP-адрес, который он использовал недавно. Т.е. - IP-адрес в сети интернет, а не локальный (типа 127.0.0.1). Даже несмотря на то, что, казалось бы, после запуска локального сервера в файле hosts
для используемого домена будет вновь прописан локальный (а не интернетный) IP-адрес.
Почему так? А потому, что браузер кэширует IP_адреса. И запросы в течение ближайшего времени (около нескольких минут, хотя, это зависит от версии и вида браузера) будут направляться на тот IP-адрес, который присутствует в кэше, а не на тот, который указан в файле hosts
.
Поэтому, даже если вновь включить/перезагрузить локальный сервер, все равно браузер будет пытаться направлять запрос в интернет, пока его кэш IP-адресов не обновится. Т.е. для корректной работы пользователю придется либо выжидать время, либо полностью обновлять страницу через Ctrl + F5
. Примерно аналогичный результат будет, если контролировать работу локального сервера путем запроса на загрузку небольшого рисунка, как показано в подходах 1...2.
Как быть?
Один из вариантов - запретить браузеру делать AJAX-запросы на целевой домен, если нужный сервер (например, локальный) не работает. И разрешать их тогда и только тогда, когда браузер убедится в обратном.
Как убедиться в том. что целевой (локальный) сервер - работает? Для этого можно, например, направить предварительный - тестовый - запрос на какой-то другой домен, который также, как и целевой, расположен на локальном сервере. И которого, что важно(!), нет в интернете.
Согласно RFC 2604 и RFC 6761, в качестве домена первого уровня, заведомо несуществующего в интернете, являются домены .invalid
и .localhost
. Зарезервированными доменами второго уровня являются example.com, example.net, example.org
. Т.е. если браузер пошлет AJAX-запрос вида http://example.invalid
, то в интернете гарантированно НЕ БУДЕТ такого домена. И, соответственно, IP-адреса такого домена в интернете не существует, соответствующие DNS-запросы браузера через некоторое время вернут ошибку, мол, адрес не найден.
А вот на локальном сервере такой домен вполне может присутствовать и быть работоспособным. Для этого нужно лишь создать соответствующую папку и там как обычно, в папке www
, создать файл index.html
или index.php
, возвращающий корректный ответ на запрос браузера.
Разумеется, на этом домене потребуется включить политику CORS.
Таким образом, если посылать запрос не на целевой, а на заведомо другой домен (который ДОЛЖЕН присутствовать на локальном сервере, но которого НЕ ДОЛЖНО БЫТЬ в интернете), например, на тот же example.invalid
, то, если локальный сервер доступен, при подключенной политике CORS будет получен ответ вида '200 ОК
'. Если же локальный сервер будет недоступным, то браузер, направив запрос в интернет, не получив ответа и, тем более, не получив заголовков подключенной CORS, вообще не сможет принять ответ из интернета; даже если вдруг домен example.invalid будет в интернете найден. Поэтому браузер выдаст ошибку и она выведется в его консоли.
Поэтому браузер НЕ БУДЕТ(!) кэшировать соответствующий IP-адрес в интернете. Так как запрос на домен example.invalid
хотя и был, но закончился ошибкой. IP-адрес этого домена в интернете браузером получен не был. Поэтому и кэшировать, собственно, нечего.
Следовательно, как только локальный сервер будет вновь включен, браузер сразу же будет направлять запрос именно на локальный сервер, а не в интернет. Т.е. выжидать время не потребуется вообще. Поэтому данный способ предпочтительнее, чем способы 1...2.
Однако, для его реализации перед каждым AJAX-запросом на целевой домен придется вначале направлять тестовый запрос на тестовый домен, которого заведомо нет в интернете (example.invalid
). Т.е. будет в два раза больше запросов. Впрочем, запрос на картинку ведь тоже, по сути, является запросом.
Вот простой пример:
<!DOCTYPE HTML>
<html>
<head>
<title>Проверка сервера</title>
</head>
<body onclick="check_server(30)">
<p>TEST</p>
<script type="text/javascript">
function check_server(timeout) {
var xhr = new XMLHttpRequest();
var url = 'http://example.invalid?' + 'random=' + Math.random();
var mess = 2;
var response = '';
try {
xhr.open("POST", url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.timeout = timeout;
xhr.ontimeout = function() { // Вызывается после истечения заданного таймаута
alert( 'The request exceeded the maximum time. Therefore, it was probably sent not to the local server, but to the Internet and the response has not yet been received.' );
};
xhr.onreadystatechange = function xhr_state() {
if (xhr.readyState != 4) return;
response = xhr.responseText;
if (xhr.status == 200) {
console.log('local server');
} else {
console.log('The local server may be unavailable');
}
};
} catch (err) {
alert("Unable to connect to server");
}
xhr.send(mess);
}
//check_server(30); // Запускаем функцию с таймаутом, равном 30 миллисекунд.
</script>
</body>
</html>
Сохраните этот код в файле html (например, test_server.html
). И - запустите его по протоколу http
, например, http://site.ru/test_server.html
. Это если файл будет расположен в корневом каталоге сайта site.ru
.
После нажатия на слово "TEST" в консоли браузера будет сообщение, доступен ли локальный сервер или нет. Для проверки, выключите локальный сервер и кликните по слову "TEST". Затем - включите локальный сервер и повторите тестирование.
Этот способ, по всей видимости, является универсальным. И, в отличие от первых двух способов-подходов, может быть корректно применен не только при обновлениях страницы, но и при AJAX-запросах.
А уже после того, как станет известным, на какой сервер уходит запрос, можно сделать дальнейшую аналитику, в том числе, уведомив пользователя об этом. Единственное НО: для того, чтобы этот подход работал, потребуется создать локальный домен example.invalid
(и там - файл index.html
или index.php
). Но это, видимо, несложно.
Когда это может быть нужным?
Например, при разработке веб-приложений, который используют ресурсы, хранящиеся на стороне клиента (т.е. на компьютере пользователя). Также это может быть полезным при разработке разного рода обслуживающих программ, систем, которые должна запускаться именно с компьютера пользователя, но не с сервера.
Какой подход является более эффективным?
В какой-то мере, более оптимальным и эффективным является 3-й подход. Потому, что он более результативен, в том числе, и в условиях сетевого соединения плохого качества. Когда непонятно (это если говорить о 1-м и, особенно, о 2-м подходе), чем вызвана ошибка 404 при запросе рисунка клиентом (браузером): то ли и в самом деле его отсутствием на сервере, то ли сетью плохого качества. Правда, для более тщательного анализа ситуации можно дополнительно использовать, скажем, событие onreadystatechange
. Которое позволяет контролировать не только конечный результат (да или нет, успех или неудача), но и промежуточные стадии обработки сетевого запроса. Ну, а также можно дополнительно анализировать код ошибки, возвращаемый сервером.
Кроме того, 1-й подход требует наличия "клона" интернет-сервиса, определяющего IP-адрес клиента, на локальном компьютере пользователя, что может быть не всегда удобно, по сравнению со 2-м подходом. К тому же, 2-й подход лучше еще и тем, что время ответа сервера на отправку небольшого рисунка будет гораздо, на порядки, меньше, чем время ответа известных сервисов по проверке IP-адресов. Кроме того, некоторые сервисы могут неконтролируемо изменить свою функциональность.