LWP::UserAgent 请求方法的真实超时

发布于 2024-07-05 11:20:04 字数 2606 浏览 10 评论 0原文

我正在尝试向不可靠的服务器发出请求。 该请求是一个很好的请求,但并不是 100% 需要我的 perl 脚本成功完成。 问题是服务器偶尔会死锁(我们试图找出原因)并且请求永远不会成功。 由于服务器认为它是活动的,因此它保持套接字连接打开,因此 LWP::UserAgent 的超时值对我们没有任何好处。 对请求强制执行绝对超时的最佳方法是什么?

仅供参考,这不是 DNS 问题。 死锁与大量更新同时到达我们的 Postgres 数据库有关。 出于测试目的,我们实际上在服务器响应处理程序中放置了 while(1) {} 行。

目前,代码如下所示:

my $ua = LWP::UserAgent->new;
ua->timeout(5); $ua->cookie_jar({});

my $req = HTTP::Request->new(POST => "http://$host:$port/auth/login");
$req->content_type('application/x-www-form-urlencoded');
$req->content("login[user]=$username&login[password]=$password");

# This line never returns 
$res = $ua->request($req);

我尝试使用信号来触发超时,但这似乎不起作用。

eval {
    local $SIG{ALRM} = sub { die "alarm\n" };
    alarm(1);
    $res = $ua->request($req);
    alarm(0);
};
# This never runs
print "here\n";

我要使用的最终答案是由线下某人提出的,但我会在这里提一下。 由于某种原因,SigAction 有效,而 $SIG(ALRM) 无效。 仍然不确定为什么,但这已经过测试可以工作。 这是两个工作版本:

# Takes a LWP::UserAgent, and a HTTP::Request, returns a HTTP::Request
sub ua_request_with_timeout {
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    use Sys::SigAction qw( timeout_call );
    our $res = undef;
    if( timeout_call( 5, sub {$res = $ua->request($req);}) ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}
sub ua_request_with_timeout2 {
    print "ua_request_with_timeout\n";
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    my $timeout_for_client = $ua->timeout() - 2;
    our $socket_has_timedout = 0;

    use POSIX;
    sigaction SIGALRM, new POSIX::SigAction(
                                            sub {
                                                $socket_has_timedout = 1;
                                                die "alarm timeout";
                                            }
                                            ) or die "Error setting SIGALRM handler: $!\n";
    my $res = undef;
    eval {
        alarm ($timeout_for_client);
        $res = $ua->request($req);
        alarm(0);
    };
    if ( $socket_has_timedout ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}

I am trying to implement a request to an unreliable server. The request is a nice to have, but not 100% required for my perl script to successfully complete. The problem is that the server will occasionally deadlock (we're trying to figure out why) and the request will never succeed. Since the server thinks it is live, it keeps the socket connection open thus LWP::UserAgent's timeout value does us no good what-so-ever. What is the best way to enforce an absolute timeout on a request?

FYI, this is not an DNS problem. The deadlock has something to do with a massive number of updates hitting our Postgres database at the same time. For testing purposes, we've essentially put a while(1) {} line in the servers response handler.

Currently, the code looks like so:

my $ua = LWP::UserAgent->new;
ua->timeout(5); $ua->cookie_jar({});

my $req = HTTP::Request->new(POST => "http://$host:$port/auth/login");
$req->content_type('application/x-www-form-urlencoded');
$req->content("login[user]=$username&login[password]=$password");

# This line never returns 
$res = $ua->request($req);

I've tried using signals to trigger a timeout, but that does not seem to work.

eval {
    local $SIG{ALRM} = sub { die "alarm\n" };
    alarm(1);
    $res = $ua->request($req);
    alarm(0);
};
# This never runs
print "here\n";

The final answer I'm going to use was proposed by someone offline, but I'll mention it here. For some reason, SigAction works while $SIG(ALRM) does not. Still not sure why, but this has been tested to work. Here are two working versions:

# Takes a LWP::UserAgent, and a HTTP::Request, returns a HTTP::Request
sub ua_request_with_timeout {
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    use Sys::SigAction qw( timeout_call );
    our $res = undef;
    if( timeout_call( 5, sub {$res = $ua->request($req);}) ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}
sub ua_request_with_timeout2 {
    print "ua_request_with_timeout\n";
    my $ua = $_[0];
    my $req = $_[1];
    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)
    my $timeout_for_client = $ua->timeout() - 2;
    our $socket_has_timedout = 0;

    use POSIX;
    sigaction SIGALRM, new POSIX::SigAction(
                                            sub {
                                                $socket_has_timedout = 1;
                                                die "alarm timeout";
                                            }
                                            ) or die "Error setting SIGALRM handler: $!\n";
    my $res = undef;
    eval {
        alarm ($timeout_for_client);
        $res = $ua->request($req);
        alarm(0);
    };
    if ( $socket_has_timedout ) {
        return HTTP::Response->new( 408 ); #408 is the HTTP timeout
    } else {
        return $res;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

私野 2024-07-12 11:20:04

据我了解,超时属性不考虑 DNS 超时。 您可以单独进行 DNS 查找,然后向服务器发出请求(如果可行的话),并为用户代理设置正确的超时值。

这是服务器的 DNS 问题还是其他问题?

编辑:这也可能是 IO::Socket 的问题。 尝试更新您的 IO::Socket 模块,看看是否有帮助。 我很确定其中存在一个错误,导致 LWP::UserAgent 超时无法正常工作。

亚历克斯

From what I understand, the timeout property doesn't take into account DNS timeouts. It's possible that you could make a DNS lookup separately, then make the request to the server if that works, with the correct timeout value set for the useragent.

Is this a DNS problem with the server, or something else?

EDIT: It could also be a problem with IO::Socket. Try updating your IO::Socket module, and see if that helps. I'm pretty sure there was a bug in there that was preventing LWP::UserAgent timeouts from working.

Alex

少女净妖师 2024-07-12 11:20:04

以下对原始答案之一的概括还将警报信号处理程序恢复到前一个处理程序,并添加对alarm(0)的第二次调用,以防评估时钟中的调用抛出非警报异常并且我们想要取消警报。 可以添加进一步的$@检查和处理:

sub ua_request_with_timeout {
    my $ua = $_[0];
    my $request = $_[1];

    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)`enter code here`
    my $timeout_for_client_sec = $ua->timeout();
    our $res_has_timedout = 0; 

    use POSIX ':signal_h';

    my $newaction = POSIX::SigAction->new(
        sub { $res_has_timedout = 1; die "web request timeout"; },# the handler code ref
        POSIX::SigSet->new(SIGALRM),
        # not using (perl 5.8.2 and later) 'safe' switch or sa_flags
    );  

    my $oldaction = POSIX::SigAction->new();
    if(!sigaction(SIGALRM, $newaction, $oldaction)) {
        log('warn',"Error setting SIGALRM handler: $!");
        return $ua->request($request);
    }   

    my $response = undef;
    eval {
        alarm ($timeout_for_client_sec);
        $response = $ua->request($request);
        alarm(0);
    }; 

    alarm(0);# cancel alarm (if eval failed because of non alarm cause)
    if(!sigaction(SIGALRM, $oldaction )) {
        log('warn', "Error resetting SIGALRM handler: $!");
    }; 

    if ( $res_has_timedout ) {
        log('warn', "Timeout($timeout_for_client_sec sec) while waiting for a response from cred central");
        return HTTP::Response->new(408); #408 is the HTTP timeout
    } else {
        return $response;
     }
}

The following generalization of one of the original answers also restores the alarm signal handler to the previous handler and adds a second call to alarm(0) in case the call in the eval clock throws a non alarm exception and we want to cancel the alarm. Further $@ inspection and handling can be added:

sub ua_request_with_timeout {
    my $ua = $_[0];
    my $request = $_[1];

    # Get whatever timeout is set for LWP and use that to 
    #  enforce a maximum timeout per request in case of server
    #  deadlock. (This has happened.)`enter code here`
    my $timeout_for_client_sec = $ua->timeout();
    our $res_has_timedout = 0; 

    use POSIX ':signal_h';

    my $newaction = POSIX::SigAction->new(
        sub { $res_has_timedout = 1; die "web request timeout"; },# the handler code ref
        POSIX::SigSet->new(SIGALRM),
        # not using (perl 5.8.2 and later) 'safe' switch or sa_flags
    );  

    my $oldaction = POSIX::SigAction->new();
    if(!sigaction(SIGALRM, $newaction, $oldaction)) {
        log('warn',"Error setting SIGALRM handler: $!");
        return $ua->request($request);
    }   

    my $response = undef;
    eval {
        alarm ($timeout_for_client_sec);
        $response = $ua->request($request);
        alarm(0);
    }; 

    alarm(0);# cancel alarm (if eval failed because of non alarm cause)
    if(!sigaction(SIGALRM, $oldaction )) {
        log('warn', "Error resetting SIGALRM handler: $!");
    }; 

    if ( $res_has_timedout ) {
        log('warn', "Timeout($timeout_for_client_sec sec) while waiting for a response from cred central");
        return HTTP::Response->new(408); #408 is the HTTP timeout
    } else {
        return $response;
     }
}
寒冷纷飞旳雪 2024-07-12 11:20:04

您可以像这样设置自己的超时:

use LWP::UserAgent;
use IO::Pipe;

my $agent = new LWP::UserAgent;

my $finished = 0;
my $timeout = 5;

$SIG{CHLD} = sub { wait, $finished = 1 };

my $pipe = new IO::Pipe;
my $pid = fork;

if($pid == 0) {
    $pipe->writer;
    my $response = $agent->get("http://stackoverflow.com/");
    $pipe->print($response->content);
    exit;
}

$pipe->reader;

sleep($timeout);

if($finished) {
    print "Finished!\n";
    my $content = join('', $pipe->getlines);
}   
else {
    kill(9, $pid);
    print "Timed out.\n";
}   

You can make your own timeout like this:

use LWP::UserAgent;
use IO::Pipe;

my $agent = new LWP::UserAgent;

my $finished = 0;
my $timeout = 5;

$SIG{CHLD} = sub { wait, $finished = 1 };

my $pipe = new IO::Pipe;
my $pid = fork;

if($pid == 0) {
    $pipe->writer;
    my $response = $agent->get("http://stackoverflow.com/");
    $pipe->print($response->content);
    exit;
}

$pipe->reader;

sleep($timeout);

if($finished) {
    print "Finished!\n";
    my $content = join('', $pipe->getlines);
}   
else {
    kill(9, $pid);
    print "Timed out.\n";
}   
☆獨立☆ 2024-07-12 11:20:04

您可以尝试 LWPx::ParanoidAgent, LWP::UserAgent 的子类,它对于如何与远程 Web 服务器交互更加谨慎。

除此之外,它还允许您指定全局超时。 它是由 Brad Fitzpatrick 开发的,作为 LiveJournal 项目的一部分。

You might try LWPx::ParanoidAgent, a subclass of LWP::UserAgent which is more cautious about how it interacts with remote webservers.

Among other things, it allows you to specify a global timeout. It was developed by Brad Fitzpatrick as part of the LiveJournal project.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文