LWP::UserAgent 请求方法的真实超时
题
我正在尝试向不可靠的服务器发出请求。该请求是一个很好的请求,但并不是 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;
}
}
解决方案
你可以尝试 LWPx::ParanoidAgent, ,LWP::UserAgent 的子类,它对于如何与远程 Web 服务器交互更加谨慎。
除此之外,它还允许您指定全局超时。它是由 Brad Fitzpatrick 开发的,作为 LiveJournal 项目的一部分。
其他提示
您可以像这样设置自己的超时:
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";
}
据我了解,超时属性不考虑 DNS 超时。您可以单独进行 DNS 查找,然后向服务器发出请求(如果可行的话),并为用户代理设置正确的超时值。
这是服务器的 DNS 问题还是其他问题?
编辑:也可能是 IO::Socket 的问题。尝试更新您的 IO::Socket 模块,看看是否有帮助。我很确定其中存在一个错误,导致 LWP::UserAgent 超时无法正常工作。
亚历克斯
以下对原始答案之一的概括还将警报信号处理程序恢复到前一个处理程序,并添加对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;
}
}