понедельник, декабря 24, 2007

Perl для Windows

Когда я ещё программил под чистым Windows(совмещая это с программированием под чистым *nix) я использовал для своих нужд ActivePerl. Однако достаточно быстро обнаружил что там есть не всё что мне нужно для работы (сейчас уже не вспомню конкретный пример, но не было достаточно простых модулей) и перешёл на виртуалку под windows (пробовал сначала cygwin, потом перешёл на vmware). В принципе прежде чем использовать ActivePerl надо было бы выяснить что компания канадская, а согласно источнику заслуживающему доверия (коим несомненно является мульт South Park) канадцы все какие-то не такие и делают всё не так - и тогда бы я не потратил время зря. :)

Сейчас узнал что оказывается есть вроде полноценные альтернативы... Например StrawberryPerl . В комплекте Strawberry Perl идёт всё необходимое для того чтобы инсталлить модули прямо с CPAN (в том числе и XS модули).

Вобщем альтернатива вполне достойная. И парни из ActiveState видимо это понимают, так как поспешили выпустить 5.10 раньше чем выйдет StrawberryPerl 5.10 - эта новость пробегала по perl'овым блогам/группам.
P.S. Возможно сейчас ситуация с perl от ActiveState улучшилась в разы - я знаю что многие гуру, ну или почти гуру успешно им пользуются. Да и времени утекло много с тех пор как я его смотрел...

суббота, декабря 15, 2007

ООП в perl. Class::Std vs Object::InsideOut

Речь пойдёт о perl без perl6 синтаксиса.

Большинство знает что классов как таковых в perl5 нет. Вместо этого издавна использовался blessed hash, т.е. объект класса создавался примерно так(этот код естественно в соответствующем классу package):
sub new {
my $self = {};
$self->{NAME} = undef;
$self->{VERSION} = undef;
$self->{OPTIONS} = [];
bless($self);
return $self;
}

sub method1 {
...
}

sub _method2 {
...
}
Ну а далее вся работа с модулем строилась на условных соглашениях, таких как например имена приватных методов и полей начинаются с подчёркивания (см. выше _method2). Естественно были решения позволяющие сделать правильные приватные методы (приведу достаточно экзотический пример: http://perlmonks.org/?node_id=332744 ), но их использование для каждого класса было не очень удобным, так как заставляло делать много лишних телодвижений. Некоторые использовали соглашение о приватных методах (имена с подчёркиванием) и скрипт-сканер который проверял все файлы проекта на то что никто не вызвал приватный метод как публичный. Но на мой взгляд это всё костыли, хоть и работающие.

Многие наверное читали книгу Конвэя: Perl Best Practicies (далее PBP). Там он предлагает отказаться от использования blessed hash объектов и использовать InsideOut объекты. Не знаю как точно перевести это, ну можно например как объекты наизнанку :). Смысл в том что объект не является хэшом с полями а наоборот для каждого поля класса создаётся хэш или массив. В книге Конвэй в основном рассматривает свой собственный класс для использования InsideOut объектов: Class::Std . Но на практике он не всегда идеален. Рассмотрим например класс в котором в конструкторе кидается exception:

package cstd;

use Carp qw(croak);

use Class::Std;
{

sub sub1 : PRIVATE {
croak "sub1 exception";
}

sub BUILD {
sub1();
}
}
1;


Code syntax highlighting by VIM captured with ScreenShot script

И вот этот в котором exception в конструкторе не кидается:

package cstd2;

use Carp qw(croak);

use Class::Std;
{

sub sub1 {
croak "sub1 exception";
}

sub BUILD {

}
}
1;


Code syntax highlighting by VIM captured with ScreenShot script
И выполним вот этот тестовый скрипт:
#!/usr/bin/perl

use strict;
use warnings;
use cstd;
use cstd2;

my $a;

print "cstd:\n";
eval {
$a = cstd->new();
};

print $@ if ($@);

print "\ncstd2:\n";
$a = cstd2->new();
eval {
$a->sub1;
};

print $@ if ($@);


Code syntax highlighting by VIM captured with ScreenShot script

В результате получим вот это:
cstd:
sub1 exception at /usr/lib/perl5/site_perl/5.8.8/Class/Std.pm line 438

cstd2:
sub1 exception at cstd.pl line 20
Как видим в первом случае невоможно понять где произошло исключение, в то время когда exception генерится не в конструкторе - то получаем сообщение о том что exception в нашем скрипте на строке 20.

Создадим класс с exception в методе вызываемом в конструкторе с помощью Object::InsideOut :

package objInOut;
{
use strict;
use warnings;
use Carp qw(croak);
use Object::InsideOut;

sub sub1 :Private {
croak "sub1 exception";
}

sub _init :Init {
sub1();
}
}
1;


Code syntax highlighting by VIM captured with ScreenShot script


Выполним следующий скрипт:

#!/usr/bin/perl

use strict;
use warnings;
use objInOut;
use Carp qw(croak);

my $a;

print "object inside out:\n";
eval {
$a = objInOut->new();
};

print $@ if ($@);


Code syntax highlighting by VIM captured with ScreenShot script

В выводе получим:
object inside out:
OIO error: Trapped uncaught error
Error: sub1 exception at objinsout.pl line 12
Package: Carp
File: /usr/lib/perl5/5.8.8/Carp.pm
Line: 269


Trace begun at /usr/lib/perl5/5.8.8/Carp.pm line 269
Carp::croak('sub1 exception') called at objInOut.pm line 9
objInOut::sub1 at objInOut.pm line 13
Object::InsideOut::new('objInOut', 'HASH(0x847d020)') called at objinsout.pl line 12
eval {...} at objinsout.pl line 11

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

Кроме этого Object::InsideOut поддерживает threads - т.е. я бы советовал использовать именно эту реализацию InsideOut классов. Вобщем это ещё одно подтверждения того что часто бывает что удачная реализация удаётся не идейным вдохновителям а кому-то ещё...

воскресенье, ноября 04, 2007

Отладка perl программ. Основы и инструментарий.

Эта заметка будет в основном интересна тем кто не выработал свою методологию отладки приложений perl или не подобрал нужного инструментария для этого.

В принципе для того чтобы отлаживать программу написанную на perl достаточно просто иметь perl. Довольно часто (особенно при отладке на завершающей стадии сложных многопоточных программ) наиболее удобным способом является отладка логгированием в файл или прямо в stdout. Для логгирования в файл есть достаточно удобный модуль File::Log. Кроме грамотного оформления(timestamp, pid и т.п.) лога он позволяет менять детализацию лога с помощью уровней дебага, т.е. при создании объекта класса File::Log задаётся уровень для отсечения сообщений с уровнем выше данного уровня (т.е. в лог будут попадать только сообщения с уровнем ниже или равным данному уровню).

Естественно необходим дебаггер в классическом понятии. В простейших случаях или когда больше ничего нет можно использовать стандартный perl debugger: perl -d script.pl . Ну расписывать о том как его использовать смысла нет - читайте perldoc perldebug, perldoc perldebtut.

Если же хочется большего удобства то можно воспользоваться более удобными дебагерами. Можно поставить EPIC под Eclipse. конечно использовать его только для дебага это почти как по воробьям из пушки, но если вы и пишите под Eclipse то вобщем искать альтернативу EPIC можно только в если дебагер под Eclipse не заработает, а такое бывает: вот например ссылка.

Если вы не хотите использовать Eclipse то может использовать гуёвый отладчик perl на базе Tk - Devel::ptkdb. Использовать так: perl -d:ptkdb program.pl . Внешний вид этого дебаггера на скриншоте ниже. В правой части окна есть три таба, "Exprs" для просмотра/вычисления значений переменных/выражений, "Subs" для просмотра функций и методов классов сгрупированный по package'ам, BrkPts для просмотра и управления точками останова. Обычно когда я хочу поставить breakpoint я действую так: на вкладке "Subs" нахожу функцию/метод и два раза кликаю на него, таким образом в левой части окна открывается исходник с этой функцией/методом, далее прокручиваю до нужной строки и ставлю breakpoint или несколько breakpoint'ов. Если что-то пошло не так, то я не закрывая ptkdb правлю исходник, затем жму Menu->Control->Restart и программа перезапускается с сохранёнными breakpoint'ами. Вобщем суммарно преимущества ptkdb над встроенным дабаггером велики.


Кроме этого есть ещё Komodo для windows и Affrus для Mac OS, но они платные и я их не использовал - так что ничего про них сказать не могу.

пятница, октября 12, 2007

Линуксовое

Линукс линуксу всё же рознь. Я уже проклял тот день когда решил поставить fedor'у, т.к. под рукой больше ничего не оказалось. Сначала всё более-менее радовало - и gprs+bluetooth запахали относительно быстро и работало стабильно всё поначалу. Потом начали появляться неприятные мелочи: конфиг апача по сравнению с debian/ubuntu сделан просто отвратно, в один прекрасный момент сеть померла - т.е. рестарт карточки не помогал, ожила только после перезагрузки.

Но больше всего бесило зависание и тупёж yum'а. После установки Yum Extender'а и Smart Package Manager'а тупежа стало меньше, но стабильности не прибавилось. Для тех кто столкнётся с помиранием yum'а "навсигда" и прекращения подавания им признаков жизни(у меня он обычно вис на futex'е если strace'ом смотреть) привожу рецепт его оживления который мне помогал, удаление старых баз и ребилд:
rm -f /var/lib/rpm/__db*
rpm -vv --rebuilddb
Потом можно ещё yum clean all на всякий случай.

вторник, сентября 25, 2007

Отладка GWT + CGI скрипт

Так как я недавно окончательно перешёл на linux, то столкнулся с некоторыми проблемами которых не было под Windows... Например под Windows в эклипсе я запросто отлаживал кроссдоменные ajax вызовы. В случае с cgi скриптами они в типичных случаях(если ничего не предпринимать) всегда кроссдоменные в режиме отладки, потому что gwt shell грузится на localhost:8888 а cgi скрипты соответственно лежат где-то ещё. Но т.к. IE (возможно только при определённых настройках - не помню менял ли я там что-то) наплевать на кроссдоменные ajax-вызовы, то под Windows всё это работало замечательно (только предупреждение вроде выскакивало и всё). Да там были другие проблемы (например с кэшом ) но всё работало так или иначе.

Под linux же я получал такой Exception: "The URL http://myhost.ru/script.cgi?param1=1is invalid or violates the same-origin security restriction". Сначала я попытался "хакнуть" firefox чтобы отключить это ограничение, но из этого у меня ничего не вышло - всё равно получал то же самое. Немного переформулировав запрос гуглу я нашёл-таки решение и даже целых два.

Решение первое было такое: поднимаем локально апач по 8889 порту, и проксируем все запросы к cgi скриптам на нужный нам хост(в примере ниже на локалхост на 80 порту) а остальные запросы на gwt shell висящий на 8888 порту локалхоста. Т.е. у меня получился примерно следующий конфиг для виртуального хоста:
<VirtualHost localhost:8889>
ProxyRequests Off
<proxy>
Order deny,allow
Allow from all
</proxy>
#redirect to my cgi application on external server
ProxyPass /cgi http://localhost:80/cgi-bin
ProxyPassReverse /cgi http://localhost:80/cgi-bin
ProxyPass / http://localhost:8888/
ProxyPassReverse / http://localhost:8888/

</VirtualHost>
Далее при запуске в hosted mode надо просто поменять в адрессной строке localhost:8888 на localhost:8889 и всё будет работать.

Да всё это работает если у вас нету хитрых mod_rewrit'ов и редиректов - иначе возможно придётся поколдовать ещё... Но в целом для отладки метод годится и работает.

Второй метод я проверять не стал, но вкратце он заключается в использовании org.apache.catalina.servlets.CGIServlet для запуска cgi-скриптов. Подробнее см. по ссылке на источник.

Источник: http://groups.google.ru/group/...

воскресенье, сентября 16, 2007

CPAN баги и LWP

Сейчас стало модно говорить о пришествии Perl6 и о всяких его вкусностях. Это всё конечно интересно, но здесь и сейчас большинству приходится писать на пятёрке.

Вот довелось мне недавно опять использовать LWP. Я его не очень люблю, но учитывая весьма куцый Curl под perl который вобщем в заброшенном состоянии, выбор вобщем не велик... Ну зачитал я доку по LWP::UserAgent где написано следующее:
$ua = LWP::UserAgent->new( %options )

This method constructs a new LWP::UserAgent object and returns it. Key/value pair arguments may be provided to set up the initial state. The following options correspond to attribute methods described below:

   KEY                     DEFAULT
----------- --------------------
agent "libwww-perl/#.##"
from undef
conn_cache undef
cookie_jar undef

default_headers HTTP::Headers->new

max_size undef
max_redirect 7
parse_head 1
protocols_allowed undef
protocols_forbidden undef
requests_redirectable ['GET', 'HEAD']
timeout 180

The following additional options are also accepted: If the env_proxy option is passed in with a TRUE value, then proxy settings are read from environment variables (see env_proxy() method below). If the keep_alive option is passed in, then a LWP::ConnCache is set up (see conn_cache() method below). The keep_alive value is passed on as the total_capacity for the connection cache.


Я специально выделил default_headers. Так вот передавать в конструктор этот параметр бесполезно - он просто никак там не обрабатывается. Убив на поиск этого неочевидного бага несколько часов, я пошёл о нём рапортовать. И нашёл его в баглисте, добавлен он был в декабре аж 2005 года: http://rt.cpan.org/Public/Bug/Display.html?id=16637 . Может автор забросил это дело вообще, подумал я и глянул на resolved bugs . Нет не забросил - всего пять недель назад баги активно правились. Вобщем не понять мне почему не исправить эту ошибку(решение - дело пары строчек), которая прямо скажем много вреда наносит (особенно если "тут работает, а там не работает")...

Такие вот тяжёлые будни, а вы говорите Perl6... Будем верить в лучшее. :)

среда, августа 29, 2007

Не все индексы полезны...

Прочитал я тут, что оказывается в таком с виду простом случае, как создания индекса для поля с булевыми значениями есть нюансы... А именно если выборка (select ... from ... where field_with_bool=1) содержит более 20% строк таблицы то с индексом она будет медленнее чем без него. Причём медленнее в несколько раз на больших объёмах.

Подробнее(с тестами) на MySQL Perfomance Blog.

А всё из-за оверхеда на операции с индексом. Век живи - век учись.

четверг, августа 09, 2007

Мощь Perl

Наткнулся в комментах ru_perl на такое:
Достаточно BEGIN { no strict 'refs'; *{'B::g'} = \&{'A::f'}; } в любом месте исходника и функция A::f(...) становится видна в пакете B как g(...). Возможность такого копирования функций тоже предлагаете описывать? И все тонкости, которые при этом происходят?

Или, например, то что "no strict 'refs'; local *{'B::g'} = *{'B::g'} ; *{'B::g'} = \&{'A::f'};"
делает все обращения к B::g(...) обращениями к A::f(...) в текущем блоке?
Тут в принципе добавить нечего, при всех минусах perl'а есть вещи которые иногда радуют (даже если видишь их уже не в первый раз). И даже если в реальности такое я не использую, но наличие самой возможности(а вдруг завтра война? :) ) греет душу.

вторник, июля 24, 2007

Как определить гуглбота

Matt Cutts упомянул эту заметку: how-to-verify-googlebot


Вкратце некий спец из гугла предлагает не просто использовать reverse dns lookup, а reverse dns lookup(получаем имя хоста вида crawl-a-b-c-d.googlebot.com) + forward dns lookup для имени хоста полученном на первом шаге и сравнение ip с исходным . Таким образом можно защитится от спуфинга.

четверг, апреля 05, 2007

LWP и задание ip client'а.

Задача: браузить из под perl с помощью LWP но с разными ip клиента задаваемыми определённым образом.


Для тех кто хочет просто забиндить ip можно не напрягаться и просто написать где нужно: @LWP::Protocol::http::EXTRA_SOCK_OPTS = (LocalAddr => $ip);. Тем же кому нужно некое централизованное решение надо сделать "нечто совсем другое" (c).

Поиском вышел на эту статью и быстренько слабал имплементоры для http и https на базе LWP::Protocol::http и LWP::Protocol::https, вот их схематичная реализация:


package myIp;

use strict;
use warnings;
use vars qw( @ISA $VERSION );
use IO::Socket::Socks;
use LWP::Protocol::http;

@ISA = qw( LWP::Protocol::http );

$VERSION = 0.01;
our $MAX_CONNECT_ATTEMPTS = 1;

sub _new_socket {
my ( $self, $host, $port, $timeout ) = @_;
my $conn_cache = $self->{ua}{conn_cache};
if ($conn_cache) {
if ( my $sock = $conn_cache->withdraw( "http", "$host:$port" ) ) {
return $sock if $sock && !$sock->can_read(0);

# if the socket is readable, then either the peer has closed the
# connection or there are some garbage bytes on it. In either
# case we abandon it.
$sock->close;
}
}

local ($^W) = 0; # IO::Socket::INET can be noisy
my $sock = $self->getSocket( $host, $port );

unless ($sock) {

# IO::Socket::INET leaves additional error messages in $@
die "Can't connect to $host:$port ($@)";
}

# perl 5.005's IO::Socket does not have the blocking method.
eval { $sock->blocking(0); };
$sock;
}

sub getSocket
{
my($self, $host, $port, $level) = @_;

if (!defined($level))
{
$level = 0;
}

my $socket = $self->socket_class->new(
PeerPort => $port,
PeerAddr => $host,
Proto => 'tcp',
LocalAddr => 'XX.XX.XX.XX',
Timeout => '5'
);

if (!$socket && ($level < $MAX_CONNECT_ATTEMPTS)) { $socket = $self->getSocket($host, $port, $level + 1);
}

return $socket;
}

#-----------------------------------------------------------
package myIp::Socket;

use strict;
use warnings;
use vars qw( @ISA );

@ISA = qw(LWP::Protocol::http::SocketMethods Net::HTTP::Methods IO::Socket::INET);

sub configure {
my ($self, $cnf) = @_;
$self->http_configure($cnf);
}
sub http_connect {
my ($self, $cnf) = @_;
$self->SUPER::configure($cnf);
}

1;



package myIpHttps;

use strict;
use warnings;
use vars qw( @ISA $VERSION );
use IO::Socket::Socks;
use LWP::Protocol::https;

@ISA = qw( myIp );

$VERSION = 0.01;
sub _check_sock {
my ( $self, $req, $sock ) = @_;
my $check = $req->header("If-SSL-Cert-Subject");
if ( defined $check ) {
my $cert = $sock->get_peer_certificate
|| die "Missing SSL certificate";
my $subject = $cert->subject_name;
die "Bad SSL certificate subject: '$subject' !~ /$check/"
unless $subject =~ /$check/;
$req->remove_header("If-SSL-Cert-Subject"); # don't pass it on
}
}

sub _get_sock_info {
my $self = shift;
$self->SUPER::_get_sock_info(@_);
my ( $res, $sock ) = @_;
$res->header( "Client-SSL-Cipher" => $sock->get_cipher );
my $cert = $sock->get_peer_certificate;
if ($cert) {
$res->header( "Client-SSL-Cert-Subject" => $cert->subject_name );
$res->header( "Client-SSL-Cert-Issuer" => $cert->issuer_name );
}
if ( !eval { $sock->get_peer_verify } ) {
$res->header( "Client-SSL-Warning" => "Peer certificate not verified" );
}
}

#-----------------------------------------------------------
package myIpHttps::Socket;

use vars qw(@ISA);
require Net::HTTPS;
@ISA = qw(Net::HTTPS LWP::Protocol::http::SocketMethods);

1;


В http для LocalAddr воткнул заглушку 'XX.XX.XX.XX' ну туда можно(и нужно) воткнуть вызов функции необходимый для ваших задач. Https это копия LWP::Protocol::https только унаследованная от myIp.

Комменты я оставил родные от LWP::Protocol::*, так проще ориентироваться где и что поменяли по сравнению с первоисточником. Естественно в зависимости от задач модифицировать придётся больше, это же просто демонстрационный пример.

P.S. В итоге от LWP всё равно отказался. Убог местами для сложных задач.

вторник, марта 27, 2007

Кэш в gwt в hosted mode

Наверное многие из тех кто использовал сервлеты под GWT в hosted mode сталкивались со странностью: после первого вызова сервлета, данные которые он отдавал как бы кешировались (при неизменности параметров вызова естественно).

А всё из-за того что в IE в настройках "Temporary Internet Files" галочка стоит не там. Надо поставить так чтобы проверка на новую версию страницы была при каждом визите на страницу и баг пропадёт.

P.S. Вроде всё очевидно, но однако я уже второй раз нарвался и потратил время ища баг не там. Так что может кому пригодится.