Логирование HTTP трафика для отладки запросов AJAX в Linux

Часто разработчику требуется видеть весь обмен трафиком между клиентом и сервером, не только заголовки HTTP, но и тело запроса и ответа. Я довольно долго разыскивал подходящий инструмент для этих целей. Но оказалось, что удобнее всего пользоваться не какими-то плагинами для браузера, а простейшим прокси и перехватом трафика с помощью tcpdump.

Выглядит это так:

1. В браузере прописываем использование прокси по адресу 127.0.0.1 порт 5678
2. В терминале открываем сессию ssh с локальным порт-форвардингом (можно и со сжатием трафика), например так:

$ ssh -C -D 5678 username@myhost.com

3. В другом терминале запускаем tcpdump и слушаем трафик на интерфейсе lo порт 5678:

# tcpdump -vvv -s0 ‘port 5678′ -w “/home/username/http.pcap” -i lo

Далее в браузере выполняем необходимые действия, после чего останавливаем tcpdump. Смотреть записанный трафик лучше всего с помощью strings:

$ strings /home/username/http.pcap

Иногда в выводе может быть немного мусора из-за keepalive запросов, но в целом видно все, что требовалось узнать.

Новый сервис: онлайн форматирование JSON

Давненько я не писал что-нибудь общественно-полезного. А тут как раз пришлось писать очердной вебсервис с блэкджеком и шлюхами, который должен выплевывать неотформатированный JSON, и в целях отладки мне пришлось его форматировать скриптами.

Потом я понял, что лично мне нужен инструмент, в который я смогу запостить нечитабельный JSON, и на выходе получить читаемый.

Если погуглить, такие сервисы уже есть. Но либо тяжелые, либо медленные, либо обвешанные рекламой. В результате сделал свой сервис, сам пользуюсь и другим рекомендую.

В качестве теста можно использовать строку JSON, например, такую:

{"data":[{"t1":"John","t2":"Doe"},{"t1":"Anna","t2":"Smith" },{"t1":"Peter" ,"t2":"Jones"}]}

На выходе будет удобно читаемый JSON:

{
   "data" : [
      {
         "t2" : "Doe",
         "t1" : "John"
      },
      {
         "t2" : "Smith",
         "t1" : "Anna"
      },
      {
         "t2" : "Jones",
         "t1" : "Peter"
      }
   ]
}

Обычным людям такой сервис вряд ли будет не нужен, но программистам – пригодится.

Perl: как избавиться от Wide character in print

Как известно, при запуске программы на Perl автоматически открываются 3 файловых дескриптора: STDIN, STDOUT и STDERR. По умолчанию они не используют кодировку utf8, поэтому print вполне может выдавать вот такой вот warning при выводе кириллических символов:

Wide character in print at line …

Ничего страшного в этом нет, но такие предупреждения засоряют вывод и раздражают меня. Лечится это довольно просто, например, привязкой режима utf8 к уже открытому файловому дескриптору в начале программы:

binmode(STDOUT,’:utf8′);

Но есть и более элегантное решение. Можно прописать в самом начале программы флаг, который скажет интерпретатору Perl открывать файловые декрипторы при запуске программы сразу в utf8, примерно так:

#!/usr/bin/perl -CS

И больше никаких манипуляций с binmode не потребуется

Проверка вхождения IP в диапазон адресов

Однажды мне понадобилось определить средствами php, входит ли IP адрес в диапазон адресов с маской. Пользуясь гуглом и природной ленью, нашел несколько решений. На мой взгляд одно из самых удачных выглядит так:


function net_match($network, $ip)
{
$ip_arr = explode('/', $network);
$network_long = ip2long($ip_arr[0]);
$x = ip2long($ip_arr[1]);
$mask = long2ip($x) == $ip_arr[1] ? $x : 0xffffffff << (32 - $ip_arr[1]);
$ip_long = ip2long($ip);
return ($ip_long & $mask) == ($network_long & $mask);
}

echo (net_match('192.168.17.1/16', '192.168.15.1')?"True":"False")."\n"; // пишет True
echo (net_match('127.0.0.1/255.255.255.255', '127.0.0.2')?"True":"False")."\n"; // пишет False
echo (net_match('10.0.0.1/32', '10.0.0.1')?"True":"False")."\n"; // пишет True

Работает на ура с двумя типами масок, как на 32 битных, так и на 64 битных машинах.

Управление ответом в php скрипте

Однажды мне понадобилось отправлять ответ пользователю в браузер до завершения работы php-скрипта. Известно, что даже такой простой скрипт не отпустит посетителя со страницы в течение 20 секунд:

header("Location: http://www.bloged.org",TRUE,301);
sleep(20);
?>

Т.е. пока скрипт не завершит свою работу, по умолчанию результат не уйдет в браузер и редиректа не случится. Однако, это ограничение можно обойти, например так:

set_time_limit(80);
header("Location: http://www.bloged.org",TRUE,301);
header("Content-Length: 0");
header("Connection: close");
flush();
error_log("Это пишется сразу в лог\n", 3, "/ramdisk/log.txt");
sleep(60);
error_log("А это через минуту\n", 3, "/ramdisk/log.txt");
?>

Несмотря на то, что пользователь уже перенаправлен в другое место, скрипт будет работать еще минуту и в логи запишет все, что надо.

JQuery, ColorBox и передача значений из iframe в родительское окно

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

Длительное гугление готовое решение не подсказало, поэтому пришлось все писать самому. В качестве движка я выбрал плагин ColorBox для JQuery, и вот что у меня получилось.

За основу был взят пример Outside Webpage (Iframe), и совсем немного допилен до подходящего состояния.

Интересный момент, изначально открытие iframe происходило по нажатию на ссылку с заданным классом; движок читал свойство href у ссылки и открывал iframe с URLом из атрибута href. Мне же надо было открывать модальное окно по нажатию на кнопку (), а атрибута href у нее стандартами не предусмотрено. Пришлось изобрести свой атрибут, и вроде бы оно работает

Исходный код примера вместе со всеми библиотеками также доступен для скачивания, может кому-то и пригодится.

Как обойти защиту от хотлинкинга

Давненько я не писал чего-нибудь общественно-бесполезного. На одном из популярных форумов люди постят картинки с сайтов, которые не хотят, чтобы их смотрели где-то еще. А вбивать чужой урл и идти на страницу с картинкой иногда бывает лень. Поэтому, чтобы такой посмотреть, надо либо подделать реферер (с помощью какого-либо плагина к браузеру), либо воспользоваться моим новым сервисом просмотра картинок с защитой от хотлинкинга.

Работает это как прокси-сервер, скрипт забирает картинку и отдает ее тому, кто ввел капчу. Вроде несложно и кому-то может пригодиться. Кстати, вручную реферер вбивать не обязательно, скрипт попытается его вычислить. Но надежности ради я предусмотрел возможность добавления реферера вручную. Проверить можно хотя бы на этом примере.

Если найдете багу, просьба писать в камменты здесь или напрямую написать мне

З.Ы. Сервис абсолютно бесплатный

Data::Dumper и текст в UTF-8

Если вы работаете со сложными структурами данных (массивы хешей, хеши массивов и т.п.), наверняка вы используете для отладки модуль Data::Dumper. Все бы хорошо, но этот модуль категорически не хочет работать с символами в кодировке UTF-8, его вывод выглядит примерно так:

'country' => "\x{420}\x{43e}\x{441}\x{441}\x{438}\x{44f}",
'city' => "\x{41c}\x{43e}\x{441}\x{43a}\x{432}\x{430}",
'airport' => "\x{414}\x{43e}\x{43c}\x{43e}\x{434}\x{435}\x{434}\x{43e}\x{432}\x{43e}",
'code' => 'DME'

Чтобы сделать вывод функции Dumper читаемым, надо просто добавить в программу вот такой кусочек кода:

$Data::Dumper::Useqq = 1;

{ no warnings 'redefine';
sub Data::Dumper::qquote {
my $s = shift;
return "'$s'";
}
}

Тогда вывод будет выглядеть совсем по другому:

'country' => 'Россия',          
'city' => 'Москва',
'terminal' => '',
'airport' => 'Домодедово',
'code' => 'DME'

Эффект черного ящика

Многие программисты на Perl частенько используют модуль XML::Simple для обработки файлов. Казалось бы, что может быть проще, чем распарсить файл:

use XML::Simple;
my $xs = XML::Simple->new();
my $ref = $xs->XMLin(‘test.xml’);

Однако даже такой простой кусок кода в разных системах реагировал по-разному на один и тот же исходный файл. Если в исходном файле XML встречались символы в кодировке utf8, скрипт мог свалиться на этапе разбора файла.

Выяснилось, что модуль XML::Simple сам по себе не занимается разбором XML, и парсинг происходит с помощью других доступных модулей. Если в системе установлен XML::SAX, то именно он будет использоваться по умолчанию, в противном случае для разбора будет применяться XML::Parser.

С XML::SAX тоже не все просто, он для разбора использует либо XML::SAX::PurePerl (что медленно и не всегда корректно работает), либо XML::SAX::Expat или XML::SAX::ExpatXS (если они установлены).

Так вот, чтобы XML::Simple смог корректно разобрать XML файл со строками в кодировке UTF-8, надо установить XML::SAX::Expat или XML::SAX::ExpatXS. Это совершенно не очевидно, и об этом можно забыть при переносе скриптов с сервера на сервер.

Апгрейд MySQL и переписывание запросов

Официальный логотип Mysql

Технически апгрейд базы MySQL несложен, но могут возникнуть не очевидные подводные камни. Например, если MySQL был 4-й версии, а после upgrade версия стала выше 5.0.11, то могут возникнуть проблемы с работой запросов, использующих LEFT JOIN.

Начиная с версии MySQL 5.0.12 запросы с JOIN обрабатываются по стандарту SQL:2003, поэтому такие запросы к базе придется переписать. Вот пример:

CREATE TABLE IF NOT EXISTS `t1` (
`id` int(10) unsigned NOT NULL auto_increment,
`type` enum('a','b') default NULL,
PRIMARY KEY (`id`),
KEY `type` (`type`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t1` VALUES (1, 'a'), (2, 'a'), (3, 'b'), (4, 'a'), (5, 'b');

CREATE TABLE IF NOT EXISTS `t2` (
`t1_id` int(10) unsigned default NULL,
KEY `t1_id` (`t1_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t2` VALUES (1), (1), (1), (2), (2);

CREATE TABLE IF NOT EXISTS `t3` (
`t1_id` int(10) unsigned default NULL,
KEY `t1_id` (`t1_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `t3` VALUES (1), (1), (2), (2), (3);

#Test 1
select t1.id
from t1, t2
left join t3 on t1.id = t3.t1_id and t1.type = 'a'
where t1.id = t2.t1_id
group by t1.id
#1054 - Unknown column 't1.id' in 'on clause'

#Test 2, swap the order of the conditions in "on"
select t1.id
from t1, t2
left join t3 on t1.type = 'a' and t1.id = t3.t1_id
where t1.id = t2.t1_id
group by t1.id
#1054 - Unknown column 't1.type' in 'on clause'

В MySQL 4.x запрос работает без ошибок, начиная с версии 5.0.12 запрос ругается на синтаксис. Если в эти запросы добавить скобок, все заработает:

select t1.id
from (t1, t2)
left join (t3) on (t1.id = t3.t1_id and t1.type = 'a')
where t1.id = t2.t1_id
group by t1.id

select t1.id
from (t1, t2)
left join (t3) on (t1.type = 'a' and t1.id = t3.t1_id)
where t1.id = t2.t1_id
group by t1.id