File Coverage

blib/lib/IO/Async/Resolver.pm
Criterion Covered Total %
statement 116 138 84.0
branch 66 96 68.7
condition 22 31 70.9
subroutine 17 22 77.2
pod 4 6 66.6
total 225 293 76.7


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