Как локализовать объект, который находится внутри свойства объекта Moo в Perl?
У меня есть объект, который хранит LWP::UserAgent. Я хочу использовать разные банки cookie для разных вызовов с этим UA, поэтому я решил сделать cookie_jar
local
при звонке.
Следующий код показывает, что я сделал без отладочной информации (для чтения, не работает). Ниже приведена еще одна версия с большим количеством результатов отладки.
package Foo;
use strictures;
use Moo;
use LWP::UserAgent;
has ua => (
is => 'ro',
default => sub { my $ua = LWP::UserAgent->new; $ua->cookie_jar( {} ); return $ua; },
);
sub request {
my ($self, $cookie_jar) = @_;
local $self->{ua}->{cookie_jar} = $cookie_jar;
$self->ua->get('http://www.stackru.com');
}
package main;
my $foo = Foo->new;
my $new_jar = HTTP::Cookies->new;
$foo->request( $new_jar );
Поэтому я решил локально переписать банку с печеньем. К сожалению, когда мы звоним get
он по-прежнему будет использовать банку печенья, которая изначально находится внутри объекта UA.
package Foo;
use strictures;
use Moo;
use LWP::UserAgent;
use HTTP::Cookies;
use Data::Printer;
use feature 'say';
has ua => (
is => 'ro',
default => sub { my $ua = LWP::UserAgent->new; $ua->cookie_jar( {} ); return $ua; },
);
sub request {
my ($self, $cookie_jar) = @_;
say "before local " . $self->{ua}->{cookie_jar};
local $self->{ua}->{cookie_jar} = $cookie_jar;
$self->ua->get('http://www.stackru.com');
print "local jar " . p $self->{ua}->{cookie_jar};
say "after local " . $self->{ua}->{cookie_jar};
}
package main;
use Data::Printer;
use HTTP::Cookies;
my $foo = Foo->new;
say "before outside of local " . $foo->{ua}->{cookie_jar};
my $new_jar = HTTP::Cookies->new;
say "before outside of local " . $new_jar;
$foo->request( $new_jar );
say "after outside of local " . $foo->{ua}->{cookie_jar};
print "global jar " . p $foo->ua->cookie_jar;
__END__
before outside of local HTTP::Cookies=HASH(0x30e1848)
before outside of local HTTP::Cookies=HASH(0x30e3b20)
before local HTTP::Cookies=HASH(0x30e1848)
local jar HTTP::Cookies {
public methods (13) : add_cookie_header, as_string, clear, clear_temporary_cookies, DESTROY, extract_cookies, load, new, revert, save, scan, set_cookie, set_cookie_ok
private methods (3) : _host, _normalize_path, _url_path
internals: {
COOKIES {}
}
}after local HTTP::Cookies=HASH(0x30e3b20)
after outside of local HTTP::Cookies=HASH(0x30e1848)
global jar HTTP::Cookies {
public methods (13) : add_cookie_header, as_string, clear, clear_temporary_cookies, DESTROY, extract_cookies, load, new, revert, save, scan, set_cookie, set_cookie_ok
private methods (3) : _host, _normalize_path, _url_path
internals: {
COOKIES {
stackru.com {
/ {
prov [
[0] 0,
[1] "185e95c6-a7f4-419a-8802-42394776ef63",
[2] undef,
[3] 1,
[4] undef,
[5] 2682374400,
[6] undef,
[7] {
HttpOnly undef
}
]
}
}
}
}
}
Как видите, объект HTTP::Cookies локализуется и заменяется правильно. Адреса выглядят абсолютно корректно.
Но вывод p
рассказывает другую историю. LWP::UA не использовал local
баночка печенья вообще. Это остается новым, пустым.
Как я могу сделать это с помощью local
один вместо?
Я пробовал использовать Moo, Moose и classic bless
объекты. Все показывают это поведение.
Изменить: так как это появилось в комментариях, позвольте мне дать немного больше информации, почему мне нужно это сделать. Это будет немного напыщенно.
TLDR: почему я не хочу альтернативного решения, но понимаю и решаю проблему
Я создаю основанное на Dancer2 веб-приложение, которое будет работать с Plack и несколькими рабочими ( Twiggy:: Prefork - несколько потоков в нескольких форках). Это позволит пользователям пользоваться услугой третьей компании. Эта компания предлагает веб-сервис SOAP. Думайте о моем приложении как о пользовательском интерфейсе этого сервиса. В веб-сервисе есть звонок для входа в систему. Он возвращает cookie (sessionid) для этого конкретного пользователя, и нам нужно передавать этот cookie при каждом последующем вызове.
Для SOAP-вещей я использую XML::Compile::WSDL11. Компиляция является довольно дорогостоящей, поэтому я не хочу делать это каждый раз, когда обрабатывается маршрут. Это было бы неэффективно. Таким образом, клиент SOAP будет скомпилирован из файла WSDL при запуске приложения. Затем он будет распространен среди всех работников.
Если клиентский объект является общим, пользовательский агент внутри также является общим. Как и баночка с печеньем. Это означает, что если есть два запроса одновременно, сессионные идентификаторы могут быть перепутаны. Приложение может в конечном итоге отправлять неправильные вещи пользователям.
Вот почему я решил локализовать банку печенья. Если он является локальным уникальным для запроса, он никогда не сможет вмешиваться в запрос другого работника, который происходит параллельно. Просто создание новой банки с печеньем для каждого запроса не приведет к ее сокращению. Они все равно будут переданы, и могут даже потеряться, потому что они перезапишут друг друга в худшем случае.
Другой подход заключается в реализации механизма блокировки, но это полностью превзойдет цель наличия нескольких работников.
Единственное другое решение, которое я вижу, - это использование другого SOAP-клиента. Существует SOAP::WSDL, который не работает на более новых Perls. по словам тестеров CPAN, он выходит из строя на 5.18, и я проверил это. Это было бы более эффективно, так как он работает как генератор кода и создает классы, которые дешевле использовать, чем просто компилировать файл WSDL каждый раз. Но так как это сломано, это вне вопроса.
SOAP:: Lite скомпилирует WSDL, и плохо. Это не то, что кто-то должен использовать в производстве, если этого можно избежать, по моему мнению. Единственная оставшаяся альтернатива, которую я вижу, это реализовать вызовы без использования файла WSDL и синтаксического анализа результатов с помощью анализатора XML, игнорируя схему. Но это БОЛЬШИЕ результаты. Это было бы очень неудобно.
Я пришел к выводу, что я хотел бы понять, почему Perl не хочет в этом случае локализовать банку печенья и исправить это.
1 ответ
Возможно, вместо того, чтобы использовать local
вы используете clone
а также cookie_jar
методы LWP::UserAgent
,
...
sub request {
my ($self, $new_cookie_jar) = @_;
my $ua = $self->ua; # cache user agent
if( defined $new_cookie_jar ){
# create a new user agent with the new cookie jar
$ua = $ua->clone;
$ua->cookie_jar( $new_cookie_jar );
}
my $result = $ua->get('http://www.stackru.com');
# allow returning the newly cloned user agent
return ( $result, $ua ) if wantarray;
return $result;
}
Если вы не хотите этого делать, вы должны по крайней мере использовать методы вместо манипулирования внутренними объектами.
...
sub request {
my ($self, $new_cookie_jar) = @_;
my $ua = $self->ua; # cache user agent
my $old_cookie_jar = $ua->cookie_jar( $new_cookie_jar );
my $result = $ua->get('http://www.stackru.com');
# put the old cookie jar back in place
$ua->cookie_jar( $old_cookie_jar );
return $result;
}