S3.Blog

19 Марта 2024
A A A   RSS-лента
"Я знаю, что ничего не знаю, но многие не знают и этого". Сократ [?].

Perl: Wrapper для работы с HTTP-запросами

Дата последнего изменения: 28 Мая 2010
Метки статьи: Готовые решения, Perl
Если у вас в коде предполагается делать не единичные http-запросы, то наверняка вы создаете отдельный метод для этого, да бы исключить повторений в коде. Вот два моих решения, основанные на LWP и AnyEvent::HTTP
 

В обоих случаях обращение к методам одинаковое:
my $response = $ua->request({
	url	=> $url,	# http или https ссылка
	url_ref => $url_referer,# referer ссылка
	user_agent => $user_agent, # имя браузера
	cookie	=> $cookie,	# сохраненные ранее cookie
	get_cookie => 1,	# 1 - сохранить полученные cookie, 0 - не сохранять
	method	=> 'GET',	# метод запроса: GET или POST
form => \@form, # данные формы, массив (var1, value1, var2, value2,...) go_sub_callback => sub {...}, # только для варианта AnyEvent::HTTP });

LWP версия

LWP довольно-таки хорошо распространен и широко известен, с ним приятно работать, но есть у него один недостаток, подробнее смотрите ниже в Итогах
Вот сам класс, ua_lwp.pm
package lib::ua_lwp;
use strict;

use LWP::UserAgent;
use HTTP::Cookies;
use Net::SSLeay qw(make_form);

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
sub new { my $class = shift; my $cfg = shift; my $self = bless ({}, $class); $self->{max_redirect} = 5; # максимальное количество перенаправлений return $self; } #-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
sub request { my $self = shift; my $param = shift; my $redirect_count = shift || 0; my $ua = new LWP::UserAgent; $ua->agent($param->{user_agent}); $ua->max_redirect( 0 ); $ua->timeout(30); $param->{method} = 'GET' unless (exists($param->{method}) && $param->{method}); my $form = ''; if ($param->{form} && scalar(@{$param->{form}})>0) { $form = make_form(@{$param->{form}}); } if (($param->{method} eq 'GET') && ($form ne '')) { $param->{url} =~ s/\?$//; if ($param->{url} =~ /\?/) { $param->{url} .= '&' . $form; } else { $param->{url} .= '?' . $form; } } my $req = HTTP::Request->new($param->{method} => $param->{url}); $req->referer($param->{url_ref}); if (($param->{method} eq 'POST') && ($form ne '')) { $req->content($form); $req->content_type('application/x-www-form-urlencoded'); } if ($param->{cookie}) { $param->{cookie}->add_cookie_header($req); } my $response = $ua->request($req); if ($param->{get_cookie}) { unless (exists($param->{cookie}) && $param->{cookie}) { $param->{cookie} = new HTTP::Cookies(); } $param->{cookie}->extract_cookies($res); } $response->{url_ref} = $param->{url}; #--------------------------------------------------------------------------- my $redirect = $response->header('Location') || ''; if ($redirect) { $redirect_count++; if ($redirect_count <= $self->{max_redirect}) { if ($redirect !~ /^https?\:\/\//) { if ($redirect =~ /^\//) { my ($http) = $response->{url_ref} =~ m/^(https?\:\/\/[^\/]+)/; $redirect = $http . $redirect; } else { my ($http) = $response->{url_ref} =~ m/^([^\?]+)/; $redirect = $http . $redirect; } } $response = $self->request({ url => $redirect, url_ref => $param->{url}, user_agent => $param->{user_agent}, cookie => $param->{cookie}, get_cookie => $param->{get_cookie}, method => 'GET', }, $redirect_count); } } $response->{cookie} = $param->{cookie}; return $response; } #-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
1;

В итоге в $response будут методы LWP (->content(), ->is_success() и т.д.) плюс сохраненные ->{cookie} в виде объекта HTTP::Cookies


AnyEvent::HTTP версия

AnyEvent - это событийно-ориентированный фреймворк. Про него я уже упоминал тут: Мини экскурс в AnyEvent - пишем паука, правда статья не моя, а нагло взята на просторах интернета, но всё описано толково. Единственный минус у AnyEvent, это то что он не работает с HTTP::Cookies, а предложенный механизм cookie_jar не совсем выполняет свои функции, поэтому пришлось снабдить код еще своим парсером кук.
package lib::ua_anyevent;
use strict;

use Net::SSLeay qw(make_form);
use Date::Parse;
use AnyEvent::HTTP;

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
sub new { my $class = shift; my $cfg = shift; my $self = bless ({}, $class); $self->{max_redirect} = 5; $self->{AnyEventCV} = AnyEvent->condvar; return $self; } #-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
sub request { my $self = shift; my $param = shift; $param->{redirect_count} = shift || 0; $param->{cookie} ||= ''; my $cookie = ''; while ($param->{cookie} =~ /([^\;]+)/g) { my $cc = $1; my ($c, $d) = split(/\|\|\|\|\|/, $cc); my ($d2) = $param->{url} =~ m/^https?\:\/\/([^\/]+)/; $d2 = '.' . $d2; $cookie .= $c . ';' if ($d && ($d2 =~ /$d/i)); $cookie .= $c . ';' unless ($d); } my $ua = { timeout => 30, recurse => 0, headers => { 'Referer' => $param->{url_ref}, 'User-Agent' => $param->{user_agent}, 'Cookie' => $cookie, }, }; if ($param->{form} && scalar(@{$param->{form}})>0) { $ua->{body} = make_form(@{$param->{form}}); $ua->{headers}->{'Content-Type'} = 'application/x-www-form-urlencoded'; } AnyEvent::HTTP::http_request( $param->{method} => $param->{url}, %{$ua}, sub { $self->request_done($param, @_); } ); return 1; } #-------------------------------------------------------------------------------
sub request_done { my $self = shift; my $param = shift; my ($body, $header) = @_; $body ||= ''; my $response = { url_ref => $param->{url}, content => $body, }; if ($param->{get_cookie}) { $header->{'set-cookie'} ||= ''; $header->{'set-cookie'} =~ s/(\,\s+)/\a/g; my ($domain) = $param->{url} =~ m/^https?\:\/\/([^\/]+)/; my (@c2) = split(/\;/, $param->{cookie}); my (@c1) = split(/\,/, $header->{'set-cookie'}); my %cc = (); my @c3 = (); foreach my $c (@c1, @c2) { $c =~ s/\a/\, /g; my ($v, $n) = $c =~ m/^([^\=]+)\=([^;]+)/; $n ||= ''; $n =~ s/^(.*)\|\|\|\|\|.*$/$1/g; my ($d) = $c =~ m/Domain=([^;]+)/i; ($d) = $c =~ m/\|\|\|\|\|(.*)/i unless ($d); $d ||= $domain; next unless $v; unless (exists($cc{$v . '-' . $d}) && $cc{$v . '-' . $d}) { my ($time) = $c =~ /Expires=([^;]+)/; $time = str2time($time) if ($time); if (!$time || ($time > time())) { $c = $v . '=' . $n . '|||||' . $d; push(@c3, $c); $cc{$v . '-' . $d} = 1; } } } $param->{cookie} = join(';', @c3); } #--------------------------------------------------------------------------- my $redirect = $header->{'location'} || ''; my $is_redirect = 0; if ($redirect) { $param->{redirect_count}++; if ($param->{redirect_count} <= $self->{max_redirect}) { if ($redirect !~ /^https?\:\/\//) { if ($redirect =~ /^\//) { my ($http) = $param->{url} =~ m/^(https?\:\/\/[^\/]+)/; $redirect = $http . $redirect; } else { my ($http) = $param->{url} =~ m/^([^\?]+)/; $redirect = $http . $redirect; } } $is_redirect = 1; $self->request({ url => $redirect, url_ref => $param->{url}, user_agent => $param->{user_agent}, cookie => $param->{cookie}, get_cookie => $param->{get_cookie}, method => 'GET', go_sub_callback => $param->{go_sub_callback}, }, $param->{redirect_count}); } } unless ($is_redirect) { $response->{cookie} = $param->{cookie}; $param->{go_sub_callback}($response); } return 1; } #-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
1;
После своей работы метод возвращает в $response хэш массив:
$response = {
	url_ref => 'http://....',	# http-ссылка куда был сделан последний запрос 
	content => '<html><body>....',	# полученная страница (аналогично LWP->content()) 
	cookie => '...',		# список cookie ввиде текста. Cookie разделены точкой с запятой ";"
};

Итого

Буду краток - оба варианта имеют право на жизнь, но если вам предстоит делать многочисленные запросы, то присмотритесь к AnyEvent, так как согласно проведенным мной тестам при многопоточных http-запросах LWP+threads намного отстает от AnyEvent::HTTP

Вот краткая статистика:

LWP + threads
запрос на http://yandex.ru/
количество запросов: 1000
максимальное количество потоков: 50
время работы программы: 73 секунды

AnyEvent::HTTP
запрос на http://yandex.ru/
количество запросов: 1000
максимальное количество потоков: 50
время работы программы: 32 секунды



Похожие материалы:




 
  Имя *:   Решите пример *: =
 
Полужирный Курсив Подчеркнутый Перечеркнутый
 
Вставить изображение Сделать цитатой Вставить ссылку Вставить код

Вставить смайл
 
 

 



© S3.Blog: Если критикуешь, не предлагая решения проблемы, то ты становишься частью этой проблемы.