From 1425eea04dd872dc6313f5315f317b2de288037c Mon Sep 17 00:00:00 2001 From: Lorry Tar Creator Date: Mon, 1 Jun 2015 14:15:30 +0000 Subject: IO-Async-0.67 --- lib/IO/Async/Resolver.pm | 689 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 689 insertions(+) create mode 100644 lib/IO/Async/Resolver.pm (limited to 'lib/IO/Async/Resolver.pm') diff --git a/lib/IO/Async/Resolver.pm b/lib/IO/Async/Resolver.pm new file mode 100644 index 0000000..10c0a15 --- /dev/null +++ b/lib/IO/Async/Resolver.pm @@ -0,0 +1,689 @@ +# You may distribute under the terms of either the GNU General Public License +# or the Artistic License (the same terms as Perl itself) +# +# (C) Paul Evans, 2007-2015 -- leonerd@leonerd.org.uk + +package IO::Async::Resolver; + +use strict; +use warnings; +use 5.010; +use base qw( IO::Async::Function ); + +our $VERSION = '0.67'; + +# Socket 2.006 fails to getaddrinfo() AI_NUMERICHOST properly on MSWin32 +use Socket 2.007 qw( + AI_NUMERICHOST AI_PASSIVE + NI_NUMERICHOST NI_NUMERICSERV NI_DGRAM + EAI_NONAME +); + +use IO::Async::OS; + +# Try to use HiRes alarm, but we don't strictly need it. +# MSWin32 doesn't implement it +BEGIN { + require Time::HiRes; + eval { Time::HiRes::alarm(0) } and Time::HiRes->import( qw( alarm ) ); +} + +use Carp; + +my $started = 0; +my %METHODS; + +=head1 NAME + +C - performing name resolutions asynchronously + +=head1 SYNOPSIS + +This object is used indirectly via an C: + + use IO::Async::Loop; + my $loop = IO::Async::Loop->new; + + $loop->resolver->getaddrinfo( + host => "www.example.com", + service => "http", + )->on_done( sub { + foreach my $addr ( @_ ) { + printf "http://www.example.com can be reached at " . + "socket(%d,%d,%d) + connect('%v02x')\n", + @{$addr}{qw( family socktype protocol addr )}; + } + }); + + $loop->resolve( type => 'getpwuid', data => [ $< ] ) + ->on_done( sub { + print "My passwd ent: " . join( "|", @_ ) . "\n"; + }); + + $loop->run; + +=head1 DESCRIPTION + +This module extends an C to use the system's name resolver +functions asynchronously. It provides a number of named resolvers, each one +providing an asynchronous wrapper around a single resolver function. + +Because the system may not provide asynchronous versions of its resolver +functions, this class is implemented using a C object +that wraps the normal (blocking) functions. In this case, name resolutions +will be performed asynchronously from the rest of the program, but will likely +be done by a single background worker process, so will be processed in the +order they were requested; a single slow lookup will hold up the queue of +other requests behind it. To mitigate this, multiple worker processes can be +used; see the C argument to the constructor. + +The C parameter for the underlying C object +is set to a default of 30 seconds, and C is set to 0. This +ensures that there are no spare processes sitting idle during the common case +of no outstanding requests. + +=cut + +sub _init +{ + my $self = shift; + my ( $params ) = @_; + $self->SUPER::_init( @_ ); + + $params->{code} = sub { + my ( $type, $timeout, @data ) = @_; + + if( my $code = $METHODS{$type} ) { + local $SIG{ALRM} = sub { die "Timed out\n" }; + + alarm( $timeout ); + my @ret = eval { $code->( @data ) }; + alarm( 0 ); + + die $@ if $@; + return @ret; + } + else { + die "Unrecognised resolver request '$type'"; + } + }; + + $params->{idle_timeout} = 30; + $params->{min_workers} = 0; + + $started = 1; +} + +=head1 METHODS + +The following methods documented with a trailing call to C<< ->get >> return +L instances. + +=cut + +=head2 @result = $loop->resolve( %params )->get + +Performs a single name resolution operation, as given by the keys in the hash. + +The C<%params> hash keys the following keys: + +=over 8 + +=item type => STRING + +Name of the resolution operation to perform. See BUILT-IN RESOLVERS for the +list of available operations. + +=item data => ARRAY + +Arguments to pass to the resolver function. Exact meaning depends on the +specific function chosen by the C; see BUILT-IN RESOLVERS. + +=item timeout => NUMBER + +Optional. Timeout in seconds, after which the resolver operation will abort +with a timeout exception. If not supplied, a default of 10 seconds will apply. + +=back + +=head2 $resolver->resolve( %params ) + +When not returning a future, additional parameters can be given containing the +continuations to invoke on success or failure: + +=over 8 + +=item on_resolved => CODE + +A continuation that is invoked when the resolver function returns a successful +result. It will be passed the array returned by the resolver function. + + $on_resolved->( @result ) + +=item on_error => CODE + +A continuation that is invoked when the resolver function fails. It will be +passed the exception thrown by the function. + +=back + +=cut + +sub resolve +{ + my $self = shift; + my %args = @_; + + my $type = $args{type}; + defined $type or croak "Expected 'type'"; + + if( $type eq "getaddrinfo" ) { + $type = "getaddrinfo_hash"; + } + + exists $METHODS{$type} or croak "Expected 'type' to be an existing resolver method, got '$type'"; + + my $on_resolved; + if( $on_resolved = $args{on_resolved} ) { + ref $on_resolved or croak "Expected 'on_resolved' to be a reference"; + } + elsif( !defined wantarray ) { + croak "Expected 'on_resolved' or to return a Future"; + } + + my $on_error; + if( $on_error = $args{on_error} ) { + ref $on_error or croak "Expected 'on_error' to be a reference"; + } + elsif( !defined wantarray ) { + croak "Expected 'on_error' or to return a Future"; + } + + my $timeout = $args{timeout} || 10; + + my $future = $self->call( + args => [ $type, $timeout, @{$args{data}} ], + ); + + $future->on_done( $on_resolved ) if $on_resolved; + $future->on_fail( $on_error ) if $on_error; + + return $future if defined wantarray; + + # Caller is not going to keep hold of the Future, so we have to ensure it + # stays alive somehow + $self->adopt_future( $future->else( sub { Future->done } ) ); +} + +=head2 @addrs = $resolver->getaddrinfo( %args )->get + +A shortcut wrapper around the C resolver, taking its arguments in +a more convenient form. + +=over 8 + +=item host => STRING + +=item service => STRING + +The host and service names to look up. At least one must be provided. + +=item family => INT or STRING + +=item socktype => INT or STRING + +=item protocol => INT + +Hint values used to filter the results. + +=item flags => INT + +Flags to control the C function. See the C constants in +L's C function for more detail. + +=item passive => BOOL + +If true, sets the C flag. This is provided as a convenience to +avoid the caller from having to import the C constant from +C. + +=item timeout => NUMBER + +Time in seconds after which to abort the lookup with a C exception + +=back + +On success, the future will yield the result as a list of HASH references; +each containing one result. Each result will contain fields called C, +C, C and C. If requested by C then the +C field will also be present. + +As a specific optimisation, this method will try to perform a lookup of +numeric values synchronously, rather than asynchronously, if it looks likely +to succeed. + +Specifically, if the service name is entirely numeric, and the hostname looks +like an IPv4 or IPv6 string, a synchronous lookup will first be performed +using the C flag. If this gives an C error, then +the lookup is performed asynchronously instead. + +=head2 $resolver->getaddrinfo( %args ) + +When not returning a future, additional parameters can be given containing the +continuations to invoke on success or failure: + +=over 8 + +=item on_resolved => CODE + +Callback which is invoked after a successful lookup. + + $on_resolved->( @addrs ) + +=item on_error => CODE + +Callback which is invoked after a failed lookup, including for a timeout. + + $on_error->( $exception ) + +=back + +=cut + +sub getaddrinfo +{ + my $self = shift; + my %args = @_; + + $args{on_resolved} or defined wantarray or + croak "Expected 'on_resolved' or to return a Future"; + + $args{on_error} or defined wantarray or + croak "Expected 'on_error' or to return a Future"; + + my $host = $args{host} || ""; + my $service = $args{service} // ""; + my $flags = $args{flags} || 0; + + $flags |= AI_PASSIVE if $args{passive}; + + $args{family} = IO::Async::OS->getfamilybyname( $args{family} ) if defined $args{family}; + $args{socktype} = IO::Async::OS->getsocktypebyname( $args{socktype} ) if defined $args{socktype}; + + # Clear any other existing but undefined hints + defined $args{$_} or delete $args{$_} for keys %args; + + # It's likely this will succeed with AI_NUMERICHOST if host contains only + # [\d.] (IPv4) or [[:xdigit:]:] (IPv6) + # Technically we should pass AI_NUMERICSERV but not all platforms support + # it, but since we're checking service contains only \d we should be fine. + + # These address tests don't have to be perfect as if it fails we'll get + # EAI_NONAME and just try it asynchronously anyway + if( ( $host =~ m/^[\d.]+$/ or $host =~ m/^[[:xdigit:]:]$/ or $host eq "" ) and + $service =~ m/^\d+$/ ) { + + my ( $err, @results ) = Socket::getaddrinfo( $host, $service, + { %args, flags => $flags | AI_NUMERICHOST } + ); + + if( !$err ) { + my $future = $self->loop->new_future->done( @results ); + $future->on_done( $args{on_resolved} ) if $args{on_resolved}; + return $future; + } + elsif( $err == EAI_NONAME ) { + # fallthrough to async case + } + else { + my $future = $self->loop->new_future->fail( $err, resolve => getaddrinfo => $err+0 ); + $future->on_fail( $args{on_error} ) if $args{on_error}; + return $future; + } + } + + my $future = $self->resolve( + type => "getaddrinfo_hash", + data => [ + host => $host, + service => $service, + flags => $flags, + map { exists $args{$_} ? ( $_ => $args{$_} ) : () } qw( family socktype protocol ), + ], + timeout => $args{timeout}, + )->else( sub { + my $message = shift; + Future->fail( $message, resolve => getaddrinfo => @_ ); + }); + + $future->on_done( $args{on_resolved} ) if $args{on_resolved}; + $future->on_fail( $args{on_error} ) if $args{on_error}; + + return $future if defined wantarray; + + # Caller is not going to keep hold of the Future, so we have to ensure it + # stays alive somehow + $self->adopt_future( $future->else( sub { Future->done } ) ); +} + +=head2 ( $host, $service ) = $resolver->getnameinfo( %args )->get + +A shortcut wrapper around the C resolver, taking its arguments in +a more convenient form. + +=over 8 + +=item addr => STRING + +The packed socket address to look up. + +=item flags => INT + +Flags to control the C function. See the C constants in +L's C for more detail. + +=item numerichost => BOOL + +=item numericserv => BOOL + +=item dgram => BOOL + +If true, set the C, C or C flags. + +=item numeric => BOOL + +If true, sets both C and C flags. + +=item timeout => NUMBER + +Time in seconds after which to abort the lookup with a C exception + +=back + +As a specific optimisation, this method will try to perform a lookup of +numeric values synchronously, rather than asynchronously, if both the +C and C flags are given. + +=head2 $resolver->getnameinfo( %args ) + +When not returning a future, additional parameters can be given containing the +continuations to invoke on success or failure: + +=over 8 + +=item on_resolved => CODE + +Callback which is invoked after a successful lookup. + + $on_resolved->( $host, $service ) + +=item on_error => CODE + +Callback which is invoked after a failed lookup, including for a timeout. + + $on_error->( $exception ) + +=back + +=cut + +sub getnameinfo +{ + my $self = shift; + my %args = @_; + + $args{on_resolved} or defined wantarray or + croak "Expected 'on_resolved' or to return a Future"; + + $args{on_error} or defined wantarray or + croak "Expected 'on_error' or to return a Future"; + + my $flags = $args{flags} || 0; + + $flags |= NI_NUMERICHOST if $args{numerichost}; + $flags |= NI_NUMERICSERV if $args{numericserv}; + $flags |= NI_DGRAM if $args{dgram}; + + $flags |= NI_NUMERICHOST|NI_NUMERICSERV if $args{numeric}; + + if( $flags & (NI_NUMERICHOST|NI_NUMERICSERV) ) { + # This is a numeric-only lookup that can be done synchronously + my ( $err, $host, $service ) = Socket::getnameinfo( $args{addr}, $flags ); + + if( $err ) { + my $future = $self->loop->new_future->fail( $err, resolve => getnameinfo => $err+0 ); + $future->on_fail( $args{on_error} ) if $args{on_error}; + return $future; + } + else { + my $future = $self->loop->new_future->done( $host, $service ); + $future->on_done( $args{on_resolved} ) if $args{on_resolved}; + return $future; + } + } + + my $future = $self->resolve( + type => "getnameinfo", + data => [ $args{addr}, $flags ], + timeout => $args{timeout}, + )->transform( + done => sub { @{ $_[0] } }, # unpack the ARRAY ref + )->else( sub { + my $message = shift; + Future->fail( $message, resolve => getnameinfo => @_ ); + }); + + $future->on_done( $args{on_resolved} ) if $args{on_resolved}; + $future->on_fail( $args{on_error} ) if $args{on_error}; + + return $future if defined wantarray; + + # Caller is not going to keep hold of the Future, so we have to ensure it + # stays alive somehow + $self->adopt_future( $future->else( sub { Future->done } ) ); +} + +=head1 FUNCTIONS + +=cut + +=head2 register_resolver( $name, $code ) + +Registers a new named resolver function that can be called by the C +method. All named resolvers must be registered before the object is +constructed. + +=over 8 + +=item $name + +The name of the resolver function; must be a plain string. This name will be +used by the C argument to the C method, to identify it. + +=item $code + +A CODE reference to the resolver function body. It will be called in list +context, being passed the list of arguments given in the C argument to +the C method. The returned list will be passed to the +C callback. If the code throws an exception at call time, it will +be passed to the C continuation. If it returns normally, the list of +values it returns will be passed to C. + +=back + +=cut + +# Plain function, not a method +sub register_resolver +{ + my ( $name, $code ) = @_; + + croak "Cannot register new resolver methods once the resolver has been started" if $started; + + croak "Already have a resolver method called '$name'" if exists $METHODS{$name}; + $METHODS{$name} = $code; +} + +=head1 BUILT-IN RESOLVERS + +The following resolver names are implemented by the same-named perl function, +taking and returning a list of values exactly as the perl function does: + + getpwnam getpwuid + getgrnam getgrgid + getservbyname getservbyport + gethostbyname gethostbyaddr + getnetbyname getnetbyaddr + getprotobyname getprotobynumber + +=cut + +# Now register the inbuilt methods + +register_resolver getpwnam => sub { my @r = getpwnam( $_[0] ) or die "$!\n"; @r }; +register_resolver getpwuid => sub { my @r = getpwuid( $_[0] ) or die "$!\n"; @r }; + +register_resolver getgrnam => sub { my @r = getgrnam( $_[0] ) or die "$!\n"; @r }; +register_resolver getgrgid => sub { my @r = getgrgid( $_[0] ) or die "$!\n"; @r }; + +register_resolver getservbyname => sub { my @r = getservbyname( $_[0], $_[1] ) or die "$!\n"; @r }; +register_resolver getservbyport => sub { my @r = getservbyport( $_[0], $_[1] ) or die "$!\n"; @r }; + +register_resolver gethostbyname => sub { my @r = gethostbyname( $_[0] ) or die "$!\n"; @r }; +register_resolver gethostbyaddr => sub { my @r = gethostbyaddr( $_[0], $_[1] ) or die "$!\n"; @r }; + +register_resolver getnetbyname => sub { my @r = getnetbyname( $_[0] ) or die "$!\n"; @r }; +register_resolver getnetbyaddr => sub { my @r = getnetbyaddr( $_[0], $_[1] ) or die "$!\n"; @r }; + +register_resolver getprotobyname => sub { my @r = getprotobyname( $_[0] ) or die "$!\n"; @r }; +register_resolver getprotobynumber => sub { my @r = getprotobynumber( $_[0] ) or die "$!\n"; @r }; + +=pod + +The following three resolver names are implemented using the L module. + + getaddrinfo_hash + getaddrinfo_array + getnameinfo + +The C resolver takes arguments in a hash of name/value pairs +and returns a list of hash structures, as the C function +does. For neatness it takes all its arguments as named values; taking the host +and service names from arguments called C and C respectively; +all the remaining arguments are passed into the hints hash. This name is also +aliased as simply C. + +The C resolver behaves more like the C version of +the function. It takes hints in a flat list, and mangles the result of the +function, so that the returned value is more useful to the caller. It splits +up the list of 5-tuples into a list of ARRAY refs, where each referenced array +contains one of the tuples of 5 values. + +As an extra convenience to the caller, both resolvers will also accept plain +string names for the C argument, converting C and possibly +C into the appropriate C value, and for the C argument, +converting C, C or C into the appropriate C value. + +The C resolver returns its result in the same form as C. + +Because this module simply uses the system's C resolver, it will +be fully IPv6-aware if the underlying platform's resolver is. This allows +programs to be fully IPv6-capable. + +=cut + +register_resolver getaddrinfo_hash => sub { + my %args = @_; + + my $host = delete $args{host}; + my $service = delete $args{service}; + + $args{family} = IO::Async::OS->getfamilybyname( $args{family} ) if defined $args{family}; + $args{socktype} = IO::Async::OS->getsocktypebyname( $args{socktype} ) if defined $args{socktype}; + + # Clear any other existing but undefined hints + defined $args{$_} or delete $args{$_} for keys %args; + + my ( $err, @addrs ) = Socket::getaddrinfo( $host, $service, \%args ); + + die "$err\n" if $err; + + return @addrs; +}; + +register_resolver getaddrinfo_array => sub { + my ( $host, $service, $family, $socktype, $protocol, $flags ) = @_; + + $family = IO::Async::OS->getfamilybyname( $family ); + $socktype = IO::Async::OS->getsocktypebyname( $socktype ); + + my %hints; + $hints{family} = $family if defined $family; + $hints{socktype} = $socktype if defined $socktype; + $hints{protocol} = $protocol if defined $protocol; + $hints{flags} = $flags if defined $flags; + + my ( $err, @addrs ) = Socket::getaddrinfo( $host, $service, \%hints ); + + die "$err\n" if $err; + + # Convert the @addrs list into a list of ARRAY refs of 5 values each + return map { + [ $_->{family}, $_->{socktype}, $_->{protocol}, $_->{addr}, $_->{canonname} ] + } @addrs; +}; + +register_resolver getnameinfo => sub { + my ( $addr, $flags ) = @_; + + my ( $err, $host, $service ) = Socket::getnameinfo( $addr, $flags || 0 ); + + die "$err\n" if $err; + + return [ $host, $service ]; +}; + +=head1 EXAMPLES + +The following somewhat contrieved example shows how to implement a new +resolver function. This example just uses in-memory data, but a real function +would likely make calls to OS functions to provide an answer. In traditional +Unix style, a pair of functions are provided that each look up the entity by +either type of key, where both functions return the same type of list. This is +purely a convention, and is in no way required or enforced by the +C itself. + + @numbers = qw( zero one two three four + five six seven eight nine ); + + register_resolver getnumberbyindex => sub { + my ( $index ) = @_; + die "Bad index $index" unless $index >= 0 and $index < @numbers; + return ( $index, $numbers[$index] ); + }; + + register_resolver getnumberbyname => sub { + my ( $name ) = @_; + foreach my $index ( 0 .. $#numbers ) { + return ( $index, $name ) if $numbers[$index] eq $name; + } + die "Bad name $name"; + }; + +=head1 TODO + +=over 4 + +=item * + +Look into (system-specific) ways of accessing asynchronous resolvers directly + +=back + +=head1 AUTHOR + +Paul Evans + +=cut + +0x55AA; -- cgit v1.2.1