如何在Perl中本地化Moo对象属性内的对象?

时间:2014-07-27 22:28:58

标签: perl cookies dancer lwp-useragent plack

我有一个存储LWP :: UserAgent的对象。我想对使用该UA的不同呼叫使用不同的cookie jar,因此我决定在拨打电话时设置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.stackoverflow.com');
}

package main;
my $foo = Foo->new;
my $new_jar = HTTP::Cookies->new;
$foo->request( $new_jar );

所以基本上我决定在本地覆盖cookie jar。不幸的是,当我们调用get时,它仍将使用最初位于UA对象内的cookie jar。

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.stackoverflow.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   {
            stackoverflow.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 cookie罐。这仍然是一个新鲜,空洞的。

如何使用local代替?

我尝试过使用Moo,Moose和经典的bless对象。所有都表现出这种行为。


编辑:由于评论中提到了这一点,让我再详细说明为什么我需要这样做。这将是一个有点咆哮。

TLDR:为什么我不想要替代解决方案,但要理解并解决问题

我正在构建一个基于Dancer2的webapp,它将与Plack和多个worker(Twiggy::Prefork一起运行 - 多个forks中的多个线程)。它将允许用户使用第三家公司的服务。该公司提供SOAP Web服务。将我的应用程序视为此服务的自定义前端。在Web服务上调用“登录用户”。它返回该特定用户的cookie(sessionid),我们需要在每次连续调用时传递该cookie。

要做SOAP-stuff我正在使用XML :: Compile :: WSDL11。编译这个东西非常昂贵,所以每次处理路由时我都不想这样做。这样效率低下。因此,当应用程序启动时,将从WSDL文件编译SOAP客户端。然后由所有工人共享。

如果共享客户端对象,则也会共享内部的用户代理。饼干罐也是如此。这意味着如果同时有两个请求,则sessionid可能会混淆。该应用最终可能会向用户发送错误的内容。

这就是我决定本地化cookie罐的原因。如果它是请求的本地唯一的,它将永远不能干扰并行发生的另一个工作者的请求。只为每个请求制作一个新的饼干罐不会削减它。它们仍然会被共享,甚至可能会丢失,因为在最坏的情况下它们会相互覆盖。

另一种方法是实现锁定机制,但这完全超出了拥有多个工作人员的目的。

我看到的唯一其他解决方案是使用另一个SOAP客户端。有SOAP :: WSDL,它不能在较新的Perls上运行。根据CPAN测试人员的说法,它打破了5.18并且我已经验证了这一点。它会更有效,因为它像代码生成器一样工作并预先创建比使用每次编译WSDL文件更便宜的类。但是,由于它被打破了,这是不可能的。

SOAP :: Lite将编译WSDL,并且非常糟糕。如果在我看来可以避免,那么任何人都不应该在生产中使用它。我看到的唯一替代方法是在不使用WSDL文件的情况下实现调用,并直接使用XML解析器解析结果,忽略模式。但这些都是大结果。这将非常不方便。

我对这个咆哮的结论是,我真的很想理解为什么Perl不想在这种情况下本地化cookie jar并修复它。

1 个答案:

答案 0 :(得分:2)

也许您不是使用local而是使用LWP::UserAgentclonecookie_jar方法。

...

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.stackoverflow.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.stackoverflow.com');

    # put the old cookie jar back in place
    $ua->cookie_jar( $old_cookie_jar );

    return $result;
}