Пришедшие вчера за помощью студенты натолкнули на мысль окончательно завершить данную тему.
Итак, чего не так в нашем скрипте для определения IP и местоположения пользователя?
А не так то, что мы анализируем лишь один содержащий IP параметр: REMOTE_ADDR
. Т.е. на самом деле это правильно, как сказано в замечательной статье. Но всей информации мы можем не увидеть, и даже у пользователя из какой-нибудь Сызрани, сидящего через не анонимный прокси, вместо Сызрани будет гордо высвечиваться какой-нибудь Вашингтон. Исправим это, поступив точно так, как рекомендуют поступить в вышеозначенной статье. Поле REMOTE_ADDR
будем анализировать в качестве первичного и основного источника информации, а потом пробежимся по всем заголовкам HTTP_
(VIA, X_FORWARDED_FOR, X_CLIENT_IP
и т.д., сколько найдем), достанем из них все, что соответствует шаблону IP, скормим определялке географического положения и выдадим в качестве дополнительной информации.
Пользователь может сидеть не через единственный прокси, а через каскад (тоже не анонимный, хехе). В таком случае, в одном или нескольких заголовках HTTP_
могут быть перечислены несколько прокси, причем тут нет никаких стандартов. Вполне возможна ситуация «кто в лес, кто по дрова»: прокси будут перечислены через запятую, пробел, через знак |, двоеточие. Это тоже нужно учесть.
Для начала небольшой: заведем условные области «подключаемых скриптов» и «глобальных переменных» перед условной «областью функций». Понятно, что в php нет никаких «областей» в скрипте, и писать можно что и где угодно, но мне так гораздо приятнее глазу.
Перенесем из условного «тела» скрипта в область внешних скриптов строчку:
include("SxGeo.php");
А в область глобальных переменных перенесем из функции
isip()
переменную$ip_pattern="#(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)#";
и команду, создающую объект SxGeo:$SxGeo = new SxGeo('SxGeoCity.dat');
Переменная с регулярным выражением IP у нас будет использоваться несколько раз в разных функциях, так что пусть лежит в одном месте (в начало функции isip()
надо не забыть дописать код global $ip_pattern;
), а объект SxGeo в процессе работы может несколько раз создаваться, так что пусть и создастся один раз в самом начале работы скрипта, потом просто будем к нему обращаться.Условная структура данных предыдущего скрипта была такова:
"IP=IP-адрес пользователя";
"ISO_CODE=Код страны (ISO)";
"COUNTRY_NAME=Страна";
"CTNR_LAT=Широта страны";
"CTNR_LON=Долгота страны";
"REGION_ISO=Код региона (ISO)";
"REGION_NAME=Регион";
"CITY_NAME=Город";
"CTY_LAT=Широта города";
"CTY_LON=Долгота города";
Дополню ее двумя полями:"FIELD=Поле с данными об IP";
"MESSAGE=Сообщение об ошибке";
Т.е. скрипт будет также показывать информацию, из какого поля получена информация об IP (имя
HTTP_
-заголовка, REMOTE_ADDR
или вручную, если IP был передан через GET
-запрос в параметре ip
), а также отображать статус своего исполнения (ОК или ошибка, и какая).Создаем отдельную функцию определения местоположения IP
Создаем новую функцию
get_info_ip($field, $ip)
и переносим туда почти весь код из «тела» скрипта, немного его модифицируем:
function get_info_ip($field, $ip)
{
global $SxGeo;
$retv=""; //возвращаемое значение
// проверка на соответствие формату
if (!isip($ip))
{
//не IP - записали в поле MESSAGE сообщение об ошибке и прекратили работу
$retv=$field."|0.0.0.0|ERROR_NOT_IP|0|0|0|0|0|0|0|0|0|";
return $retv;
}
//проверяем, не попал ли IP в особый диапазон
$check_diap = get_spec_diap($ip);
if ($check_diap!=1)
{
$retv=$field."|".$ip."|".$check_diap."|0|0|0|0|0|0|0|0|0|";
return $retv;
}
$add_info = $SxGeo->getCityFull($ip); // Вся информация о городе
$main_info = $SxGeo->get($ip); // Краткая информация о городе или код страны (если используется база SxGeo Country)
//"FIELD|IP|MESSAGE|ISO_CODE|COUNTRY_NAME|CTNR_LAT|CTNR_LON|REGION_ISO|REGION_NAME|CITY_NAME|CTY_LAT|CTY_LON|n";
$retv=$field."|".$ip."|OK|".$main_info['country']['iso']."|".$add_info['country']['name_en']."|".
$add_info['country']['lat']."|".$add_info['country']['lon']."|".
$add_info['region']['iso']."|".$add_info['region']['name_en']."|".
$main_info['city']['name_en'].'|'.$main_info['city']['lat']."|".$main_info['city']['lon'].'|';
return $retv;
}
Основные отличия от предыдущей версии скрипта в том, что:
1. Немного поменяна последовательность получения данных.
2. При ошибках и предупреждениях работа скрипта не прерывается командой
die();
, а формируется структура с заполненным сообщением об ошибке и пустыми данными3. Функция сама не занимается выводом сообщений. Результат работы аккумулируется в переменной и возвращается вызвавшей функции.
4. Добавлен аргумент
$field
, куда вызвавшая функция должна записать источник IP-адреса.1. В цикле обойдем массив
$_SERVER
и если в его элементах HTTP_
будет найдено совпадение с регулярным выражением для ip, то вызовем функциюpreg_match_all($ip_pattern,$v,$matches);
в таком виде, где:
$ip_pattern
— регулярное выражение для IP$v
— значение элемента массива $_SERVER$matches
— многомерный массив, в который preg_match_all
запишет все найденные IP.2. С помощью двух вложенных циклов обойдем массив
$matches
и передадим каждый IP функции get_info_ip
function get_all_info_ip() //получаем информацию о всех IP, из всех переменных HTTP_* сервера
{
global $ip_pattern;
$ret="";
foreach ($_SERVER as $k => $v)
{
//если нашли в поле HTTP_* (HTTP_VIA, HTTP_X_FORWARDED_FOR и т.д.)
//что-то похожее на IP
if ((substr($k,0,5)=="HTTP_") AND (preg_match($ip_pattern,$v)))
{
preg_match_all($ip_pattern,$v,$matches); //вытаскиваем из строки все совпадения с шаблоном IP
foreach ($matches as $tmp) //preg_match_all выдает многомерный массив
{
foreach($tmp as $ip) //вытаскиваем каждый отдельный IP
{
$ret.=get_info_ip($k,$ip)."n"; //получаем информацию для каждого IP
}
}
}
}
return $ret;
}
Выведем структуру данных и сообщение о начале анализа основного IP:
echo "FIELD|IP|MESSAGE|ISO_CODE|COUNTRY_NAME|CTNR_LAT|CTNR_LON|REGION_ISO|REGION_NAME|CITY_NAME|CTY_LAT|CTY_LON|n";
echo '---START-MAIN-DATA---'."n";
Если IP был передан через переменную GET-запроса ip, то анализируем только его, выводим информационную строку о завершении анализа основного IP и прерываем работу скрипта:
if (isset($_GET['ip']))
{
$ip=$_GET['ip'];
echo get_info_ip("MANUAL",$ip)."n";
echo '---END-MAIN-DATA---'."n";
die();
}
Если IP поступил от пользователя, анализируем
REMOTE_ADDR
, потом заголовки HTTP_
, выводим данные:$ip = $_SERVER['REMOTE_ADDR'];
echo get_info_ip("REMOTE_ADDR",$ip)."n";
echo '---END-MAIN-DATA---'."n";
echo '---START-ADD-DATA---'."n";
$ip=get_all_info_ip();
echo $ip;
echo '---END-ADD-DATA---'."n";
Получился скрипт, выдающий данные об IP пользователя в виде, удобном для машинной обработки (например приложению или скрипту для ведения логов).
Каскад прокси
Анонимный прокси, заполняющий несколько заголовков HTTP_
Скачать. Посмотреть код на PasteBin Посмотреть в работе
Впрочем, совсем не составляет труда сделать ему вид, более радующий глаз человека:
1. В строке
header('Content-type: text/plain; charset=utf8');
изменим text/plain
на text/html
2. Модифицируем сообщения скрипта.
3. Добавим код, выводящий оформление HTML (2 и 3 см. в самом скрипте ниже)
Каскад прокси
Анонимный прокси, заполняющий несколько заголовков HTTP_
Скачать. Посмотреть код на PasteBin Посмотреть в работе
Предыдущая серия
Это перепост заметки из моего блога на LJ.ROSSIA.ORG
Оригинал находится здесь: http://lj.rossia.org/users/hex_laden/330304.html
Прокомментировать заметку можно по ссылке выше.
Pingback: Определение IP и местоположения посетителя сайта 4. | Персональный блог Толика Панкова
Pingback: Определение IP и местоположения посетителя сайта 6. | Персональный блог Толика Панкова