Толик Панков
hex_laden
............ .................. ................

October 2025
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Толик Панков [userpic]
Определение IP и местоположения пользователя посетителя сайта 5.

Пришедшие вчера за помощью студенты натолкнули на мысль окончательно завершить данную тему.
Итак, чего не так в нашем скрипте для определения 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-адреса.

Поиск IP в заголовках HTTP_
Алгоритм таков:
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 Посмотреть в работе
Предыдущая серия

Tags: , ,
Comments
(Anonymous)

Пропан -- наше все.

(Anonymous)

газ, газ, газ!!!
16.50 р./л.
ну есть и за 19 и даже за 20.

Ще, где, какой газ?
И IP-адреса юзеров тут причем?