File Coverage

blib/lib/Net/ClientServer.pm
Criterion Covered Total %
statement 11 13 84.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 16 18 88.8


line stmt bran cond sub pod time code
1             package Net::ClientServer;
2             BEGIN {
3 2     2   211155 $Net::ClientServer::VERSION = '0.0007';
4             }
5             # ABSTRACT: A client/server platform for IPC on localhost
6              
7 2     2   22 use strict;
  2         3  
  2         70  
8 2     2   12 use warnings;
  2         4  
  2         62  
9              
10              
11             #The API is still young and pretty fluid. See the SYNOPSIS for examples (for now)
12              
13             #Daemonization (via C<< ->start >>) is on by default, disable it with: C<< daemon => 0 >>
14              
15 2     2   1743 use Any::Moose;
  2         79097  
  2         17  
16              
17 2     2   5833 use Daemon::Daemonize qw/ write_pidfile check_pidfile /;
  0            
  0            
18             use Path::Class;
19             use IO::Socket::INET;
20             use File::HomeDir;
21             use Path::Class;
22             use Carp;
23             use Net::ClientServer::Server;
24             use Socket qw/ INADDR_ANY INADDR_LOOPBACK /;
25              
26             has host => qw/ is ro /, default => 'localhost';
27             has port => qw/ is ro required 1 /;
28              
29             has [ map { "${_}_routine" } qw/ start stop serve run / ] => qw/ is rw isa Maybe[CodeRef] /;
30              
31             has daemon => qw/ is rw default 1 /;
32             sub _daemon_options { }
33              
34             has _server_options => qw/ is rw isa HashRef lazy_build 1 /;
35             sub _build__server_options { {} }
36              
37             sub BUILD {
38             my $self = shift;
39             my $given = shift;
40              
41             my $file_default = 1;
42             $file_default = 0 unless $given->{home} || $given->{name};
43             $self->_default_pidfile( $file_default );
44             $self->_default_stderr( $file_default );
45              
46             for (qw/ start stop serve run /) {
47             my $routine = "${_}_routine";
48             next unless $given->{$_};
49             if ( $given->{$routine} ) {
50             carp "Given $routine AND $_ as options";
51             next;
52             }
53             $self->$routine( $given->{$_} );
54             }
55              
56             $self->_server_options->{fork} = 1 if $given->{fork};
57             }
58              
59             for my $field (qw/ name home pidfile stderr /) {
60             my $data = "_data_$field";
61             my $built = "_built_$field";
62             my $build = "_build_$field";
63             my $reset = "_reset_$field";
64             has $data => qw/ is rw /, init_arg => $field, predicate => "has_$field";
65             has $built => qw/ is ro lazy 1 /, clearer => $reset, builder => $build;
66             __PACKAGE__->meta->add_method( $field => sub {
67             my $self = shift;
68             if ( @_ ) {
69             $self->$data( $_[0] );
70             $self->$reset;
71             }
72             return $self->$built;
73             } );
74             }
75              
76             for my $field (qw/ pidfile stderr /) {
77             my $default = "_default_$field";
78             has $default => qw/ is rw /;
79             }
80              
81             sub _with_home { return $_[0]->name || ( $_[0]->has_home && $_[0]->home ) }
82              
83             sub _build_name { return $_[0]->_data_name }
84             sub _build_home {
85             my $self = shift;
86             my @dir;
87             if ( $self->has_home ) {
88             return unless my $home = $self->_data_home;
89             push @dir, $home if $home ne 1;
90             }
91             unless ( @dir ) {
92             my $name;
93             if ( $name = $self->name ) { }
94             else {
95             my $port = $self->port;
96             croak "Missing name for home (home == 1)" if $port =~ m/\D/;
97             $self->name( join '-', 'net-client-server', $port );
98             $name = $self->name;
99             }
100             push @dir, File::HomeDir->my_home, join '', ".$name";
101             }
102             return dir( @dir )->absolute;
103             }
104             sub _yield_file_field {
105             my $self = shift;
106             my $field = shift;
107             my $default = shift;
108              
109             my $data = "_data_$field";
110             my $has = "has_$field";
111             my $_default = "_default_$field"; # Default from during construction
112            
113             my $file = $self->$_default;
114             $file = $self->$data if $self->$has;
115             return undef unless $file; # O, '', undef => No file
116             $file = $default if $file eq '1';
117             if ( $file =~ m/^\// ) {}
118             elsif ( $file =~ m/^\.\// ) {}
119             else {
120             croak "Missing home for $field"
121             unless ( ($self->has_home || $self->has_name ) && $self->home );
122             $file = $self->home->file( $file );
123             }
124             return file( $file )->absolute;
125             }
126             sub _build_pidfile {
127             my $self = shift;
128             return $self->_yield_file_field( 'pidfile', 'pid' );
129             }
130             sub _build_stderr {
131             my $self = shift;
132             return $self->_yield_file_field( 'stderr', 'stderr' );
133             }
134              
135             #open(STDERR,"|/bin/logger -t \"${PROGNAME}[$$]: STDERR\"") or die "Error: Unable to redirect STDERR to logger!";
136             #open(STDOUT,"|/bin/logger -t \"${PROGNAME}[$$]: STDOUT\"") or die "Error: Unable to redirect STDOUT to logger!";
137              
138             sub server_socket {
139             my $self = shift;
140             return Net::ClientServer::Server->server_socket( host => $self->host, port => $self->port, @_ );
141             }
142              
143             sub _is_localhost {
144             my $self = shift;
145             my $host = shift;
146              
147             return 1 unless $host;
148             return 1 if $host eq '::' or
149             $host eq '0.0.0.0' or
150             $host eq INADDR_ANY or
151             $host eq INADDR_LOOPBACK;
152             return 1 if eval {
153             require Socket6;
154             return 1 if $host eq &Socket6::in6addr_any or
155             $host eq &Socket6::in6addr_loopback;
156            
157             };
158             return 0;
159             }
160              
161             sub client_socket {
162             my $self = shift;
163              
164             my @arguments;
165              
166             my $host = $self->host;
167             $host = 'localhost' if $self->_is_localhost( $host );
168             my $port = $self->port;
169              
170             return IO::Socket::INET->new( PeerHost => $host, PeerPort => $port, Proto => 'tcp' );
171             }
172              
173             sub pid {
174             my $self = shift;
175             return 0 unless $self->pidfile;
176             return check_pidfile( $self->pidfile );
177             }
178              
179             sub delete_pidfile {
180             my $self = shift;
181             return unless $self->has_pidfile && ( my $pidfile = $self->pidfile );
182             Daemon::Daemonize::delete_pidfile( $pidfile );
183             }
184              
185             sub started {
186             my $self = shift;
187             return 1 if $self->pid || $self->client_socket;
188             return 0;
189             }
190              
191             sub start {
192             my $self = shift;
193             return if $self->started;
194             if ( $self->daemon ) { $self->daemonize( _run => sub { $self->serve } ) }
195             else { $self->serve }
196             }
197              
198             sub _file_mkdir {
199             my $self = shift;
200             my $file = shift;
201             return unless $file;
202             $file = file( $file ) if ref $file eq '';
203             return unless blessed $file && $file->isa( 'Path::Class::File' );
204             $file->parent->mkpath;
205             }
206              
207             sub daemonize {
208             my $self = shift;
209             my %options = @_;
210              
211             my $platform = $self;
212             my @daemon_arguments;
213              
214             push @daemon_arguments, chdir => undef, close => 1;
215              
216             if ( ( $self->_with_home || $self->has_stderr ) && ( my $stderr = $self->stderr ) ) {
217             $self->_file_mkdir( $stderr );
218             push @daemon_arguments, stderr => $stderr;
219             }
220              
221             my $pidfile;
222             if ( ( $self->_with_home || $self->has_pidfile ) && ( $pidfile = $self->pidfile ) ) {
223             $self->_file_mkdir( $pidfile );
224             push @daemon_arguments, pidfile => $pidfile;
225             }
226              
227             my %daemon = $self->_daemon_options;
228              
229             my ( $override_run, $run, $_run ) =
230             ( delete @daemon{qw/ override_run run /}, $options{_run} );
231              
232             if ( $override_run ) {
233             push @daemon_arguments, run => $override_run;
234             }
235             else {
236             $run = $_run unless $run;
237             push @daemon_arguments, run => sub {
238             if ( $pidfile ) {
239             write_pidfile( $pidfile );
240             $SIG{TERM} = $SIG{INT} = sub { Daemon::Daemonize::delete_pidfile( $pidfile ) }
241             }
242             $run->( $platform );
243             };
244             }
245              
246             push @daemon_arguments, %daemon;
247              
248             Daemon::Daemonize->daemonize( chdir => undef, close => 1, @daemon_arguments );
249              
250             if ( $pidfile ) {
251             do { sleep 1 } until -s $pidfile;
252             }
253             }
254              
255             sub serve {
256             my $self = shift;
257              
258             my $platform = $self;
259             my %server_options = %{ $self->_server_options };
260              
261             for (qw/ start stop serve run /) {
262             my $routine = "${_}_routine";
263             next unless my $code = $self->$routine;
264             $server_options{$_} ||= sub { $code->( @_, $platform ) };
265             }
266             Net::ClientServer::Server->serve( host => $self->host, port => $self->port, %server_options );
267             }
268              
269             # Stoled from Net::Server
270             sub stdin2socket {
271             my $self = shift;
272             my $socket = shift;
273              
274             my $fileno = fileno $socket;
275             close STDIN;
276             if ( defined $fileno ) {
277             open STDIN, "<&$fileno" or die "Unable open STDIN to socket: $!";
278             }
279             else {
280             *STDIN= \*{ $socket };
281             }
282             STDIN->autoflush( 1 );
283             }
284              
285             # Stoled from Net::Server
286             sub stdout2socket {
287             my $self = shift;
288             my $socket = shift;
289              
290             my $fileno = fileno $socket;
291             close STDOUT;
292             if ( defined $fileno ) {
293             open STDOUT, ">&$fileno" or die "Unable open STDOUT to socket: $!";
294             }
295             else {
296             *STDOUT= \*{ $socket } unless $socket->isa( 'IO::Socket::SSL' );
297             }
298             STDOUT->autoflush( 1 );
299             }
300              
301             # Stoled from Net::Server
302             sub stderr2socket {
303             my $self = shift;
304             my $socket = shift;
305              
306             my $fileno = fileno $socket;
307             close STDERR;
308             if ( defined $fileno ) {
309             open STDERR, ">&$fileno" or die "Unable open STDERR to socket: $!";
310             }
311             else {
312             *STDERR= \*{ $socket } unless $socket->isa( 'IO::Socket::SSL' );
313             }
314             STDERR->autoflush( 1 );
315             }
316              
317             1;
318              
319             __END__