File Coverage

blib/lib/IO/Async/Resolver.pm
Criterion Covered Total %
statement 118 140 84.2
branch 68 98 69.3
condition 20 29 68.9
subroutine 17 22 77.2
pod 4 6 66.6
total 227 295 76.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2007-2021 -- leonerd@leonerd.org.uk
5              
6             package IO::Async::Resolver;
7              
8 7     7   1408 use strict;
  7         17  
  7         261  
9 7     7   37 use warnings;
  7         14  
  7         214  
10 7     7   46 use base qw( IO::Async::Function );
  7         16  
  7         3958  
11              
12             our $VERSION = '0.801';
13              
14             # Socket 2.006 fails to getaddrinfo() AI_NUMERICHOST properly on MSWin32
15 7         610 use Socket 2.007 qw(
16             AI_NUMERICHOST AI_PASSIVE
17             NI_NUMERICHOST NI_NUMERICSERV NI_DGRAM
18             EAI_NONAME
19 7     7   57 );
  7         176  
20              
21 7     7   63 use IO::Async::Metrics '$METRICS';
  7         16  
  7         54  
22 7     7   46 use IO::Async::OS;
  7         34  
  7         457  
23              
24             # Try to use HiRes alarm, but we don't strictly need it.
25             # MSWin32 doesn't implement it
26             BEGIN {
27 7     7   57 require Time::HiRes;
28 7 50       17 eval { Time::HiRes::alarm(0) } and Time::HiRes->import( qw( alarm ) );
  7         300  
29             }
30              
31 7     7   46 use Carp;
  7         13  
  7         21189  
32              
33             my $started = 0;
34             my %METHODS;
35              
36             =head1 NAME
37              
38             C - performing name resolutions asynchronously
39              
40             =head1 SYNOPSIS
41              
42             This object is used indirectly via an L:
43              
44             use IO::Async::Loop;
45             my $loop = IO::Async::Loop->new;
46              
47             my @results = $loop->resolver->getaddrinfo(
48             host => "www.example.com",
49             service => "http",
50             )->get;
51              
52             foreach my $addr ( @results ) {
53             printf "http://www.example.com can be reached at " .
54             "socket(%d,%d,%d) + connect('%v02x')\n",
55             @{$addr}{qw( family socktype protocol addr )};
56             }
57              
58             my @pwent = $loop->resolve( type => 'getpwuid', data => [ $< ] )->get;
59              
60             print "My passwd ent: " . join( "|", @pwent ) . "\n";
61              
62             =head1 DESCRIPTION
63              
64             This module extends an L to use the system's name resolver
65             functions asynchronously. It provides a number of named resolvers, each one
66             providing an asynchronous wrapper around a single resolver function.
67              
68             Because the system may not provide asynchronous versions of its resolver
69             functions, this class is implemented using a L object
70             that wraps the normal (blocking) functions. In this case, name resolutions
71             will be performed asynchronously from the rest of the program, but will likely
72             be done by a single background worker process, so will be processed in the
73             order they were requested; a single slow lookup will hold up the queue of
74             other requests behind it. To mitigate this, multiple worker processes can be
75             used; see the C argument to the constructor.
76              
77             The C parameter for the underlying L object
78             is set to a default of 30 seconds, and C is set to 0. This
79             ensures that there are no spare processes sitting idle during the common case
80             of no outstanding requests.
81              
82             =cut
83              
84             sub _init
85             {
86 6     6   28 my $self = shift;
87 6         15 my ( $params ) = @_;
88 6         41 $self->SUPER::_init( @_ );
89              
90 6         15 $params->{module} = __PACKAGE__;
91 6         13 $params->{func} = "_resolve";
92              
93 6         14 $params->{idle_timeout} = 30;
94 6         8 $params->{min_workers} = 0;
95              
96 6         21 $started = 1;
97             }
98              
99             sub _resolve
100             {
101 0     0   0 my ( $type, $timeout, @data ) = @_;
102              
103 0 0       0 if( my $code = $METHODS{$type} ) {
104 0     0   0 local $SIG{ALRM} = sub { die "Timed out\n" };
  0         0  
105              
106 0         0 alarm( $timeout );
107 0         0 my @ret = eval { $code->( @data ) };
  0         0  
108 0         0 alarm( 0 );
109              
110 0 0       0 die $@ if $@;
111 0         0 return @ret;
112             }
113             else {
114 0         0 die "Unrecognised resolver request '$type'";
115             }
116             }
117              
118             sub debug_printf_call
119             {
120 15     15 0 22 my $self = shift;
121 15         43 my ( $type, undef, @data ) = @_;
122              
123 15         28 my $arg0;
124 15 100       48 if( $type eq "getaddrinfo" ) {
    100          
125 5         39 my %args = @data;
126 5         30 $arg0 = sprintf "%s:%s", @args{qw( host service )};
127             }
128             elsif( $type eq "getnameinfo" ) {
129             # cheat
130 3         11 $arg0 = sprintf "%s:%s", ( Socket::getnameinfo( $data[0], NI_NUMERICHOST|NI_NUMERICSERV ) )[1,2];
131             }
132             else {
133 7         14 $arg0 = $data[0];
134             }
135              
136 15         150 $self->debug_printf( "CALL $type $arg0" );
137             }
138              
139             sub debug_printf_result
140             {
141 14     14 0 20 my $self = shift;
142 14         32 my ( @result ) = @_;
143 14         61 $self->debug_printf( "RESULT n=" . scalar @result );
144             }
145              
146             =head1 METHODS
147              
148             The following methods documented with a trailing call to C<< ->get >> return
149             L instances.
150              
151             =cut
152              
153             =head2 resolve
154              
155             @result = $loop->resolve( %params )->get
156              
157             Performs a single name resolution operation, as given by the keys in the hash.
158              
159             The C<%params> hash keys the following keys:
160              
161             =over 8
162              
163             =item type => STRING
164              
165             Name of the resolution operation to perform. See BUILT-IN RESOLVERS for the
166             list of available operations.
167              
168             =item data => ARRAY
169              
170             Arguments to pass to the resolver function. Exact meaning depends on the
171             specific function chosen by the C; see BUILT-IN RESOLVERS.
172              
173             =item timeout => NUMBER
174              
175             Optional. Timeout in seconds, after which the resolver operation will abort
176             with a timeout exception. If not supplied, a default of 10 seconds will apply.
177              
178             =back
179              
180             On failure, the fail category name is C; the details give the
181             individual resolver function name (e.g. C), followed by other
182             error details specific to the resolver in question.
183              
184             ->fail( $message, resolve => $type => @details )
185              
186             =head2 resolve (void)
187              
188             $resolver->resolve( %params )
189              
190             When not returning a future, additional parameters can be given containing the
191             continuations to invoke on success or failure:
192              
193             =over 8
194              
195             =item on_resolved => CODE
196              
197             A continuation that is invoked when the resolver function returns a successful
198             result. It will be passed the array returned by the resolver function.
199              
200             $on_resolved->( @result )
201              
202             =item on_error => CODE
203              
204             A continuation that is invoked when the resolver function fails. It will be
205             passed the exception thrown by the function.
206              
207             =back
208              
209             =cut
210              
211             sub resolve
212             {
213 15     15 1 15102 my $self = shift;
214 15         89 my %args = @_;
215              
216 15         32 my $type = $args{type};
217 15 50       38 defined $type or croak "Expected 'type'";
218              
219 15 100       37 if( $type eq "getaddrinfo_hash" ) {
220 1         5 $type = "getaddrinfo";
221             }
222              
223 15 50       35 exists $METHODS{$type} or croak "Expected 'type' to be an existing resolver method, got '$type'";
224              
225 15         25 my $on_resolved;
226 15 100       48 if( $on_resolved = $args{on_resolved} ) {
    50          
227 7 50       17 ref $on_resolved or croak "Expected 'on_resolved' to be a reference";
228             }
229             elsif( !defined wantarray ) {
230 0         0 croak "Expected 'on_resolved' or to return a Future";
231             }
232              
233 15         24 my $on_error;
234 15 100       36 if( $on_error = $args{on_error} ) {
    50          
235 7 50       14 ref $on_error or croak "Expected 'on_error' to be a reference";
236             }
237             elsif( !defined wantarray ) {
238 0         0 croak "Expected 'on_error' or to return a Future";
239             }
240              
241 15   50     59 my $timeout = $args{timeout} || 10;
242              
243 15 100       72 $METRICS and $METRICS->inc_counter( resolver_lookups => [ type => $type ] );
244              
245             my $future = $self->call(
246 15         71 args => [ $type, $timeout, @{$args{data}} ],
247             )->else( sub {
248 1     1   34 my ( $message, @detail ) = @_;
249 1 50       6 $METRICS and $METRICS->inc_counter( resolver_failures => [ type => $type ] );
250 1         99 Future->fail( $message, resolve => $type => @detail );
251 15         1286 });
252              
253 15 100       620 $future->on_done( $on_resolved ) if $on_resolved;
254 15 100       169 $future->on_fail( $on_error ) if $on_error;
255              
256 15 100       270 return $future if defined wantarray;
257              
258             # Caller is not going to keep hold of the Future, so we have to ensure it
259             # stays alive somehow
260 7     0   24 $self->adopt_future( $future->else( sub { Future->done } ) );
  0         0  
261             }
262              
263             =head2 getaddrinfo
264              
265             @addrs = $resolver->getaddrinfo( %args )->get
266              
267             A shortcut wrapper around the C resolver, taking its arguments in
268             a more convenient form.
269              
270             =over 8
271              
272             =item host => STRING
273              
274             =item service => STRING
275              
276             The host and service names to look up. At least one must be provided.
277              
278             =item family => INT or STRING
279              
280             =item socktype => INT or STRING
281              
282             =item protocol => INT
283              
284             Hint values used to filter the results.
285              
286             =item flags => INT
287              
288             Flags to control the C function. See the C constants in
289             L's C function for more detail.
290              
291             =item passive => BOOL
292              
293             If true, sets the C flag. This is provided as a convenience to
294             avoid the caller from having to import the C constant from
295             C.
296              
297             =item timeout => NUMBER
298              
299             Time in seconds after which to abort the lookup with a C exception
300              
301             =back
302              
303             On success, the future will yield the result as a list of HASH references;
304             each containing one result. Each result will contain fields called C,
305             C, C and C. If requested by C then the
306             C field will also be present.
307              
308             On failure, the detail field will give the error number, which should match
309             one of the C constants.
310              
311             ->fail( $message, resolve => getaddrinfo => $eai_errno )
312              
313             As a specific optimisation, this method will try to perform a lookup of
314             numeric values synchronously, rather than asynchronously, if it looks likely
315             to succeed.
316              
317             Specifically, if the service name is entirely numeric, and the hostname looks
318             like an IPv4 or IPv6 string, a synchronous lookup will first be performed
319             using the C flag. If this gives an C error, then
320             the lookup is performed asynchronously instead.
321              
322             =head2 getaddrinfo (void)
323              
324             $resolver->getaddrinfo( %args )
325              
326             When not returning a future, additional parameters can be given containing the
327             continuations to invoke on success or failure:
328              
329             =over 8
330              
331             =item on_resolved => CODE
332              
333             Callback which is invoked after a successful lookup.
334              
335             $on_resolved->( @addrs )
336              
337             =item on_error => CODE
338              
339             Callback which is invoked after a failed lookup, including for a timeout.
340              
341             $on_error->( $exception )
342              
343             =back
344              
345             =cut
346              
347             sub getaddrinfo
348             {
349 16     16 1 8075 my $self = shift;
350 16         76 my %args = @_;
351              
352             $args{on_resolved} or defined wantarray or
353 16 50 66     96 croak "Expected 'on_resolved' or to return a Future";
354              
355             $args{on_error} or defined wantarray or
356 16 50 66     90 croak "Expected 'on_error' or to return a Future";
357              
358 16   100     58 my $host = $args{host} || "";
359 16 100       36 my $service = $args{service}; defined $service or $service = "";
  16         52  
360 16   50     72 my $flags = $args{flags} || 0;
361              
362 16 100       38 $flags |= AI_PASSIVE if $args{passive};
363              
364 16 100       100 $args{family} = IO::Async::OS->getfamilybyname( $args{family} ) if defined $args{family};
365 16 50       99 $args{socktype} = IO::Async::OS->getsocktypebyname( $args{socktype} ) if defined $args{socktype};
366              
367             # Clear any other existing but undefined hints
368 16   66     110 defined $args{$_} or delete $args{$_} for keys %args;
369              
370             # It's likely this will succeed with AI_NUMERICHOST if host contains only
371             # [\d.] (IPv4) or [[:xdigit:]:] (IPv6)
372             # Technically we should pass AI_NUMERICSERV but not all platforms support
373             # it, but since we're checking service contains only \d we should be fine.
374              
375             # These address tests don't have to be perfect as if it fails we'll get
376             # EAI_NONAME and just try it asynchronously anyway
377 16 100 100     233 if( ( $host =~ m/^[\d.]+$/ or $host =~ m/^[[:xdigit:]:]$/ or $host eq "" ) and
      66        
378             $service =~ m/^\d*$/ ) {
379              
380 12         558 my ( $err, @results ) = Socket::getaddrinfo( $host, $service,
381             { %args, flags => $flags | AI_NUMERICHOST }
382             );
383              
384 12 50       224 if( !$err ) {
    0          
385 12         45 my $future = $self->loop->new_future->done( @results );
386 12 100       525 $future->on_done( $args{on_resolved} ) if $args{on_resolved};
387 12         137 return $future;
388             }
389             elsif( $err == EAI_NONAME ) {
390             # fallthrough to async case
391             }
392             else {
393 0         0 my $future = $self->loop->new_future->fail( $err, resolve => getaddrinfo => $err+0 );
394 0 0       0 $future->on_fail( $args{on_error} ) if $args{on_error};
395 0         0 return $future;
396             }
397             }
398              
399             my $future = $self->resolve(
400             type => "getaddrinfo",
401             data => [
402             host => $host,
403             service => $service,
404             flags => $flags,
405 12 100       63 map { exists $args{$_} ? ( $_ => $args{$_} ) : () } qw( family socktype protocol ),
406             ],
407             timeout => $args{timeout},
408 4         21 );
409              
410 4 100       32 $future->on_done( $args{on_resolved} ) if $args{on_resolved};
411 4 100       45 $future->on_fail( $args{on_error} ) if $args{on_error};
412              
413 4 100       107 return $future if defined wantarray;
414              
415             # Caller is not going to keep hold of the Future, so we have to ensure it
416             # stays alive somehow
417 1     0   15 $self->adopt_future( $future->else( sub { Future->done } ) );
  0         0  
418             }
419              
420             =head2 getnameinfo
421              
422             ( $host, $service ) = $resolver->getnameinfo( %args )->get
423              
424             A shortcut wrapper around the C resolver, taking its arguments in
425             a more convenient form.
426              
427             =over 8
428              
429             =item addr => STRING
430              
431             The packed socket address to look up.
432              
433             =item flags => INT
434              
435             Flags to control the C function. See the C constants in
436             L's C for more detail.
437              
438             =item numerichost => BOOL
439              
440             =item numericserv => BOOL
441              
442             =item dgram => BOOL
443              
444             If true, set the C, C or C flags.
445              
446             =item numeric => BOOL
447              
448             If true, sets both C and C flags.
449              
450             =item timeout => NUMBER
451              
452             Time in seconds after which to abort the lookup with a C exception
453              
454             =back
455              
456             On failure, the detail field will give the error number, which should match
457             one of the C constants.
458              
459             ->fail( $message, resolve => getnameinfo => $eai_errno )
460              
461             As a specific optimisation, this method will try to perform a lookup of
462             numeric values synchronously, rather than asynchronously, if both the
463             C and C flags are given.
464              
465             =head2 getnameinfo (void)
466              
467             $resolver->getnameinfo( %args )
468              
469             When not returning a future, additional parameters can be given containing the
470             continuations to invoke on success or failure:
471              
472             =over 8
473              
474             =item on_resolved => CODE
475              
476             Callback which is invoked after a successful lookup.
477              
478             $on_resolved->( $host, $service )
479              
480             =item on_error => CODE
481              
482             Callback which is invoked after a failed lookup, including for a timeout.
483              
484             $on_error->( $exception )
485              
486             =back
487              
488             =cut
489              
490             sub getnameinfo
491             {
492 5     5 1 5194 my $self = shift;
493 5         28 my %args = @_;
494              
495             $args{on_resolved} or defined wantarray or
496 5 50 66     26 croak "Expected 'on_resolved' or to return a Future";
497              
498             $args{on_error} or defined wantarray or
499 5 50 66     21 croak "Expected 'on_error' or to return a Future";
500              
501 5   50     30 my $flags = $args{flags} || 0;
502              
503 5 50       16 $flags |= NI_NUMERICHOST if $args{numerichost};
504 5 50       11 $flags |= NI_NUMERICSERV if $args{numericserv};
505 5 50       13 $flags |= NI_DGRAM if $args{dgram};
506              
507 5 100       10 $flags |= NI_NUMERICHOST|NI_NUMERICSERV if $args{numeric};
508              
509 5 100       15 if( $flags & (NI_NUMERICHOST|NI_NUMERICSERV) ) {
510             # This is a numeric-only lookup that can be done synchronously
511 2         8 my ( $err, $host, $service ) = Socket::getnameinfo( $args{addr}, $flags );
512              
513 2 50       48 if( $err ) {
514 0         0 my $future = $self->loop->new_future->fail( $err, resolve => getnameinfo => $err+0 );
515 0 0       0 $future->on_fail( $args{on_error} ) if $args{on_error};
516 0         0 return $future;
517             }
518             else {
519 2         8 my $future = $self->loop->new_future->done( $host, $service );
520 2 100       74 $future->on_done( $args{on_resolved} ) if $args{on_resolved};
521 2         28 return $future;
522             }
523             }
524              
525             my $future = $self->resolve(
526             type => "getnameinfo",
527             data => [ $args{addr}, $flags ],
528             timeout => $args{timeout},
529             )->transform(
530 3     3   313 done => sub { @{ $_[0] } }, # unpack the ARRAY ref
  3         10  
531 3         15 );
532              
533 3 100       115 $future->on_done( $args{on_resolved} ) if $args{on_resolved};
534 3 100       41 $future->on_fail( $args{on_error} ) if $args{on_error};
535              
536 3 100       37 return $future if defined wantarray;
537              
538             # Caller is not going to keep hold of the Future, so we have to ensure it
539             # stays alive somehow
540 1     0   30 $self->adopt_future( $future->else( sub { Future->done } ) );
  0         0  
541             }
542              
543             =head1 FUNCTIONS
544              
545             =cut
546              
547             =head2 register_resolver( $name, $code )
548              
549             Registers a new named resolver function that can be called by the C
550             method. All named resolvers must be registered before the object is
551             constructed.
552              
553             =over 8
554              
555             =item $name
556              
557             The name of the resolver function; must be a plain string. This name will be
558             used by the C argument to the C method, to identify it.
559              
560             =item $code
561              
562             A CODE reference to the resolver function body. It will be called in list
563             context, being passed the list of arguments given in the C argument to
564             the C method. The returned list will be passed to the
565             C callback. If the code throws an exception at call time, it will
566             be passed to the C continuation. If it returns normally, the list of
567             values it returns will be passed to C.
568              
569             =back
570              
571             =cut
572              
573             # Plain function, not a method
574             sub register_resolver
575             {
576 105     105 1 175 my ( $name, $code ) = @_;
577              
578 105 50       202 croak "Cannot register new resolver methods once the resolver has been started" if $started;
579              
580 105 50       196 croak "Already have a resolver method called '$name'" if exists $METHODS{$name};
581 105         188 $METHODS{$name} = $code;
582             }
583              
584             =head1 BUILT-IN RESOLVERS
585              
586             The following resolver names are implemented by the same-named perl function,
587             taking and returning a list of values exactly as the perl function does:
588              
589             getpwnam getpwuid
590             getgrnam getgrgid
591             getservbyname getservbyport
592             gethostbyname gethostbyaddr
593             getnetbyname getnetbyaddr
594             getprotobyname getprotobynumber
595              
596             =cut
597              
598             # Now register the inbuilt methods
599              
600             register_resolver getpwnam => sub { my @r = getpwnam( $_[0] ) or die "$!\n"; @r };
601             register_resolver getpwuid => sub { my @r = getpwuid( $_[0] ) or die "$!\n"; @r };
602              
603             register_resolver getgrnam => sub { my @r = getgrnam( $_[0] ) or die "$!\n"; @r };
604             register_resolver getgrgid => sub { my @r = getgrgid( $_[0] ) or die "$!\n"; @r };
605              
606             register_resolver getservbyname => sub { my @r = getservbyname( $_[0], $_[1] ) or die "$!\n"; @r };
607             register_resolver getservbyport => sub { my @r = getservbyport( $_[0], $_[1] ) or die "$!\n"; @r };
608              
609             register_resolver gethostbyname => sub { my @r = gethostbyname( $_[0] ) or die "$!\n"; @r };
610             register_resolver gethostbyaddr => sub { my @r = gethostbyaddr( $_[0], $_[1] ) or die "$!\n"; @r };
611              
612             register_resolver getnetbyname => sub { my @r = getnetbyname( $_[0] ) or die "$!\n"; @r };
613             register_resolver getnetbyaddr => sub { my @r = getnetbyaddr( $_[0], $_[1] ) or die "$!\n"; @r };
614              
615             register_resolver getprotobyname => sub { my @r = getprotobyname( $_[0] ) or die "$!\n"; @r };
616             register_resolver getprotobynumber => sub { my @r = getprotobynumber( $_[0] ) or die "$!\n"; @r };
617              
618             =pod
619              
620             The following three resolver names are implemented using the L module.
621              
622             getaddrinfo
623             getaddrinfo_array
624             getnameinfo
625              
626             The C resolver takes arguments in a hash of name/value pairs and
627             returns a list of hash structures, as the C function
628             does. For neatness it takes all its arguments as named values; taking the host
629             and service names from arguments called C and C respectively;
630             all the remaining arguments are passed into the hints hash. This name is also
631             aliased as simply C.
632              
633             The C resolver behaves more like the C version of
634             the function. It takes hints in a flat list, and mangles the result of the
635             function, so that the returned value is more useful to the caller. It splits
636             up the list of 5-tuples into a list of ARRAY refs, where each referenced array
637             contains one of the tuples of 5 values.
638              
639             As an extra convenience to the caller, both resolvers will also accept plain
640             string names for the C argument, converting C and possibly
641             C into the appropriate C value, and for the C argument,
642             converting C, C or C into the appropriate C value.
643              
644             The C resolver returns its result in the same form as C.
645              
646             Because this module simply uses the system's C resolver, it will
647             be fully IPv6-aware if the underlying platform's resolver is. This allows
648             programs to be fully IPv6-capable.
649              
650             =cut
651              
652             register_resolver getaddrinfo => sub {
653             my %args = @_;
654              
655             my $host = delete $args{host};
656             my $service = delete $args{service};
657              
658             $args{family} = IO::Async::OS->getfamilybyname( $args{family} ) if defined $args{family};
659             $args{socktype} = IO::Async::OS->getsocktypebyname( $args{socktype} ) if defined $args{socktype};
660              
661             # Clear any other existing but undefined hints
662             defined $args{$_} or delete $args{$_} for keys %args;
663              
664             my ( $err, @addrs ) = Socket::getaddrinfo( $host, $service, \%args );
665              
666             die [ "$err", $err+0 ] if $err;
667              
668             return @addrs;
669             };
670              
671             register_resolver getaddrinfo_array => sub {
672             my ( $host, $service, $family, $socktype, $protocol, $flags ) = @_;
673              
674             $family = IO::Async::OS->getfamilybyname( $family );
675             $socktype = IO::Async::OS->getsocktypebyname( $socktype );
676              
677             my %hints;
678             $hints{family} = $family if defined $family;
679             $hints{socktype} = $socktype if defined $socktype;
680             $hints{protocol} = $protocol if defined $protocol;
681             $hints{flags} = $flags if defined $flags;
682              
683             my ( $err, @addrs ) = Socket::getaddrinfo( $host, $service, \%hints );
684              
685             die [ "$err", $err+0 ] if $err;
686              
687             # Convert the @addrs list into a list of ARRAY refs of 5 values each
688             return map {
689             [ $_->{family}, $_->{socktype}, $_->{protocol}, $_->{addr}, $_->{canonname} ]
690             } @addrs;
691             };
692              
693             register_resolver getnameinfo => sub {
694             my ( $addr, $flags ) = @_;
695              
696             my ( $err, $host, $service ) = Socket::getnameinfo( $addr, $flags || 0 );
697              
698             die [ "$err", $err+0 ] if $err;
699              
700             return [ $host, $service ];
701             };
702              
703             =head1 EXAMPLES
704              
705             The following somewhat contrieved example shows how to implement a new
706             resolver function. This example just uses in-memory data, but a real function
707             would likely make calls to OS functions to provide an answer. In traditional
708             Unix style, a pair of functions are provided that each look up the entity by
709             either type of key, where both functions return the same type of list. This is
710             purely a convention, and is in no way required or enforced by the
711             L itself.
712              
713             @numbers = qw( zero one two three four
714             five six seven eight nine );
715              
716             register_resolver getnumberbyindex => sub {
717             my ( $index ) = @_;
718             die "Bad index $index" unless $index >= 0 and $index < @numbers;
719             return ( $index, $numbers[$index] );
720             };
721              
722             register_resolver getnumberbyname => sub {
723             my ( $name ) = @_;
724             foreach my $index ( 0 .. $#numbers ) {
725             return ( $index, $name ) if $numbers[$index] eq $name;
726             }
727             die "Bad name $name";
728             };
729              
730             =head1 AUTHOR
731              
732             Paul Evans
733              
734             =cut
735              
736             0x55AA;