POE-Component-Resolver-0.920/000755 000765 000024 00000000000 11762546422 016031 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/CHANGES000644 000765 000024 00000016756 11762546422 017043 0ustar00trocstaff000000 000000 ================================================== Changes from 2011-06-04 00:00:00 +0000 to present. ================================================== ------------------------------------------ version 0.920 at 2012-06-03 02:33:52 +0000 ------------------------------------------ Change: 2a038120059176b412e51388ab1a6494c03a15d5 Author: Rocco Caputo Date : 2012-06-02 22:32:41 +0000 Remove use of Socket::GetAddrInfo. Socket 2.001 (and possibly earlier) expose getaddrinfo() so we don't need Socket::GetAddrInfo anymore. Also, the author of that module suggests that it's obsolete since Socket provides it now. ------------------------------------------ version 0.919 at 2012-06-02 06:40:46 +0000 ------------------------------------------ Change: 4a415e5228e7c41da67027e7821ad606a418363f Author: Rocco Caputo Date : 2012-06-02 02:40:46 +0000 Fix usage of Socket::GetAddrInfo for new version 0.22. avenj on irc.perl.org #poe pointed out that Socket::GetAddrInfo 0.22 changed public behavior and began throwing the following error. This change compensates for that and requires version 0.22 or later. :newapi tag is no longer supported by Socket::GetAddrInfo; just 'use' it directly ------------------------------------------ version 0.918 at 2012-05-15 15:12:36 +0000 ------------------------------------------ Change: 202dea1c77b0b80aae8a06eef5318f63cc189c9e Author: Rocco Caputo Date : 2012-05-15 11:12:36 +0000 Fix address untainting error. Reported by Alberto Simões on irc.perl.org and diagnosted with copious help from Chris Williams. Thanks, guys! ------------------------------------------ version 0.917 at 2012-05-13 08:45:53 +0000 ------------------------------------------ Change: 963f8fe5bf94ddabf53b9b40e9458aad2c895e46 Author: Rocco Caputo Date : 2012-05-13 04:45:53 +0000 [rt.cpan.org 76987] Add a cancel() method to stop pending requests. Modified resolve() to return a request ID. Added cancel(), which accepts a request ID and cancels it. POE::Component::Client::Keepalive requires it. Change: 34d54dbcb61ada8b0f97b4e145723be9dc35c542 Author: Rocco Caputo Date : 2012-05-05 19:01:15 +0000 [rt.cpan.org 76550] Avoid hang when no requests made. Fixed thanks to Sergei Kozunov's bug report and test case. A sidecar process was created at startup, but no idle timeout was set. These timeouts are only set when requests happen, and in an application where all addresses are already resolved, no timeout is set. So the component lingers forever. Change: accbcc5a17b77642325a00267e78d1506d936640 Author: Rocco Caputo Date : 2012-04-29 19:22:55 +0000 [rt.cpan.org 76549] Fix a little typo in sidecar cleanup. _poe_sidecar_closed() was destroying the wrong sidecar process. Thank you, Kozunov, for the code review and bug report. Two little octets can make a huge difference. Change: 401d307f6ea076b0152e51caf53717ebf7616c90 Author: Rocco Caputo Date : 2012-04-29 18:46:27 +0000 [rt.cpan.org 76314] Untaint addresses before Socket::GetAddrInfo. Dylan Doxey pointed out that Socket::GetAddrInfo rejects tainted addresses. Untaint them first, per his recommendation and test case. ------------------------------------------ version 0.916 at 2012-03-05 02:55:32 +0000 ------------------------------------------ Change: 9610861f210360b2d2a6d263a8b0f48a4d134bf4 Author: Rocco Caputo Date : 2012-03-04 21:55:32 +0000 Avoid a final timeout if the user explicitly shuts down everything. ------------------------------------------ version 0.915 at 2012-03-05 00:17:58 +0000 ------------------------------------------ Change: 466a7e1fa548913d59dcc454951c5bd0e293d551 Author: Rocco Caputo Date : 2012-03-04 19:17:58 +0000 Fix dist.ini and .gitignore to allow Git::Check to pass. Change: 3557a330346457155b951c505decc4152abaf0d3 Author: Rocco Caputo Date : 2012-03-04 18:48:42 +0000 [rt.cpan.org 67601] Test IPv6 availability against non-localhost. Larwan Burke pointed out that localhost is a bad name to resolve when testing for IPv6 availability. It tends to be in /etc/hosts, which resolves whether or not the named can. Change: 32d22afac0c59a6ad5f68dd690a5c17849a0536f Author: Rocco Caputo Date : 2012-03-04 17:06:56 +0000 [rt.cpan.org 74486] Fix a typo in a hash key. I hate hash-based data structures for just this reason: typos are silently ignored. Many thanks to Jon Portnoy for reporting this one. Change: 9c3be3c8497c043f7883136383c00ce42baa95d9 Author: Rocco Caputo Date : 2012-03-04 17:03:52 +0000 Add an environment variable to override the default IP version preference. As IPv6 is adopted, more and more places will behave differently than expected. The POCO_RESOLVER_IPV environment variable gives users a way to override the legacy behavior during this potentially painful transition. Change: 4ba15258e501c522856aa2f0e89ac3e429ae0ed3 Author: Rocco Caputo Date : 2012-03-04 17:03:01 +0000 Update dist.ini to automatically bump versions and tag releases. ------------------------------------------ version 0_914 at 2011-09-15 05:27:46 +0000 ------------------------------------------ Change: e4f3fb11df5d5bf43a1445edc498cb4ee75839ae Author: Rocco Caputo Date : 2011-09-15 01:27:46 +0000 Resolve a runaway fork when running in EmbedPerl. Sjors Gielen reported a runaway fork in irc.perl.org #poe. $^X is not always a path to the perl binary. Resolved by using Configure's notion of perlpath and _exe, per Sjors' recommendation and discussion of $^X in perlvar. ------------------------------------------ version 0_913 at 2011-07-30 08:02:15 +0000 ------------------------------------------ Change: b2d2af51183d837e5e98d7cf0324ea9429ef54dc Author: Rocco Caputo Date : 2011-07-30 04:02:15 +0000 Bump the release version. Change: 82af41c8ab2dd061acd7deb9979fa6cb5c4725ae Author: Rocco Caputo Date : 2011-07-30 03:49:09 +0000 Allow developers to use sidecar-based modules with PAR and other packagers. Applied a modified versin of a patch from Markus Jansen at Ericsson. It adds a parameter to POE::Component::Resolver so the developer can point to a custom sidecar program. That program can be bundld in PAR using special techniques. Thanks also go to Steffen Mueller and Roderich Schupp, who helped design the patch. Change: f0d9d4484e3a3400c7b9ca6b712882e0e20a04d6 Author: Rocco Caputo Date : 2011-07-29 08:59:32 +0000 Use port 80 rather than string "http" on Solaris. Chris Williams discovered tests would fail on Solaris with "service name not available for the specified socket type". It turns out they don't list "http" in /etc/services. Change: 638cd9e616a5b4ffbf413672784f41896de97430 Author: Rocco Caputo Date : 2011-07-29 00:44:00 +0000 Load the POE::Component::Resolver::Sidecar class. The presence of this module is requested on MSWin32. Resolves rt.cpan.org ticket 69172, reported by Gabor Szabo. ================================================ Plus 4 releases after 2011-06-04 00:00:00 +0000. ================================================ POE-Component-Resolver-0.920/dist.ini000644 000765 000024 00000001664 11762546422 017504 0ustar00trocstaff000000 000000 name = POE-Component-Resolver author = Rocco Caputo license = Perl_5 copyright_holder = Rocco Caputo [Prereqs] POE = 1.311 Scalar::Util = 1.23 Socket = 2.001 Storable = 2.18 Test::More = 0.96 Time::HiRes = 1.9711 [MetaResources] bugtracker = http://rt.cpan.org/Public/Dist/Display.html?POE-Component-Resolver repository = http://github.com/rcaputo/poe-component-resolver [Repository] git_remote = gh [ReadmeFromPod] [ReadmeMarkdownFromPod] [ReportVersions] ; Require everything to be checked in. [Git::Check] ; Calculate the release version. [Git::NextVersion] first_version = 0.915 version_regexp = ^v(\d+\.\d+)$ ; Generate the changelog. [ChangelogFromGit] tag_regexp = v(\d+[_.]\d+) ; Tag the repository after release. [Git::Tag] tag_format = v%v tag_message = Release %v. [@Classic] POE-Component-Resolver-0.920/lib/000755 000765 000024 00000000000 11762546422 016577 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/LICENSE000644 000765 000024 00000043514 11762546422 017045 0ustar00trocstaff000000 000000 This software is copyright (c) 2012 by Rocco Caputo. This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2012 by Rocco Caputo. This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2012 by Rocco Caputo. This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End POE-Component-Resolver-0.920/Makefile.PL000644 000765 000024 00000002177 11762546422 020012 0ustar00trocstaff000000 000000 use strict; use warnings; use ExtUtils::MakeMaker 6.30; my %WriteMakefileArgs = ( "ABSTRACT" => "A non-blocking getaddrinfo() resolver", "AUTHOR" => "Rocco Caputo ", "BUILD_REQUIRES" => {}, "CONFIGURE_REQUIRES" => { "ExtUtils::MakeMaker" => "6.30" }, "DISTNAME" => "POE-Component-Resolver", "EXE_FILES" => [], "LICENSE" => "perl", "NAME" => "POE::Component::Resolver", "PREREQ_PM" => { "POE" => "1.311", "Scalar::Util" => "1.23", "Socket" => "2.001", "Storable" => "2.18", "Test::More" => "0.96", "Time::HiRes" => "1.9711" }, "VERSION" => "0.920", "test" => { "TESTS" => "t/*.t" } ); unless ( eval { ExtUtils::MakeMaker->VERSION(6.56) } ) { my $br = delete $WriteMakefileArgs{BUILD_REQUIRES}; my $pp = $WriteMakefileArgs{PREREQ_PM}; for my $mod ( keys %$br ) { if ( exists $pp->{$mod} ) { $pp->{$mod} = $br->{$mod} if $br->{$mod} > $pp->{$mod}; } else { $pp->{$mod} = $br->{$mod}; } } } delete $WriteMakefileArgs{CONFIGURE_REQUIRES} unless eval { ExtUtils::MakeMaker->VERSION(6.52) }; WriteMakefile(%WriteMakefileArgs); POE-Component-Resolver-0.920/MANIFEST000644 000765 000024 00000000375 11762546422 017167 0ustar00trocstaff000000 000000 CHANGES LICENSE MANIFEST MANIFEST.SKIP META.yml Makefile.PL README README.mkdn dist.ini lib/POE/Component/Resolver.pm lib/POE/Component/Resolver/Sidecar.pm t/000-report-versions.t t/01-basic.t t/release-pod-coverage.t t/release-pod-syntax.t t/rt76550.t POE-Component-Resolver-0.920/MANIFEST.SKIP000600 000765 000024 00000000320 11762546422 017712 0ustar00trocstaff000000 000000 CVS \.\# \.bak$ \.cvsignore \.git \.gz$ \.orig$ \.patch$ \.ppd$ \.rej$ \.rej$ \.svn \.swo$ \.swp$ ^Makefile$ ^Makefile\.old$ ^\. ^_Inline ^_build ^blib/ ^comptest ^cover_db ^coverage\.report$ ^pm_to_blib$ ~$ POE-Component-Resolver-0.920/META.yml000644 000765 000024 00000001265 11762546422 017306 0ustar00trocstaff000000 000000 --- abstract: 'A non-blocking getaddrinfo() resolver' author: - 'Rocco Caputo ' build_requires: {} configure_requires: ExtUtils::MakeMaker: 6.30 dynamic_config: 0 generated_by: 'Dist::Zilla version 4.300016, CPAN::Meta::Converter version 2.120921' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: POE-Component-Resolver requires: POE: 1.311 Scalar::Util: 1.23 Socket: 2.001 Storable: 2.18 Test::More: 0.96 Time::HiRes: 1.9711 resources: bugtracker: http://rt.cpan.org/Public/Dist/Display.html?POE-Component-Resolver repository: git://github.com/rcaputo/poe-component-resolver.git version: 0.920 POE-Component-Resolver-0.920/README000644 000765 000024 00000025354 11762546422 016722 0ustar00trocstaff000000 000000 NAME POE::Component::Resolver - A non-blocking getaddrinfo() resolver VERSION version 0.920 SYNOPSIS #!/usr/bin/perl use warnings; use strict; use POE; use POE::Component::Resolver qw(AF_INET AF_INET6); my $r = POE::Component::Resolver->new( max_resolvers => 8, idle_timeout => 5, af_order => [ AF_INET6, AF_INET ], # sidecar_program => $path_to_program, ); my @hosts = qw( ipv6-test.com ); my $tcp = getprotobyname("tcp"); POE::Session->create( inline_states => { _start => sub { foreach my $host (@hosts) { $r->resolve( host => $host, service => "http", event => "got_response", hints => { protocol => $tcp }, ) or die $!; } }, _stop => sub { print "client session stopped\n" }, got_response => sub { my ($error, $addresses, $request) = @_[ARG0..ARG2]; use YAML; print YAML::Dump( { error => $error, addr => $addresses, req => $request, } ); }, } ); POE::Kernel->run(); DESCRIPTION POE::Component::Resolver performs Socket::getaddrinfo() calls in subprocesses where they're permitted to block as long as necessary. By default it will run eight subprocesses and prefer address families in whatever order getaddrinfo() returns them. These defaults can be overridden with constructor parameters. getaddrinfo() delegates to the operating system's resolver, which may be reconfigured according to the usual conventions. PUBLIC METHODS new Create a new resolver. Returns an object that must be held and used to make requests. See the synopsis. Accepts up to four optional named parameters. "af_order" may contain an arrayref with the address families to permit, in the order in which they're preferred. Without "af_order", the component will prefer IPv4 addresses over IPv6 for legacy compatibility. This may change in the future as IPv6 gains more widespread acceptance. See "ENVIRONMENT VARIABLES" for a way to override the default without hacking modules. # Prefer IPv6 addresses, but also return IPv4 ones. my $r1 = POE::Component::Resolver->new( af_order => [ AF_INET6, AF_INET ] ); # Only return IPv6 addresses, # or nothing in cases where only IPv4 addresses exist. my $r2 = POE::Component::Resolver->new( af_order => [ AF_INET6 ] ); "idle_timeout" determines how long to keep idle resolver subprocesses before cleaning them up, in seconds. It defaults to 15.0 seconds. "max_resolvers" controls the component's parallelism by defining the maximum number of sidecar processes to manage. It defaults to 8, but fewer or more processes can be configured depending on the resources you have available and the amount of parallelism you require. # One at a time, but without the pesky blocking. my $r3 = POE::Component::Resolver->new( max_resolvers => 1 ); "sidecar_program" contains the disk location of a program that will perform blocking lookups on standard input and print the results on standard output. The sidecar program is needed only in special environments where the bundling and execution of extra utilities is tricky. PAR is one such environment. The sidecar program needs to contain at least two statements: use POE::Component::Resolver::Sidecar; POE::Component::Resover::Sidecar->main(); resolve resolve() begins a new request to resolve a domain. The request will be enqueued in the component until a sidecar process can service it. resolve() returns a request ID that may be used to cancel() a request before it has completed (or undef if the request couldn't begin, such as during shutdown). Resolve requires two parameters and accepts some additional optional ones. "host" and "service" are required and contain the host (name or Internet address) and service (name or numeric port) that will be passed verbatim to getaddrinfo(). See Socket for details. "event" is optional; it contains the name of the event that will contain the resolver response. If omitted, it will default to "resolver_response"; you may want to specify a shorter event name. "hints" is optional. If specified, it must contain a hashref of hints exactly as getaddrinfo() expects them. See Socket for details. "misc" is optional continuation data that will be passed back in the response. It may contain any type of data the application requires. cancel Cancel a request, given the request's ID. my $request_id = $resolver->resolve("poe.dyndns.org", "http"); $resolver->cancel($request_id); shutdown Shut down the resolver. POE::Component::Resolver retains resources including child processes for up to "idle_timeout" seconds. This may keep programs running up to "idle_timeout" seconds longer than they should. POE::Component::Resolver will release its resources (including child processes) when its shutdown() method is called. unpack_addr In scalar context, unpack_addr($response_addr_hashref) returns the addr element of $response_addr_hashref in a numeric form appropriate for the address family of the address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my $numeric_addr = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } In list context, it returns the numeric port and address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my ($$numeric_addr, $port) = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } unpack_addr() is a convenience wrapper around getnameinfo() from Socket. You're certainly welcome to use the discrete function instead. unpack_addr() returns bleak emptiness on failure, regardless of context. You can check for undef return. PUBLIC EVENTS resolver_response The resolver response event includes three parameters. $_[ARG0] and $_[ARG1] contain the retrn values from Socket's getaddrinfo() call. These are an error message (if the call failed), and an arrayref of address structures if the call succeeded. The component provides its own error message, 'component shut down'. This response is given for every pending request at the time the user shuts down the component. $_[ARG2] contains a hashref of information provided to the resolve() method. Specifically, the values of resolve()'s "host", "service" and "misc" parameters. ENVIRONMENT VARIABLES POCO_RESOLVER_IPV The POCO_RESOLVER_IPV environment variable sets this component's default Internet Protocol Version search order. If the variable exists, it should contain a string with the numbers 4 and/or 6. POE::Component::Resolver will treate these as Internet Protocol versions to consider, in the order they are preferred. POE::Component::Resolver's new() method accepts an "af_order" parameter that overrides this environment variable. Default to IPv4 addresses only: export POCO_RESOLVER_IPV=4 Default to IPv6 addresses only: export POCO_RESOLVER_IPV=6 Prefer IPv6, but accept IPv4 if needed: export POCO_RESOLVER_IPV=64 Prefer IPv4, but accept IPv6 if needed: export POCO_RESOLVER_IPV=46 COMPATIBILITY ISSUES Microsoft Windows This module requires "Microsoft TCP/IP version 6" to be installed. Steps for Windows XP Pro (the steps for your particular version of Windows may be subtly or drastically different): * Open your Control Panel * Open your Network Connections * Select your network connection from the available one(s) * In the Local Area Connection Status dialog, click the Properties button * If "Microsoft TCP/IP version 6" is listed as an item being used, you are done. * Otherwise click Install... * Choose to add a Protocol * And install "Microsoft TCP/IP version 6" from the list of network protocols. BUGS There is no timeout on requests. There is no way to cancel a pending request. TROUBLESHOOTING programs linger for several seconds before exiting Programs should shutdown() their POE::Component::Resolver objects when they are through needing asynchronous DNS resolution. Programs should additionally destroy their resolvers if they intend to run awhile and want to reuse the memory they consume. In some cases, it may be necessary to shutdown components that perform asynchronous DNS using POE::Component::Resolver... such as POE::Component::IRC, POE::Component::Client::Keepalive and POE::Component::Client::HTTP. By default, the resolver subprocesses hang around for idle_timeout, which defaults to 15.0 seconds. Destroying the Resolver object will clean up the process pool. Assuming only that is keeping the event loop active, the program will then exit cleanly. Alternatively, reduce idle_timeout to a more manageable number, such as 5.0 seconds. Otherwise something else may also be keeping the event loop active. LICENSE Except where otherwise noted, this distribution is Copyright 2011 by Rocco Caputo. All rights reserved. This distribution is free software; you may redistribute it and/or modify it under the same terms as Perl itself. POE-Component-Resolver-0.920/README.mkdn000644 000765 000024 00000022052 11762546422 017642 0ustar00trocstaff000000 000000 # NAME POE::Component::Resolver - A non-blocking getaddrinfo() resolver # VERSION version 0.920 # SYNOPSIS #!/usr/bin/perl use warnings; use strict; use POE; use POE::Component::Resolver qw(AF_INET AF_INET6); my $r = POE::Component::Resolver->new( max_resolvers => 8, idle_timeout => 5, af_order => [ AF_INET6, AF_INET ], # sidecar_program => $path_to_program, ); my @hosts = qw( ipv6-test.com ); my $tcp = getprotobyname("tcp"); POE::Session->create( inline_states => { _start => sub { foreach my $host (@hosts) { $r->resolve( host => $host, service => "http", event => "got_response", hints => { protocol => $tcp }, ) or die $!; } }, _stop => sub { print "client session stopped\n" }, got_response => sub { my ($error, $addresses, $request) = @_[ARG0..ARG2]; use YAML; print YAML::Dump( { error => $error, addr => $addresses, req => $request, } ); }, } ); POE::Kernel->run(); # DESCRIPTION POE::Component::Resolver performs Socket::getaddrinfo() calls in subprocesses where they're permitted to block as long as necessary. By default it will run eight subprocesses and prefer address families in whatever order getaddrinfo() returns them. These defaults can be overridden with constructor parameters. getaddrinfo() delegates to the operating system's resolver, which may be reconfigured according to the usual conventions. ## PUBLIC METHODS ### new Create a new resolver. Returns an object that must be held and used to make requests. See the synopsis. Accepts up to four optional named parameters. "af_order" may contain an arrayref with the address families to permit, in the order in which they're preferred. Without "af_order", the component will prefer IPv4 addresses over IPv6 for legacy compatibility. This may change in the future as IPv6 gains more widespread acceptance. See [ENVIRONMENT VARIABLES](#pod_ENVIRONMENT VARIABLES) for a way to override the default without hacking modules. # Prefer IPv6 addresses, but also return IPv4 ones. my $r1 = POE::Component::Resolver->new( af_order => [ AF_INET6, AF_INET ] ); # Only return IPv6 addresses, # or nothing in cases where only IPv4 addresses exist. my $r2 = POE::Component::Resolver->new( af_order => [ AF_INET6 ] ); "idle_timeout" determines how long to keep idle resolver subprocesses before cleaning them up, in seconds. It defaults to 15.0 seconds. "max_resolvers" controls the component's parallelism by defining the maximum number of sidecar processes to manage. It defaults to 8, but fewer or more processes can be configured depending on the resources you have available and the amount of parallelism you require. # One at a time, but without the pesky blocking. my $r3 = POE::Component::Resolver->new( max_resolvers => 1 ); "sidecar_program" contains the disk location of a program that will perform blocking lookups on standard input and print the results on standard output. The sidecar program is needed only in special environments where the bundling and execution of extra utilities is tricky. PAR is one such environment. The sidecar program needs to contain at least two statements: use POE::Component::Resolver::Sidecar; POE::Component::Resover::Sidecar->main(); ### resolve resolve() begins a new request to resolve a domain. The request will be enqueued in the component until a sidecar process can service it. resolve() returns a request ID that may be used to cancel() a request before it has completed (or undef if the request couldn't begin, such as during shutdown). Resolve requires two parameters and accepts some additional optional ones. "host" and "service" are required and contain the host (name or Internet address) and service (name or numeric port) that will be passed verbatim to getaddrinfo(). See [Socket](http://search.cpan.org/perldoc?Socket) for details. "event" is optional; it contains the name of the event that will contain the resolver response. If omitted, it will default to "resolver_response"; you may want to specify a shorter event name. "hints" is optional. If specified, it must contain a hashref of hints exactly as getaddrinfo() expects them. See [Socket](http://search.cpan.org/perldoc?Socket) for details. "misc" is optional continuation data that will be passed back in the response. It may contain any type of data the application requires. ### cancel Cancel a request, given the request's ID. my $request_id = $resolver->resolve("poe.dyndns.org", "http"); $resolver->cancel($request_id); ### shutdown Shut down the resolver. POE::Component::Resolver retains resources including child processes for up to "idle_timeout" seconds. This may keep programs running up to "idle_timeout" seconds longer than they should. POE::Component::Resolver will release its resources (including child processes) when its shutdown() method is called. ### unpack_addr In scalar context, unpack_addr($response_addr_hashref) returns the addr element of $response_addr_hashref in a numeric form appropriate for the address family of the address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my $numeric_addr = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } In list context, it returns the numeric port and address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my ($$numeric_addr, $port) = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } unpack_addr() is a convenience wrapper around getnameinfo() from [Socket](http://search.cpan.org/perldoc?Socket). You're certainly welcome to use the discrete function instead. unpack_addr() returns bleak emptiness on failure, regardless of context. You can check for undef return. ## PUBLIC EVENTS ### resolver_response The resolver response event includes three parameters. $_[ARG0] and $_[ARG1] contain the retrn values from Socket's getaddrinfo() call. These are an error message (if the call failed), and an arrayref of address structures if the call succeeded. The component provides its own error message, 'component shut down'. This response is given for every pending request at the time the user shuts down the component. $_[ARG2] contains a hashref of information provided to the resolve() method. Specifically, the values of resolve()'s "host", "service" and "misc" parameters. # ENVIRONMENT VARIABLES ## POCO_RESOLVER_IPV The POCO_RESOLVER_IPV environment variable sets this component's default Internet Protocol Version search order. If the variable exists, it should contain a string with the numbers 4 and/or 6. POE::Component::Resolver will treate these as Internet Protocol versions to consider, in the order they are preferred. POE::Component::Resolver's new() method accepts an "af_order" parameter that overrides this environment variable. Default to IPv4 addresses only: export POCO_RESOLVER_IPV=4 Default to IPv6 addresses only: export POCO_RESOLVER_IPV=6 Prefer IPv6, but accept IPv4 if needed: export POCO_RESOLVER_IPV=64 Prefer IPv4, but accept IPv6 if needed: export POCO_RESOLVER_IPV=46 # COMPATIBILITY ISSUES ## Microsoft Windows This module requires "Microsoft TCP/IP version 6" to be installed. Steps for Windows XP Pro (the steps for your particular version of Windows may be subtly or drastically different): - * Open your Control Panel - * Open your Network Connections - * Select your network connection from the available one(s) - * In the Local Area Connection Status dialog, click the Properties button - * If "Microsoft TCP/IP version 6" is listed as an item being used, you are done. - * Otherwise click Install... - * Choose to add a Protocol - * And install "Microsoft TCP/IP version 6" from the list of network protocols. # BUGS There is no timeout on requests. There is no way to cancel a pending request. # TROUBLESHOOTING ## programs linger for several seconds before exiting Programs should shutdown() their POE::Component::Resolver objects when they are through needing asynchronous DNS resolution. Programs should additionally destroy their resolvers if they intend to run awhile and want to reuse the memory they consume. In some cases, it may be necessary to shutdown components that perform asynchronous DNS using POE::Component::Resolver... such as POE::Component::IRC, POE::Component::Client::Keepalive and POE::Component::Client::HTTP. By default, the resolver subprocesses hang around for idle_timeout, which defaults to 15.0 seconds. Destroying the Resolver object will clean up the process pool. Assuming only that is keeping the event loop active, the program will then exit cleanly. Alternatively, reduce idle_timeout to a more manageable number, such as 5.0 seconds. Otherwise something else may also be keeping the event loop active. # LICENSE Except where otherwise noted, this distribution is Copyright 2011 by Rocco Caputo. All rights reserved. This distribution is free software; you may redistribute it and/or modify it under the same terms as Perl itself.POE-Component-Resolver-0.920/t/000755 000765 000024 00000000000 11762546422 016274 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/t/000-report-versions.t000644 000765 000024 00000031270 11762546422 022142 0ustar00trocstaff000000 000000 #!perl use warnings; use strict; use Test::More 0.94; # Include a cut-down version of YAML::Tiny so we don't introduce unnecessary # dependencies ourselves. package Local::YAML::Tiny; use strict; use Carp 'croak'; # UTF Support? sub HAVE_UTF8 () { $] >= 5.007003 } BEGIN { if ( HAVE_UTF8 ) { # The string eval helps hide this from Test::MinimumVersion eval "require utf8;"; die "Failed to load UTF-8 support" if $@; } # Class structure require 5.004; $YAML::Tiny::VERSION = '1.40'; # Error storage $YAML::Tiny::errstr = ''; } # Printable characters for escapes my %UNESCAPES = ( z => "\x00", a => "\x07", t => "\x09", n => "\x0a", v => "\x0b", f => "\x0c", r => "\x0d", e => "\x1b", '\\' => '\\', ); ##################################################################### # Implementation # Create an empty YAML::Tiny object sub new { my $class = shift; bless [ @_ ], $class; } # Create an object from a file sub read { my $class = ref $_[0] ? ref shift : shift; # Check the file my $file = shift or return $class->_error( 'You did not specify a file name' ); return $class->_error( "File '$file' does not exist" ) unless -e $file; return $class->_error( "'$file' is a directory, not a file" ) unless -f _; return $class->_error( "Insufficient permissions to read '$file'" ) unless -r _; # Slurp in the file local $/ = undef; local *CFG; unless ( open(CFG, $file) ) { return $class->_error("Failed to open file '$file': $!"); } my $contents = ; unless ( close(CFG) ) { return $class->_error("Failed to close file '$file': $!"); } $class->read_string( $contents ); } # Create an object from a string sub read_string { my $class = ref $_[0] ? ref shift : shift; my $self = bless [], $class; my $string = $_[0]; unless ( defined $string ) { return $self->_error("Did not provide a string to load"); } # Byte order marks # NOTE: Keeping this here to educate maintainers # my %BOM = ( # "\357\273\277" => 'UTF-8', # "\376\377" => 'UTF-16BE', # "\377\376" => 'UTF-16LE', # "\377\376\0\0" => 'UTF-32LE' # "\0\0\376\377" => 'UTF-32BE', # ); if ( $string =~ /^(?:\376\377|\377\376|\377\376\0\0|\0\0\376\377)/ ) { return $self->_error("Stream has a non UTF-8 BOM"); } else { # Strip UTF-8 bom if found, we'll just ignore it $string =~ s/^\357\273\277//; } # Try to decode as utf8 utf8::decode($string) if HAVE_UTF8; # Check for some special cases return $self unless length $string; unless ( $string =~ /[\012\015]+\z/ ) { return $self->_error("Stream does not end with newline character"); } # Split the file into lines my @lines = grep { ! /^\s*(?:\#.*)?\z/ } split /(?:\015{1,2}\012|\015|\012)/, $string; # Strip the initial YAML header @lines and $lines[0] =~ /^\%YAML[: ][\d\.]+.*\z/ and shift @lines; # A nibbling parser while ( @lines ) { # Do we have a document header? if ( $lines[0] =~ /^---\s*(?:(.+)\s*)?\z/ ) { # Handle scalar documents shift @lines; if ( defined $1 and $1 !~ /^(?:\#.+|\%YAML[: ][\d\.]+)\z/ ) { push @$self, $self->_read_scalar( "$1", [ undef ], \@lines ); next; } } if ( ! @lines or $lines[0] =~ /^(?:---|\.\.\.)/ ) { # A naked document push @$self, undef; while ( @lines and $lines[0] !~ /^---/ ) { shift @lines; } } elsif ( $lines[0] =~ /^\s*\-/ ) { # An array at the root my $document = [ ]; push @$self, $document; $self->_read_array( $document, [ 0 ], \@lines ); } elsif ( $lines[0] =~ /^(\s*)\S/ ) { # A hash at the root my $document = { }; push @$self, $document; $self->_read_hash( $document, [ length($1) ], \@lines ); } else { croak("YAML::Tiny failed to classify the line '$lines[0]'"); } } $self; } # Deparse a scalar string to the actual scalar sub _read_scalar { my ($self, $string, $indent, $lines) = @_; # Trim trailing whitespace $string =~ s/\s*\z//; # Explitic null/undef return undef if $string eq '~'; # Quotes if ( $string =~ /^\'(.*?)\'\z/ ) { return '' unless defined $1; $string = $1; $string =~ s/\'\'/\'/g; return $string; } if ( $string =~ /^\"((?:\\.|[^\"])*)\"\z/ ) { # Reusing the variable is a little ugly, # but avoids a new variable and a string copy. $string = $1; $string =~ s/\\"/"/g; $string =~ s/\\([never\\fartz]|x([0-9a-fA-F]{2}))/(length($1)>1)?pack("H2",$2):$UNESCAPES{$1}/gex; return $string; } # Special cases if ( $string =~ /^[\'\"!&]/ ) { croak("YAML::Tiny does not support a feature in line '$lines->[0]'"); } return {} if $string eq '{}'; return [] if $string eq '[]'; # Regular unquoted string return $string unless $string =~ /^[>|]/; # Error croak("YAML::Tiny failed to find multi-line scalar content") unless @$lines; # Check the indent depth $lines->[0] =~ /^(\s*)/; $indent->[-1] = length("$1"); if ( defined $indent->[-2] and $indent->[-1] <= $indent->[-2] ) { croak("YAML::Tiny found bad indenting in line '$lines->[0]'"); } # Pull the lines my @multiline = (); while ( @$lines ) { $lines->[0] =~ /^(\s*)/; last unless length($1) >= $indent->[-1]; push @multiline, substr(shift(@$lines), length($1)); } my $j = (substr($string, 0, 1) eq '>') ? ' ' : "\n"; my $t = (substr($string, 1, 1) eq '-') ? '' : "\n"; return join( $j, @multiline ) . $t; } # Parse an array sub _read_array { my ($self, $array, $indent, $lines) = @_; while ( @$lines ) { # Check for a new document if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) { while ( @$lines and $lines->[0] !~ /^---/ ) { shift @$lines; } return 1; } # Check the indent level $lines->[0] =~ /^(\s*)/; if ( length($1) < $indent->[-1] ) { return 1; } elsif ( length($1) > $indent->[-1] ) { croak("YAML::Tiny found bad indenting in line '$lines->[0]'"); } if ( $lines->[0] =~ /^(\s*\-\s+)[^\'\"]\S*\s*:(?:\s+|$)/ ) { # Inline nested hash my $indent2 = length("$1"); $lines->[0] =~ s/-/ /; push @$array, { }; $self->_read_hash( $array->[-1], [ @$indent, $indent2 ], $lines ); } elsif ( $lines->[0] =~ /^\s*\-(\s*)(.+?)\s*\z/ ) { # Array entry with a value shift @$lines; push @$array, $self->_read_scalar( "$2", [ @$indent, undef ], $lines ); } elsif ( $lines->[0] =~ /^\s*\-\s*\z/ ) { shift @$lines; unless ( @$lines ) { push @$array, undef; return 1; } if ( $lines->[0] =~ /^(\s*)\-/ ) { my $indent2 = length("$1"); if ( $indent->[-1] == $indent2 ) { # Null array entry push @$array, undef; } else { # Naked indenter push @$array, [ ]; $self->_read_array( $array->[-1], [ @$indent, $indent2 ], $lines ); } } elsif ( $lines->[0] =~ /^(\s*)\S/ ) { push @$array, { }; $self->_read_hash( $array->[-1], [ @$indent, length("$1") ], $lines ); } else { croak("YAML::Tiny failed to classify line '$lines->[0]'"); } } elsif ( defined $indent->[-2] and $indent->[-1] == $indent->[-2] ) { # This is probably a structure like the following... # --- # foo: # - list # bar: value # # ... so lets return and let the hash parser handle it return 1; } else { croak("YAML::Tiny failed to classify line '$lines->[0]'"); } } return 1; } # Parse an array sub _read_hash { my ($self, $hash, $indent, $lines) = @_; while ( @$lines ) { # Check for a new document if ( $lines->[0] =~ /^(?:---|\.\.\.)/ ) { while ( @$lines and $lines->[0] !~ /^---/ ) { shift @$lines; } return 1; } # Check the indent level $lines->[0] =~ /^(\s*)/; if ( length($1) < $indent->[-1] ) { return 1; } elsif ( length($1) > $indent->[-1] ) { croak("YAML::Tiny found bad indenting in line '$lines->[0]'"); } # Get the key unless ( $lines->[0] =~ s/^\s*([^\'\" ][^\n]*?)\s*:(\s+|$)// ) { if ( $lines->[0] =~ /^\s*[?\'\"]/ ) { croak("YAML::Tiny does not support a feature in line '$lines->[0]'"); } croak("YAML::Tiny failed to classify line '$lines->[0]'"); } my $key = $1; # Do we have a value? if ( length $lines->[0] ) { # Yes $hash->{$key} = $self->_read_scalar( shift(@$lines), [ @$indent, undef ], $lines ); } else { # An indent shift @$lines; unless ( @$lines ) { $hash->{$key} = undef; return 1; } if ( $lines->[0] =~ /^(\s*)-/ ) { $hash->{$key} = []; $self->_read_array( $hash->{$key}, [ @$indent, length($1) ], $lines ); } elsif ( $lines->[0] =~ /^(\s*)./ ) { my $indent2 = length("$1"); if ( $indent->[-1] >= $indent2 ) { # Null hash entry $hash->{$key} = undef; } else { $hash->{$key} = {}; $self->_read_hash( $hash->{$key}, [ @$indent, length($1) ], $lines ); } } } } return 1; } # Set error sub _error { $YAML::Tiny::errstr = $_[1]; undef; } # Retrieve error sub errstr { $YAML::Tiny::errstr; } ##################################################################### # Use Scalar::Util if possible, otherwise emulate it BEGIN { eval { require Scalar::Util; }; if ( $@ ) { # Failed to load Scalar::Util eval <<'END_PERL'; sub refaddr { my $pkg = ref($_[0]) or return undef; if (!!UNIVERSAL::can($_[0], 'can')) { bless $_[0], 'Scalar::Util::Fake'; } else { $pkg = undef; } "$_[0]" =~ /0x(\w+)/; my $i = do { local $^W; hex $1 }; bless $_[0], $pkg if defined $pkg; $i; } END_PERL } else { Scalar::Util->import('refaddr'); } } ##################################################################### # main test ##################################################################### package main; BEGIN { # Skip modules that either don't want to be loaded directly, such as # Module::Install, or that mess with the test count, such as the Test::* # modules listed here. # # Moose::Role conflicts if Moose is loaded as well, but Moose::Role is in # the Moose distribution and it's certain that someone who uses # Moose::Role also uses Moose somewhere, so if we disallow Moose::Role, # we'll still get the relevant version number. my %skip = map { $_ => 1 } qw( App::FatPacker Class::Accessor::Classy Devel::Cover Module::Install Moose::Role POE::Loop::Tk Template::Test Test::Kwalitee Test::Pod::Coverage Test::Portability::Files Test::YAML::Meta open ); my $Test = Test::Builder->new; $Test->plan(skip_all => "META.yml could not be found") unless -f 'META.yml' and -r _; my $meta = (Local::YAML::Tiny->read('META.yml'))->[0]; my %requires; for my $require_key (grep { /requires/ } keys %$meta) { my %h = %{ $meta->{$require_key} }; $requires{$_}++ for keys %h; } delete $requires{perl}; diag("Testing with Perl $], $^X"); for my $module (sort keys %requires) { if ($skip{$module}) { note "$module doesn't want to be loaded directly, skipping"; next; } local $SIG{__WARN__} = sub { note "$module: $_[0]" }; require_ok $module or BAIL_OUT("can't load $module"); my $version = $module->VERSION; $version = 'undefined' unless defined $version; diag(" $module version is $version"); } done_testing; } POE-Component-Resolver-0.920/t/01-basic.t000644 000765 000024 00000005174 11762546422 017767 0ustar00trocstaff000000 000000 #!/usr/bin/perl use warnings; use strict; use Test::More tests => 4; sub POE::Kernel::ASSERT_DEFAULT () { 1 } use POE; use POE::Component::Resolver qw(AF_INET AF_INET6); my $r4 = POE::Component::Resolver->new( max_resolvers => 1, idle_timeout => 1, af_order => [ AF_INET ], ); # Try to detect whether we can resolve IPv6 addresses at all. use Socket qw(getaddrinfo); my $has_ipv6 = do { my ($error, @addresses) = getaddrinfo( "ipv6.test-ipv6.com", "www", { family => AF_INET6 } ); ($error or not @addresses) ? 0 : 1; }; # If we can't, don't bother setting up resolvers for them. my ($r6, $r46, $r64); if ($has_ipv6) { $r6 = POE::Component::Resolver->new( max_resolvers => 1, idle_timeout => 1, af_order => [ AF_INET6 ], ); $r46 = POE::Component::Resolver->new( max_resolvers => 1, idle_timeout => 1, af_order => [ AF_INET, AF_INET6 ], ); $r64 = POE::Component::Resolver->new( max_resolvers => 1, idle_timeout => 1, af_order => [ AF_INET6, AF_INET ], ); } # TODO - Not robust to try a single remote host. I fully expect this # to bite me later, unless someone wants to take a shot at it. my $host = 'ipv6-test.com'; my $tcp = getprotobyname("tcp"); my $service = $^O eq 'solaris' ? 80 : 'http'; POE::Session->create( inline_states => { _start => sub { $r4->resolve( host => $host, service => $service,, hints => { protocol => $tcp }, misc => [ AF_INET ], ) or die $!; SKIP: { skip("IPv6 not detected; skipping IPv6 tests", 3) unless $has_ipv6; $r6->resolve( host => $host, service => $service, hints => { protocol => $tcp }, misc => [ AF_INET6 ], ) or die $!; $r46->resolve( host => $host, service => $service, hints => { protocol => $tcp }, misc => [ AF_INET, AF_INET6 ], ) or die $!; $r64->resolve( host => $host, service => $service, hints => { protocol => $tcp }, misc => [ AF_INET6, AF_INET ], ) or die $!; } }, resolver_response => sub { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { diag("$request->{host} = ", scalar($r4->unpack_addr($a))); } my $expected_families = $request->{misc}; my @got_families = map { $_->{family} } @$addresses; my $i = $#got_families; while ($i--) { splice(@got_families, $i, 1) if ( $got_families[$i] == $got_families[$i+1] ); } is_deeply( \@got_families, $expected_families, "address families are as expected (@$expected_families)", ); }, _stop => sub { undef }, # for ASSERT_DEFAULT }, ); POE::Kernel->run(); POE-Component-Resolver-0.920/t/release-pod-coverage.t000644 000765 000024 00000000765 11762546422 022462 0ustar00trocstaff000000 000000 #!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod::Coverage 1.08"; plan skip_all => "Test::Pod::Coverage 1.08 required for testing POD coverage" if $@; eval "use Pod::Coverage::TrustPod"; plan skip_all => "Pod::Coverage::TrustPod required for testing POD coverage" if $@; all_pod_coverage_ok({ coverage_class => 'Pod::Coverage::TrustPod' }); POE-Component-Resolver-0.920/t/release-pod-syntax.t000644 000765 000024 00000000450 11762546422 022204 0ustar00trocstaff000000 000000 #!perl BEGIN { unless ($ENV{RELEASE_TESTING}) { require Test::More; Test::More::plan(skip_all => 'these tests are for release candidate testing'); } } use Test::More; eval "use Test::Pod 1.41"; plan skip_all => "Test::Pod 1.41 required for testing POD" if $@; all_pod_files_ok(); POE-Component-Resolver-0.920/t/rt76550.t000644 000765 000024 00000002114 11762546422 017513 0ustar00trocstaff000000 000000 #!/usr/bin/perl -w use utf8; use warnings; use strict; sub POE::Kernel::ASSERT_DEFAULT () { 1 } sub POE::Kernel::CATCH_EXCEPTIONS () { 0 } use Test::More tests => 1; use POE; use POE::Component::Resolver; my $responses = 0; my $resolver = POE::Component::Resolver->new( max_resolvers => 1, idle_timeout => 2, ); POE::Session->create( inline_states => { _start => sub { $_[KERNEL]->alias_set("client_session"); }, shutdown => sub { $_[KERNEL]->alias_remove("client_session"); }, _stop => sub { $resolver = undef; }, }, ); # The test fails when it doesn't exit promptly (if at all). # Use SIGALRM to detect the condition, with enough time for even slow # computers to finish normally... I hope. my $timed_out = 0; alarm(5); $SIG{ALRM} = sub { $timed_out = 1; POE::Kernel->post( client_session => 'shutdown' ); }; POE::Kernel->run(); # There is a bit of a race condition here. The alarm could go off # somewhere in POE::Kernel->run() as it's trying to clean up. What # can be done to mitigate that? alarm(0); is($timed_out, 0, "exited normally"); exit; POE-Component-Resolver-0.920/lib/POE/000755 000765 000024 00000000000 11762546422 017222 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/lib/POE/Component/000755 000765 000024 00000000000 11762546422 021164 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/lib/POE/Component/Resolver/000755 000765 000024 00000000000 11762546422 022765 5ustar00trocstaff000000 000000 POE-Component-Resolver-0.920/lib/POE/Component/Resolver.pm000644 000765 000024 00000055126 11762546422 023334 0ustar00trocstaff000000 000000 package POE::Component::Resolver; { $POE::Component::Resolver::VERSION = '0.920'; } use warnings; use strict; use POE qw(Wheel::Run Filter::Reference); use Carp qw(croak carp); use Time::HiRes qw(time); use Socket qw( unpack_sockaddr_in AF_INET AF_INET6 getnameinfo NI_NUMERICSERV NI_NUMERICHOST ); use POE::Component::Resolver::Sidecar; use Exporter; use base 'Exporter'; our (@EXPORT_OK) = qw(AF_INET AF_INET6); my $next_alias_index = "aaaaaaaa"; # Determine Perl's location, per perldoc perlvar's treatment of $^X. use Config; my $perl_path = $Config{perlpath}; if ($^O ne 'VMS') { $perl_path .= $Config{_exe} unless ( $perl_path =~ /$Config{_exe}$/i ); } # Plain Perl constructor. sub new { my ($class, @args) = @_; croak "new() requires an even number of parameters" if @args % 2; my %args = @args; my $max_resolvers = delete($args{max_resolvers}) || 8; my $idle_timeout = delete($args{idle_timeout}) || 15; my $debug = delete($args{debug}) || 0; my $sidecar_program = delete($args{sidecar_program}); my $af_order = delete($args{af_order}); if (defined $af_order and @$af_order) { if (ref($af_order) eq "") { $af_order = [ $af_order ]; } elsif (ref($af_order) ne "ARRAY") { croak "af_order must be a scalar or an array reference"; } my @illegal_afs = grep { ($_ ne AF_INET) && ($_ ne AF_INET6) } @$af_order; croak "af_order may only contain AF_INET and/or AF_INET6" if @illegal_afs; } elsif (exists $ENV{POCO_RESOLVER_IPV}) { my %number_to_address_family = ( 4 => AF_INET, 6 => AF_INET6 ); $af_order = [ map { $number_to_address_family{$_} } ($ENV{POCO_RESOLVER_IPV} =~ m/([46])/g) ]; } unless ($af_order and @$af_order) { # Default to IPv4 preference for backward compatibility. $af_order = [ AF_INET, AF_INET6 ]; } my @error = sort keys %args; croak "unknown new() parameter(s): @error" if @error; unless (defined $sidecar_program and length $sidecar_program) { if ($^O eq "MSWin32") { $sidecar_program = \&POE::Component::Resolver::Sidecar::main; } else { $sidecar_program = [ $perl_path, (map { "-I$_" } @INC), '-MPOE::Component::Resolver::Sidecar', '-e', 'POE::Component::Resolver::Sidecar->main()' ]; } } my $self = bless { alias => "poe_component_resolver_" . $next_alias_index++, debug => $debug, }, $class; POE::Session->create( inline_states => { _start => \&_poe_start, _stop => \&_poe_stop, _parent => sub { undef }, # for ASSERT_DEFAULT _child => sub { undef }, # for ASSERT_DEFAULT request => \&_poe_request, shutdown => \&_poe_shutdown, cancel => \&_poe_cancel, sidecar_closed => \&_poe_sidecar_closed, sidecar_error => \&_poe_sidecar_error, sidecar_response => \&_poe_sidecar_response, sidecar_signal => \&_poe_sidecar_signal, sidecar_eject => \&_poe_sidecar_eject, sidecar_attach => \&_poe_sidecar_attach, }, heap => { af_order => $af_order, alias => $self->{alias}, idle_timeout => $idle_timeout, last_request_id => 0, max_resolvers => $max_resolvers, requests => { }, sidecar_ring => [ ], sidecar_program => $sidecar_program, debug => $debug, } ); return $self; } sub DESTROY { my $self = shift; # Can't resolve the session: it must already be gone. return unless $poe_kernel->alias_resolve($self->{alias}); carp " destroying $self->{alias}" if $self->{debug}; $poe_kernel->call($self->{alias}, "shutdown"); } sub _poe_stop { my $heap = $_[HEAP]; carp " stopping $heap->{alias}" if $heap->{debug}; } sub shutdown { my $self = shift; # Can't resolve the session: it must already be gone. return unless $poe_kernel->alias_resolve($self->{alias}); carp " got shutdown request for $self->{alias}" if $self->{debug}; $poe_kernel->call($self->{alias}, "shutdown"); } # Internal POE event handler to release all resources owned by the # hidden POE::Session and then shut it down. It's an event handler so # that this code can run "within" the POE::Session. sub _poe_shutdown { my ($kernel, $heap) = @_[KERNEL, HEAP]; $heap->{shutdown} = 1; $kernel->alias_remove($heap->{alias}); _poe_wipe_sidecars($heap); foreach my $request (values %{$heap->{requests}}) { $kernel->post( $request->{sender}, $request->{event}, 'component shut down', [ ], { map { $_ => $request->{$_} } qw(host service misc) }, ); warn " $heap->{alias} --refcount for sender $request->{sender}" if ( $heap->{debug} ); $kernel->refcount_decrement($request->{sender}, __PACKAGE__); } $heap->{requests} = {}; # No more sidecars to eject. $kernel->delay(sidecar_eject => undef); } # POE event handler to accept a request from some other session. The # public Perl resolve() method forwards into this. This runs "within" # the session so the resources it creates are properly owned. sub _poe_request { my ($kernel, $heap, $host, $service, $hints, $event, $misc) = @_[ KERNEL, HEAP, ARG0..ARG4 ]; return if $heap->{shutdown}; my $request_id = ++$heap->{last_request_id}; my $sender_id = $_[SENDER]->ID(); warn " $heap->{alias} ++refcount for sender $sender_id" if ( $heap->{debug} ); $kernel->refcount_increment($sender_id, __PACKAGE__); _poe_setup_sidecar_ring($kernel, $heap); my $next_sidecar = pop @{$heap->{sidecar_ring}}; unshift @{$heap->{sidecar_ring}}, $next_sidecar; $next_sidecar->put( [ $request_id, $host, $service, $hints ] ); $heap->{requests}{$request_id} = { begin => time(), host => $host, service => $service, hints => $hints, sender => $sender_id, event => $event, misc => $misc, sidecar_id => $next_sidecar->ID(), }; # No ejecting until we're done. $kernel->delay(sidecar_eject => undef); return $request_id; } # The user wishes to cancel a DNS request that may still be in # progress. This can happen in places like PoCo::Client::HTTP when # the HTTP request times out before the DNS request is done. # # The public cancel() API forwards the cancelation request into the # POE::Session managing requests via POE::Kernel's call() method. sub cancel { my ($self, $request_id) = @_; return $poe_kernel->call($self->{alias}, "cancel", $request_id); } # The inside-POE cancelation code. It must run within POE so that the # proper resources are removed from the correct session. sub _poe_cancel { my ($kernel, $heap, $request_id) = @_[KERNEL, HEAP, ARG0]; return unless exists $heap->{requests}{$request_id}; my $request = $heap->{requests}{$request_id}; _sidecar_cleanup($kernel, $heap, $request->{sidecar_id}); } # POE _start handler. Initialize the session and start sidecar # processes, which are owned and managed by that session. sub _poe_start { my ($kernel, $heap) = @_[KERNEL, HEAP]; carp " starting $heap->{alias}" if $heap->{debug}; $kernel->alias_set($heap->{alias}); #_poe_setup_sidecar_ring($kernel, $heap); undef; } # Internal helper sub. Make sure the apprpriate number of sidecar # resolvers are running at any given time. sub _poe_setup_sidecar_ring { my ($kernel, $heap) = @_; return if $heap->{shutdown}; while (scalar(keys %{$heap->{sidecar}}) < $heap->{max_resolvers}) { my $sidecar = POE::Wheel::Run->new( StdioFilter => POE::Filter::Reference->new(), StdoutEvent => 'sidecar_response', StderrEvent => 'sidecar_error', CloseEvent => 'sidecar_closed', Program => $heap->{sidecar_program}, ); $heap->{sidecar}{$sidecar->PID} = $sidecar; $heap->{sidecar_id}{$sidecar->ID} = $sidecar; push @{$heap->{sidecar_ring}}, $sidecar; $kernel->sig_child($sidecar->PID(), "sidecar_signal"); } } # Internal helper sub to replay pending requests when their associated # sidecars are destroyed. sub _poe_replay_pending { my ($kernel, $heap) = @_; while (my ($request_id, $request) = each %{$heap->{requests}}) { # This request is riding in an existing sidecar. # No need to replay it. next if exists $heap->{sidecar_id}{$request->{sidecar_id}}; # Give the request to a new sidecar. my $next_sidecar = pop @{$heap->{sidecar_ring}}; unshift @{$heap->{sidecar_ring}}, $next_sidecar; $request->{sidecar_id} = $next_sidecar->ID(); $next_sidecar->put( [ $request_id, $request->{host}, $request->{service}, $request->{hints} ] ); } } # Internal event handler to briefly defer replaying requests until any # responses in the queue have had time to be delivered. This prevents # us from replaying requests that may already have answers. sub _poe_sidecar_attach { my ($kernel, $heap) = @_[KERNEL, HEAP]; # Nothing to do if we don't have requests. return unless scalar keys %{$heap->{requests}}; # Requests exist. _poe_setup_sidecar_ring($kernel, $heap); _poe_replay_pending($kernel, $heap); } # Plain public Perl method. Begin resolving something. sub resolve { my ($self, @args) = @_; croak "resolve() requires an even number of parameters" if @args % 2; my %args = @args; my $host = delete $args{host}; croak "resolve() requires a host" unless defined $host and length $host; my $service = delete $args{service}; croak "resolve() requires a service" unless ( defined $service and length $service ); my $misc = delete $args{misc}; $misc = "" unless defined $misc; my $hints = delete $args{hints}; $hints ||= { }; my $event = delete $args{event}; $event = "resolver_response" unless defined $event and length $event; my @error = sort keys %args; croak "unknown resolve() parameter(s): @error" if @error; my $result; croak "resolve() on shutdown resolver" unless ( $result = $poe_kernel->call( $self->{alias}, "request", $host, $service, $hints, $event, $misc ) ); carp " $self->{alias} request for host($host) service($service)" if ( $self->{debug} ); return $result; } # A sidecar process has produced an error or warning. Pass it # through to the parent process' console. sub _poe_sidecar_error { warn __PACKAGE__, " error in getaddrinfo subprocess: $_[ARG0]\n"; } # A sidecar process has closed its standard output. We will receive # no more responses from it. Clean up the sidecar's resources, and # start new ones if necessary. sub _poe_sidecar_closed { my ($kernel, $heap, $wheel_id) = @_[KERNEL, HEAP, ARG0]; # Don't bother checking for pending requests if we've shut down. return if $heap->{shutdown}; _sidecar_cleanup($kernel, $heap, $wheel_id); } sub _sidecar_cleanup { my ($kernel, $heap, $wheel_id) = @_; my $sidecar = delete $heap->{sidecar_id}{$wheel_id}; if (defined $sidecar) { $sidecar->kill(9); delete $heap->{sidecar}{$sidecar->PID()}; } # Remove the sidecar from the rotation. my $i = @{$heap->{sidecar_ring}}; while ($i--) { next unless $heap->{sidecar_ring}[$i]->ID() == $wheel_id; splice(@{$heap->{sidecar_ring}}, $i, 1); last; } _poe_setup_sidecar_ring($kernel, $heap); _poe_replay_pending($kernel, $heap) if scalar keys %{$heap->{requests}}; } # A sidecar has produced a response. Pass it back to the original # caller of resolve(). If we've run out of requests, briefly defer a # partial shutdown. We don't need all those sidecar processes if we # might be done. sub _poe_sidecar_response { my ($kernel, $heap, $response_rec) = @_[KERNEL, HEAP, ARG0]; my ($request_id, $error, $addresses) = @$response_rec; my $request_rec = delete $heap->{requests}{$request_id}; return unless defined $request_rec; if (defined $heap->{af_order}) { my @filtered_addresses; foreach my $af_filter (@{$heap->{af_order}}) { push @filtered_addresses, grep { $_->{family} == $af_filter } @$addresses; } $addresses = \@filtered_addresses; } $kernel->post( $request_rec->{sender}, $request_rec->{event}, $error, $addresses, { map { $_ => $request_rec->{$_} } qw(host service misc) }, ); warn " $heap->{alias} --refcount for sender $request_rec->{sender}" if ( $heap->{debug} ); $kernel->refcount_decrement($request_rec->{sender}, __PACKAGE__); # No more requests? Consder detaching sidecar. $kernel->delay(sidecar_eject => $heap->{idle_timeout}) unless ( scalar keys %{$heap->{requests}} ); } # A sidecar process has exited. Clean up its resources, and attach a # replacement sidecar if there are requests. sub _poe_sidecar_signal { my ($heap, $pid) = @_[HEAP, ARG1]; return unless exists $heap->{sidecar}{$pid}; my $sidecar = delete $heap->{sidecar}{$pid}; my $sidecar_id = $sidecar->ID(); delete $heap->{sidecar_id}{$sidecar_id}; # Remove the sidecar from the rotation. my $i = @{$heap->{sidecar_ring}}; while ($i--) { next unless $heap->{sidecar_ring}[$i]->ID() == $sidecar_id; splice(@{$heap->{sidecar_ring}}, 1, 1); last; } $_[KERNEL]->yield("sidecar_attach") if scalar keys %{$heap->{requests}}; undef; } # Event handler to defer wiping out all sidecars. This allows for # lazy cleanup, which may eliminate thrashing in some situations. sub _poe_sidecar_eject { my ($kernel, $heap) = @_[KERNEL, HEAP]; _poe_wipe_sidecars($heap) unless scalar keys %{$heap->{requests}}; } # Internal helper sub to synchronously wipe out all sidecars. sub _poe_wipe_sidecars { my $heap = shift; return unless @{$heap->{sidecar_ring}}; foreach my $sidecar (@{$heap->{sidecar_ring}}) { $sidecar->kill(-9); } $heap->{sidecar} = {}; $heap->{sidecar_id} = {}; $heap->{sidecar_ring} = []; } sub unpack_addr { my ($self, $address_rec) = @_; # [rt.cpan.org 76314] Untaint the address. my ($input_addr) = ($address_rec->{addr} =~ /\A(.*)\z/s); my ($error, $address, $port) = ( (getnameinfo $input_addr, NI_NUMERICHOST | NI_NUMERICSERV)[0,1] ); return if $error; return($address, $port) if wantarray(); return $address; } 1; __END__ =head1 NAME POE::Component::Resolver - A non-blocking getaddrinfo() resolver =head1 VERSION version 0.920 =head1 SYNOPSIS #!/usr/bin/perl use warnings; use strict; use POE; use POE::Component::Resolver qw(AF_INET AF_INET6); my $r = POE::Component::Resolver->new( max_resolvers => 8, idle_timeout => 5, af_order => [ AF_INET6, AF_INET ], # sidecar_program => $path_to_program, ); my @hosts = qw( ipv6-test.com ); my $tcp = getprotobyname("tcp"); POE::Session->create( inline_states => { _start => sub { foreach my $host (@hosts) { $r->resolve( host => $host, service => "http", event => "got_response", hints => { protocol => $tcp }, ) or die $!; } }, _stop => sub { print "client session stopped\n" }, got_response => sub { my ($error, $addresses, $request) = @_[ARG0..ARG2]; use YAML; print YAML::Dump( { error => $error, addr => $addresses, req => $request, } ); }, } ); POE::Kernel->run(); =head1 DESCRIPTION POE::Component::Resolver performs Socket::getaddrinfo() calls in subprocesses where they're permitted to block as long as necessary. By default it will run eight subprocesses and prefer address families in whatever order getaddrinfo() returns them. These defaults can be overridden with constructor parameters. getaddrinfo() delegates to the operating system's resolver, which may be reconfigured according to the usual conventions. =head2 PUBLIC METHODS =head3 new Create a new resolver. Returns an object that must be held and used to make requests. See the synopsis. Accepts up to four optional named parameters. "af_order" may contain an arrayref with the address families to permit, in the order in which they're preferred. Without "af_order", the component will prefer IPv4 addresses over IPv6 for legacy compatibility. This may change in the future as IPv6 gains more widespread acceptance. See L for a way to override the default without hacking modules. # Prefer IPv6 addresses, but also return IPv4 ones. my $r1 = POE::Component::Resolver->new( af_order => [ AF_INET6, AF_INET ] ); # Only return IPv6 addresses, # or nothing in cases where only IPv4 addresses exist. my $r2 = POE::Component::Resolver->new( af_order => [ AF_INET6 ] ); "idle_timeout" determines how long to keep idle resolver subprocesses before cleaning them up, in seconds. It defaults to 15.0 seconds. "max_resolvers" controls the component's parallelism by defining the maximum number of sidecar processes to manage. It defaults to 8, but fewer or more processes can be configured depending on the resources you have available and the amount of parallelism you require. # One at a time, but without the pesky blocking. my $r3 = POE::Component::Resolver->new( max_resolvers => 1 ); "sidecar_program" contains the disk location of a program that will perform blocking lookups on standard input and print the results on standard output. The sidecar program is needed only in special environments where the bundling and execution of extra utilities is tricky. PAR is one such environment. The sidecar program needs to contain at least two statements: use POE::Component::Resolver::Sidecar; POE::Component::Resover::Sidecar->main(); =head3 resolve resolve() begins a new request to resolve a domain. The request will be enqueued in the component until a sidecar process can service it. resolve() returns a request ID that may be used to cancel() a request before it has completed (or undef if the request couldn't begin, such as during shutdown). Resolve requires two parameters and accepts some additional optional ones. "host" and "service" are required and contain the host (name or Internet address) and service (name or numeric port) that will be passed verbatim to getaddrinfo(). See L for details. "event" is optional; it contains the name of the event that will contain the resolver response. If omitted, it will default to "resolver_response"; you may want to specify a shorter event name. "hints" is optional. If specified, it must contain a hashref of hints exactly as getaddrinfo() expects them. See L for details. "misc" is optional continuation data that will be passed back in the response. It may contain any type of data the application requires. =head3 cancel Cancel a request, given the request's ID. my $request_id = $resolver->resolve("poe.dyndns.org", "http"); $resolver->cancel($request_id); =head3 shutdown Shut down the resolver. POE::Component::Resolver retains resources including child processes for up to "idle_timeout" seconds. This may keep programs running up to "idle_timeout" seconds longer than they should. POE::Component::Resolver will release its resources (including child processes) when its shutdown() method is called. =head3 unpack_addr In scalar context, unpack_addr($response_addr_hashref) returns the addr element of $response_addr_hashref in a numeric form appropriate for the address family of the address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my $numeric_addr = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } In list context, it returns the numeric port and address. sub handle_resolver_response { my ($error, $addresses, $request) = @_[ARG0..ARG2]; foreach my $a (@$addresses) { my ($$numeric_addr, $port) = $resolver->unpack_addr($a); print "$request->{host} = $numeric_addr\n"; } } unpack_addr() is a convenience wrapper around getnameinfo() from L. You're certainly welcome to use the discrete function instead. unpack_addr() returns bleak emptiness on failure, regardless of context. You can check for undef return. =head2 PUBLIC EVENTS =head3 resolver_response The resolver response event includes three parameters. $_[ARG0] and $_[ARG1] contain the retrn values from Socket's getaddrinfo() call. These are an error message (if the call failed), and an arrayref of address structures if the call succeeded. The component provides its own error message, 'component shut down'. This response is given for every pending request at the time the user shuts down the component. $_[ARG2] contains a hashref of information provided to the resolve() method. Specifically, the values of resolve()'s "host", "service" and "misc" parameters. =head1 ENVIRONMENT VARIABLES =head2 POCO_RESOLVER_IPV The POCO_RESOLVER_IPV environment variable sets this component's default Internet Protocol Version search order. If the variable exists, it should contain a string with the numbers 4 and/or 6. POE::Component::Resolver will treate these as Internet Protocol versions to consider, in the order they are preferred. POE::Component::Resolver's new() method accepts an "af_order" parameter that overrides this environment variable. Default to IPv4 addresses only: export POCO_RESOLVER_IPV=4 Default to IPv6 addresses only: export POCO_RESOLVER_IPV=6 Prefer IPv6, but accept IPv4 if needed: export POCO_RESOLVER_IPV=64 Prefer IPv4, but accept IPv6 if needed: export POCO_RESOLVER_IPV=46 =head1 COMPATIBILITY ISSUES =head2 Microsoft Windows This module requires "Microsoft TCP/IP version 6" to be installed. Steps for Windows XP Pro (the steps for your particular version of Windows may be subtly or drastically different): =over =item * Open your Control Panel =item * Open your Network Connections =item * Select your network connection from the available one(s) =item * In the Local Area Connection Status dialog, click the Properties button =item * If "Microsoft TCP/IP version 6" is listed as an item being used, you are done. =item * Otherwise click Install... =item * Choose to add a Protocol =item * And install "Microsoft TCP/IP version 6" from the list of network protocols. =back =head1 BUGS There is no timeout on requests. There is no way to cancel a pending request. =head1 TROUBLESHOOTING =head2 programs linger for several seconds before exiting Programs should shutdown() their POE::Component::Resolver objects when they are through needing asynchronous DNS resolution. Programs should additionally destroy their resolvers if they intend to run awhile and want to reuse the memory they consume. In some cases, it may be necessary to shutdown components that perform asynchronous DNS using POE::Component::Resolver... such as POE::Component::IRC, POE::Component::Client::Keepalive and POE::Component::Client::HTTP. By default, the resolver subprocesses hang around for idle_timeout, which defaults to 15.0 seconds. Destroying the Resolver object will clean up the process pool. Assuming only that is keeping the event loop active, the program will then exit cleanly. Alternatively, reduce idle_timeout to a more manageable number, such as 5.0 seconds. Otherwise something else may also be keeping the event loop active. =head1 LICENSE Except where otherwise noted, this distribution is Copyright 2011 by Rocco Caputo. All rights reserved. This distribution is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =cut POE-Component-Resolver-0.920/lib/POE/Component/Resolver/Sidecar.pm000644 000765 000024 00000004524 11762546422 024702 0ustar00trocstaff000000 000000 package POE::Component::Resolver::Sidecar; { $POE::Component::Resolver::Sidecar::VERSION = '0.920'; } use warnings; use strict; use Storable qw(nfreeze thaw); use Socket qw(getaddrinfo); sub main { my $buffer = ""; my $read_length; binmode(STDIN); binmode(STDOUT); select STDOUT; $| = 1; use bytes; while (1) { if (defined $read_length) { if (length($buffer) >= $read_length) { my $request = thaw(substr($buffer, 0, $read_length, "")); $read_length = undef; my ($request_id, $host, $service, $hints) = @$request; my ($err, @addrs) = getaddrinfo($host, $service, $hints); my $streamable = nfreeze( [ $request_id, $err, \@addrs ] ); my $stream = length($streamable) . chr(0) . $streamable; my $octets_wrote = syswrite(STDOUT, $stream); die $! unless $octets_wrote == length($stream); next; } } elsif ($buffer =~ s/^(\d+)\0//) { $read_length = $1; next; } my $octets_read = sysread(STDIN, $buffer, 4096, length($buffer)); last unless $octets_read; } exit 0; } 1; __END__ =head1 NAME POE::Component::Resolver::Sidecar - delegate subprocess to call getaddrinfo() =head1 VERSION version 0.920 =head1 SYNOPSIS Used internally by POE::Component::Resolver. =head1 DESCRIPTION POE::Component::Resolver creates subprocesses to call getaddrinfo() so that the main program doesn't block during that time. The actual getaddrinfo() calling code is abstracted into this module so it can be run in a separate executable program. This reduces the memory footprint of forking the entire main process for just getaddrinfo(). It's a strong, useful pattern that other POE::Components have implemented before. POE::Quickie does it generically. POE::Component::SimpleDBI and POE::Component::EasyDBI do it so their DBI subprocesses are relatively lightweight. =head2 main The main code to read POE::Component::Resolver requests from STDIN and write getaddrinfo() responses to STDOUT. =head1 SEE ALSO L is one generic implementation of this pattern. L is another generic implementation of this pattern. =head1 BUGS None known. =head1 LICENSE Except where otherwise noted, this distribution is Copyright 2011 by Rocco Caputo. All rights reserved. This distribution is free software; you may redistribute it and/or modify it under the same terms as Perl itself. =cut