mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-16 04:36:02 +00:00
HttpUtils: complete rewrite, addition of HttpUtils_NonblockingGet
git-svn-id: https://svn.fhem.de/fhem/trunk@4514 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
2930641431
commit
d1d39de466
@ -53,94 +53,174 @@ urlDecode($) {
|
||||
return $_;
|
||||
}
|
||||
|
||||
##################
|
||||
# - if data (which is urlEncoded) is set, then a POST is performed, else a GET.
|
||||
# - noshutdown must be set for e.g the Fritz!Box
|
||||
# 4.0 is needed for some clients trying to reach fhem.de, 2.0 was not enough
|
||||
sub
|
||||
CustomGetFileFromURL($$@)
|
||||
HttpUtils_ConnErr($)
|
||||
{
|
||||
my ($quiet, $url, $timeout, $data, $noshutdown, $loglevel) = @_;
|
||||
$timeout = 4.0 if(!defined($timeout));
|
||||
$loglevel = 4 if(!$loglevel);
|
||||
|
||||
my $redirects= 0;
|
||||
|
||||
RETRY:
|
||||
|
||||
my $displayurl= $quiet ? "<hidden>" : $url;
|
||||
Log3 undef, $loglevel, "CustomGetFileFromURL url=$displayurl";
|
||||
if($url !~ /^(http|https):\/\/(([^:\/]+):([^:\/]+)@)?([^:\/]+)(:\d+)?(\/.*)$/) {
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: malformed or unsupported URL";
|
||||
return undef;
|
||||
my ($hash) = @_;
|
||||
if(defined($hash->{FD})) {
|
||||
delete($hash->{FD});
|
||||
delete($selectlist{$hash});
|
||||
$hash->{callback}($hash, "connect to $hash->{addr} timed out", "");
|
||||
}
|
||||
|
||||
my ($protocol,$authstring,$user,$pwd,$host,$port,$path) =
|
||||
($1,$2,$3,$4,$5,$6,$7);
|
||||
}
|
||||
|
||||
sub
|
||||
HttpUtils_ReadErr($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
if(defined($hash->{FD})) {
|
||||
delete($hash->{FD});
|
||||
delete($selectlist{$hash});
|
||||
$hash->{callback}($hash, "read from $hash->{addr} timed out", "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub
|
||||
HttpUtils_Connect($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{timeout} = 4 if(!defined($hash->{timeout}));
|
||||
$hash->{loglevel} = 4 if(!$hash->{loglevel});
|
||||
$hash->{redirects} = 0 if(!$hash->{redirects});
|
||||
$hash->{displayurl} = $hash->{hideurl} ? "<hidden>" : $hash->{url};
|
||||
|
||||
Log3 undef, $hash->{loglevel}, "HttpUtils url=$hash->{displayurl}";
|
||||
|
||||
if($hash->{url} !~
|
||||
/^(http|https):\/\/(([^:\/]+):([^:\/]+)@)?([^:\/]+)(:\d+)?(\/.*)$/) {
|
||||
return "$hash->{displayurl}: malformed or unsupported URL";
|
||||
}
|
||||
|
||||
my ($authstring,$user,$pwd,$port,$host);
|
||||
($hash->{protocol},$authstring,$user,$pwd,$host,$port,$hash->{path})
|
||||
= ($1,$2,$3,$4,$5,$6,$7);
|
||||
$hash->{host} = $host;
|
||||
|
||||
if(defined($port)) {
|
||||
$port =~ s/^://;
|
||||
} else {
|
||||
$port = ($protocol eq "https" ? 443: 80);
|
||||
$port = ($hash->{protocol} eq "https" ? 443: 80);
|
||||
}
|
||||
$path= '/' unless defined($path);
|
||||
$hash->{path} = '/' unless defined($hash->{path});
|
||||
$hash->{addr} = "$hash->{protocol}://$host:$port";
|
||||
$hash->{auth} = encode_base64("$user:$pwd","") if($authstring);
|
||||
|
||||
my $auth64;
|
||||
if(defined($authstring)) {
|
||||
$auth64 = encode_base64("$user:$pwd","");
|
||||
if($hash->{callback}) { # Nonblocking staff
|
||||
$hash->{conn} = IO::Socket::INET->new(Proto=>'tcp', Blocking=>0);
|
||||
if($hash->{conn}) {
|
||||
my $iaddr = inet_aton($host);
|
||||
if(!defined($iaddr)) {
|
||||
my @addr = gethostbyname($host); # This is still blocking
|
||||
return "gethostbyname $host failed" if(!$addr[0]);
|
||||
$iaddr = $addr[4];
|
||||
}
|
||||
my $ret = connect($hash->{conn}, sockaddr_in($port, $iaddr));
|
||||
if(!$ret) {
|
||||
if($!{EINPROGRESS} || int($!)==10035) { # Nonblocking connect
|
||||
|
||||
$hash->{FD} = $hash->{conn}->fileno();
|
||||
$hash->{directWriteFn} = sub() {
|
||||
delete($hash->{FD});
|
||||
delete($hash->{directWriteFn});
|
||||
delete($selectlist{$hash});
|
||||
|
||||
my $packed = getsockopt($hash->{conn}, SOL_SOCKET, SO_ERROR);
|
||||
my $errno = unpack("I",$packed);
|
||||
return $hash->{callback}($hash, "$host: ".strerror($errno), "")
|
||||
if($errno);
|
||||
|
||||
return HttpUtils_Connect2($hash);
|
||||
};
|
||||
$hash->{NAME} = ""; # Delete might check it
|
||||
$selectlist{$hash} = $hash;
|
||||
InternalTimer(gettimeofday()+$hash->{timeout},
|
||||
"HttpUtils_ConnErr", $hash, 0);
|
||||
return undef;
|
||||
} else {
|
||||
return "connect: $!";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$hash->{conn} = IO::Socket::INET->new(
|
||||
PeerAddr=>"$host:$port", Timeout=>$hash->{timeout});
|
||||
}
|
||||
return HttpUtils_Connect2($hash);
|
||||
}
|
||||
|
||||
my $conn;
|
||||
my $errstr= "";
|
||||
if($protocol eq "https") {
|
||||
sub
|
||||
HttpUtils_Connect2($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
|
||||
if($hash->{protocol} eq "https" && $hash->{conn}) {
|
||||
eval "use IO::Socket::SSL";
|
||||
if($@) {
|
||||
Log3 undef, $loglevel, $@;
|
||||
Log3 undef, $hash->{loglevel}, $@;
|
||||
} else {
|
||||
$conn = IO::Socket::SSL->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
|
||||
$hash->{conn}->blocking(1);
|
||||
IO::Socket::SSL->start_SSL($hash->{conn}, Timeout=>$hash->{timeout})
|
||||
|| undef $hash->{conn};
|
||||
}
|
||||
} else {
|
||||
$conn = IO::Socket::INET->new(PeerAddr=>"$host:$port", Timeout=>$timeout);
|
||||
}
|
||||
$errstr= $@ if(!$conn);
|
||||
if(!$conn) {
|
||||
Log3 undef, $loglevel, "CustomGetFileFromURL $displayurl: ".
|
||||
"Can't connect to $protocol://$host:$port, $errstr";
|
||||
undef $conn;
|
||||
if(!$hash->{conn}) {
|
||||
undef $hash->{conn};
|
||||
return "$hash->{displayurl}: Can't connect to $hash->{addr}: $@";
|
||||
}
|
||||
|
||||
$hash->{host} =~ s/:.*//;
|
||||
my $hdr = ($hash->{data} ? "POST" : "GET")." $hash->{path} HTTP/1.0\r\n";
|
||||
$hdr .= "Host: $hash->{host}\r\n";
|
||||
$hdr .= "Authorization: Basic $hash->{auth}\r\n" if(defined($hash->{auth}));
|
||||
$hdr .= $hash->{header}."\r\n" if(defined($hash->{header}));
|
||||
if(defined($hash->{data})) {
|
||||
$hdr .= "Content-Length: ".length($hash->{data})."\r\n";
|
||||
$hdr .= "Content-Type: application/x-www-form-urlencoded\r\n"
|
||||
if ($hdr !~ "Content-Type:");
|
||||
}
|
||||
$hdr .= "\r\n";
|
||||
syswrite $hash->{conn}, $hdr;
|
||||
syswrite $hash->{conn}, $hash->{data} if(defined($hash->{data}));
|
||||
shutdown $hash->{conn}, 1 if(!$hash->{noshutdown});
|
||||
|
||||
if($hash->{callback}) { # Nonblocking read
|
||||
$hash->{FD} = $hash->{conn}->fileno();
|
||||
$hash->{buf} = "";
|
||||
$hash->{directReadFn} = sub() {
|
||||
my $buf;
|
||||
my $len = sysread($hash->{conn},$buf,65536);
|
||||
if(!defined($len) || $len <= 0) { # EOF
|
||||
delete($hash->{FD});
|
||||
delete($hash->{directReadFn});
|
||||
delete($selectlist{$hash});
|
||||
my ($err, $ret, $redirect) = HttpUtils_ParseAnswer($hash, $hash->{buf});
|
||||
$hash->{callback}($hash, $err, $ret) if(!$redirect);
|
||||
return;
|
||||
}
|
||||
$hash->{buf} .= $buf;
|
||||
};
|
||||
$selectlist{$hash} = $hash;
|
||||
InternalTimer(gettimeofday()+$hash->{timeout},
|
||||
"HttpUtils_ReadErr", $hash, 0);
|
||||
return undef;
|
||||
}
|
||||
|
||||
$host =~ s/:.*//;
|
||||
my $hdr = ($data ? "POST" : "GET")." $path HTTP/1.0\r\nHost: $host\r\n";
|
||||
if(defined($authstring)) {
|
||||
$hdr .= "Authorization: Basic $auth64\r\n";
|
||||
}
|
||||
if(defined($data)) {
|
||||
$hdr .= "Content-Length: ".length($data)."\r\n";
|
||||
$hdr .= "Content-Type: application/x-www-form-urlencoded";
|
||||
}
|
||||
$hdr .= "\r\n\r\n";
|
||||
syswrite $conn, $hdr;
|
||||
syswrite $conn, $data if(defined($data));
|
||||
shutdown $conn, 1 if(!$noshutdown);
|
||||
return undef;
|
||||
}
|
||||
|
||||
my ($buf, $ret) = ("", "");
|
||||
$conn->timeout($timeout);
|
||||
for(;;) {
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $conn->fileno(), 1) = 1;
|
||||
my $nfound = select($rout=$rin, undef, undef, $timeout);
|
||||
if($nfound <= 0) {
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: Select timeout/error: $!";
|
||||
undef $conn;
|
||||
return undef;
|
||||
}
|
||||
sub
|
||||
HttpUtils_ParseAnswer($$)
|
||||
{
|
||||
my ($hash, $ret) = @_;
|
||||
|
||||
my $len = sysread($conn,$buf,65536);
|
||||
last if(!defined($len) || $len <= 0);
|
||||
$ret .= $buf;
|
||||
$hash->{conn}->close();
|
||||
undef $hash->{conn};
|
||||
|
||||
if(!$ret) {
|
||||
return ("$hash->{displayurl}: empty answer received", "");
|
||||
}
|
||||
|
||||
$ret=~ s/(.*?)\r\n\r\n//s; # Not greedy: switch off the header.
|
||||
@ -149,44 +229,114 @@ CustomGetFileFromURL($$@)
|
||||
my $code= $header0[1];
|
||||
|
||||
if(!defined($code) || $code eq "") {
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: empty answer received";
|
||||
return undef;
|
||||
return ("$hash->{displayurl}: empty answer received", "");
|
||||
}
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: HTTP Response code $code";
|
||||
Log3 undef,$hash->{loglevel}, "$hash->{displayurl}: HTTP response code $code";
|
||||
|
||||
if($code == 301 || $code == 302 || $code == 303) { # redirect
|
||||
if(++$redirects > 5) {
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: Too many redirects";
|
||||
if($code==301 || $code==302 || $code==303) { # redirect
|
||||
if(++$hash->{redirects} > 5) {
|
||||
return ("$hash->{displayurl}: Too many redirects", "");
|
||||
|
||||
} else {
|
||||
map { $url=$1 if($_ =~ m/Location:\s*(\S+)$/) } @header;
|
||||
Log3 undef, $loglevel, "CustomGetFileFromURL $displayurl: ".
|
||||
"Redirect to ".($quiet ? "<hidden>" : $url);
|
||||
goto RETRY;
|
||||
|
||||
my $ra;
|
||||
map { $ra=$1 if($_ =~ m/Location:\s*(\S+)$/) } @header;
|
||||
$hash->{url} = ($ra =~ m/^http/) ? $ra: $hash->{addr}.$ra;
|
||||
Log3 undef, $hash->{loglevel}, "HttpUtils $hash->{displayurl}: ".
|
||||
"Redirect to ".($hash->{hideurl} ? "<hidden>" : $hash->{url});
|
||||
if($hash->{callback}) {
|
||||
HttpUtils_NonblockingGet($hash);
|
||||
return ("", "", 1);
|
||||
} else {
|
||||
return HttpUtils_BlockingGet($hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $hostpath= $quiet ? "<hidden>" : $host . $path;
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: Got data, length: ".length($ret);
|
||||
# Debug
|
||||
Log3 undef, $hash->{loglevel},
|
||||
"HttpUtils $hash->{displayurl}: Got data, length: ". length($ret);
|
||||
if(!length($ret)) {
|
||||
Log3 undef, $loglevel,
|
||||
"CustomGetFileFromURL $displayurl: Zero length data, header follows...";
|
||||
Log3 undef, $hash->{loglevel}, "HttpUtils $hash->{displayurl}: ".
|
||||
"Zero length data, header follows:";
|
||||
for (@header) {
|
||||
Log3 undef, $loglevel, "CustomGetFileFromURL $displayurl: $_";
|
||||
Log3 undef, $hash->{loglevel}, " $_";
|
||||
}
|
||||
}
|
||||
undef $conn;
|
||||
return ("", $ret);
|
||||
}
|
||||
|
||||
# Parameters in the hash:
|
||||
# mandatory:
|
||||
# url, callback
|
||||
# optional(default):
|
||||
# hideurl(0),timeout(4),data(""),noshutdown(0),loglevel(4),header("")
|
||||
# Example:
|
||||
# HttpUtils_NonblockingGet({
|
||||
# url=>"http://192.168.178.112:8888/fhem",
|
||||
# myParam=>7,
|
||||
# callback=>sub($$$){ Log 1,"$_[0]->{myParam} ERR:$_[1] DATA:$_[2]" }
|
||||
# })
|
||||
sub
|
||||
HttpUtils_NonblockingGet($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $err = HttpUtils_Connect($hash);
|
||||
$hash->{callback}($hash, $err, "") if($err);
|
||||
}
|
||||
|
||||
#################
|
||||
# Parameters same as HttpUtils_NonblockingGet up to callback
|
||||
# Returns (err,data)
|
||||
sub
|
||||
HttpUtils_BlockingGet($)
|
||||
{
|
||||
my ($hash) = @_;
|
||||
my $err = HttpUtils_Connect($hash);
|
||||
return ($err, undef) if($err);
|
||||
|
||||
my ($buf, $ret) = ("", "");
|
||||
$hash->{conn}->timeout($hash->{timeout});
|
||||
for(;;) {
|
||||
my ($rout, $rin) = ('', '');
|
||||
vec($rin, $hash->{conn}->fileno(), 1) = 1;
|
||||
my $nfound = select($rout=$rin, undef, undef, $hash->{timeout});
|
||||
if($nfound <= 0) {
|
||||
undef $hash->{conn};
|
||||
return ("$hash->{displayurl}: Select timeout/error: $!", undef);
|
||||
}
|
||||
|
||||
my $len = sysread($hash->{conn},$buf,65536);
|
||||
last if(!defined($len) || $len <= 0);
|
||||
$ret .= $buf;
|
||||
}
|
||||
return HttpUtils_ParseAnswer($hash, $ret);
|
||||
}
|
||||
|
||||
# Deprecated, use GetFileFromURL/GetFileFromURLQuiet
|
||||
sub
|
||||
CustomGetFileFromURL($$@)
|
||||
{
|
||||
my ($hideurl, $url, $timeout, $data, $noshutdown, $loglevel) = @_;
|
||||
my $hash = { hideurl => $hideurl,
|
||||
url => $url,
|
||||
timeout => $timeout,
|
||||
data => $data,
|
||||
noshutdown=> $noshutdown,
|
||||
loglevel => $loglevel,
|
||||
};
|
||||
my ($err, $ret) = HttpUtils_BlockingGet($hash);
|
||||
if($err) {
|
||||
Log3 undef, $hash->{loglevel}, "CustomGetFileFromURL $err";
|
||||
return undef;
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
##################
|
||||
# Compatibility mode
|
||||
# Parameter: $url, $timeout, $data, $noshutdown, $loglevel
|
||||
# - if data (which is urlEncoded) is set, then a POST is performed, else a GET.
|
||||
# - noshutdown must be set e.g. if calling the Fritz!Box Webserver
|
||||
sub
|
||||
GetFileFromURL($@)
|
||||
{
|
||||
@ -194,6 +344,8 @@ GetFileFromURL($@)
|
||||
return CustomGetFileFromURL(0, $url, @a);
|
||||
}
|
||||
|
||||
##################
|
||||
# Same as GetFileFromURL, but the url is not displayed in the log.
|
||||
sub
|
||||
GetFileFromURLQuiet($@)
|
||||
{
|
||||
@ -208,5 +360,4 @@ GetHttpFile($$)
|
||||
return GetFileFromURL("http://$host$file");
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user