File Coverage

blib/lib/Perlbal/TCPListener.pm
Criterion Covered Total %
statement 61 92 66.3
branch 17 38 44.7
condition 1 5 20.0
subroutine 10 13 76.9
pod 3 6 50.0
total 92 154 59.7


line stmt bran cond sub pod time code
1             ######################################################################
2             # TCP listener on a given port
3             #
4             # Copyright 2004, Danga Interactive, Inc.
5             # Copyright 2005-2007, Six Apart, Ltd.
6              
7              
8             package Perlbal::TCPListener;
9 22     22   140 use strict;
  22         54  
  22         942  
10 22     22   137 use warnings;
  22         546  
  22         1035  
11 22     22   339 no warnings qw(deprecated);
  22         357  
  22         952  
12              
13 22     22   127 use base "Perlbal::Socket";
  22         47  
  22         6031  
14 22         216 use fields ('service',
15             'hostport',
16             'sslopts',
17             'v6', # bool: IPv6 libraries are available
18 22     22   141 );
  22         44  
19 22     22   1801 use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF);
  22         43  
  22         2845  
20              
21             BEGIN {
22 22     22   69 eval { require Perlbal::SocketSSL };
  22         19837  
23 22         29238 if (Perlbal::DEBUG > 0 && $@) { warn "SSL support failed on load: $@\n" }
24             }
25              
26             # TCPListener
27             sub new {
28 39     39 1 94 my Perlbal::TCPListener $self = shift;
29 39         104 my ($hostport, $service, $opts) = @_;
30              
31 39 50       210 $self = fields::new($self) unless ref $self;
32 39   50     14582 $opts ||= {};
33              
34             # Were ipv4 or ipv6 explicitly mentioned by syntax?
35 39         82 my $force_v4 = 0;
36 39         69 my $force_v6 = 0;
37              
38 39         71 my @args;
39 39 50       457 if ($hostport =~ /^\d+$/) {
    50          
40 0         0 @args = ('LocalPort' => $hostport);
41             } elsif ($hostport =~ /^\d+\.\d+\.\d+\.\d+:/) {
42 39         62 $force_v4 = 1;
43 39         129 @args = ('LocalAddr' => $hostport);
44             }
45              
46 39         84 my $v6_errors = "";
47              
48 39         66 my $can_v6 = 0;
49 39 50       156 if (!$force_v4) {
50 0         0 eval "use Danga::Socket 1.61; 1; ";
51 0 0       0 if ($@) {
    0          
52 0         0 $v6_errors = "Danga::Socket 1.61 required for IPv6 support.";
53 0         0 } elsif (!eval { require IO::Socket::INET6; 1 }) {
  0         0  
54 0         0 $v6_errors = "IO::Socket::INET6 required for IPv6 support.";
55             } else {
56 0         0 $can_v6 = 1;
57             }
58             }
59              
60 39 50       134 my $socket_class = $can_v6 ? "IO::Socket::INET6" : "IO::Socket::INET";
61 39         101 $self->{v6} = $can_v6;
62              
63 39         678 my $sock = $socket_class->new(
64             @args,
65             Proto => IPPROTO_TCP,
66             Listen => 1024,
67             ReuseAddr => 1,
68             );
69              
70 39 50 0     16856 return Perlbal::error("Error creating listening socket: " . ($@ || $!))
71             unless $sock;
72              
73 39 50       208 if ($^O eq 'MSWin32') {
74             # On Windows, we have to do this a bit differently.
75             # IO::Socket should really do this for us, but whatever.
76 0         0 my $do = 1;
77 0 0       0 ioctl($sock, 0x8004667E, \$do) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
78             }
79             else {
80             # IO::Socket::INET's Blocking => 0 just doesn't seem to work
81             # on lots of perls. who knows why.
82 39 50       621 IO::Handle::blocking($sock, 0) or return Perlbal::error("Unable to make listener on $hostport non-blocking: $!");
83             }
84              
85 39         398 $self->SUPER::new($sock);
86 39         88 $self->{service} = $service;
87 39         93 $self->{hostport} = $hostport;
88 39         91 $self->{sslopts} = $opts->{ssl};
89 39         1813 $self->watch_read(1);
90 39         5394 return $self;
91             }
92              
93             # TCPListener: accepts a new client connection
94             sub event_read {
95 89     89 1 5827458 my Perlbal::TCPListener $self = shift;
96              
97             # accept as many connections as we can
98 89         1987 while (my ($psock, $peeraddr) = $self->{sock}->accept) {
99 89         27136 IO::Handle::blocking($psock, 0);
100              
101 89 50       1764 if (my $sndbuf = $self->{service}->{client_sndbuf_size}) {
102 0         0 my $rv = setsockopt($psock, SOL_SOCKET, SO_SNDBUF, pack("L", $sndbuf));
103             }
104              
105 89         174 if (Perlbal::DEBUG >= 1) {
106             my ($pport, $pipr) = $self->{v6} ?
107             Socket6::unpack_sockaddr_in6($peeraddr) :
108             Socket::sockaddr_in($peeraddr);
109             my $pip = $self->{v6} ?
110             "[" . Socket6::inet_ntop(Socket6::AF_INET6(), $pipr) . "]" :
111             Socket::inet_ntoa($pipr);
112             print "Got new conn: $psock ($pip:$pport) for " . $self->{service}->role . "\n";
113             }
114              
115             # SSL promotion if necessary
116 89 50       471 if ($self->{sslopts}) {
117             # try to upgrade to SSL, this does no IO it just re-blesses
118             # and prepares the SSL engine for handling us later
119 0         0 Perlbal::SocketSSL2->start_SSL(
120             $psock,
121             SSL_server => 1,
122             SSL_startHandshake => 0,
123 0         0 %{ $self->{sslopts} },
124             );
125 0         0 print " .. socket upgraded to SSL!\n" if Perlbal::DEBUG >= 1;
126              
127             # safety checking to ensure we got upgraded
128 0 0       0 return $psock->close
129             unless ref $psock eq 'Perlbal::SocketSSL2';
130              
131             # class into new package and run with it
132 0         0 my $sslsock = new Perlbal::SocketSSL($psock, $self);
133 0         0 $sslsock->try_accept;
134              
135             # all done from our point of view
136 0         0 next;
137             }
138              
139             # puts this socket into the right class
140 89         658 $self->class_new_socket($psock);
141             }
142             }
143              
144             sub class_new_socket {
145 89     89 0 235 my Perlbal::TCPListener $self = shift;
146 89         212 my $psock = shift;
147              
148 89         911 my $service_role = $self->{service}->role;
149 89 100       768 if ($service_role eq "reverse_proxy") {
    100          
    100          
    50          
    0          
150 25         357 return Perlbal::ClientProxy->new($self->{service}, $psock);
151             } elsif ($service_role eq "management") {
152 17         236 return Perlbal::ClientManage->new($self->{service}, $psock);
153             } elsif ($service_role eq "web_server") {
154 42         533 return Perlbal::ClientHTTP->new($self->{service}, $psock);
155             } elsif ($service_role eq "selector") {
156             # will be cast to a more specific class later...
157 5         70 return Perlbal::ClientHTTPBase->new($self->{service}, $psock, $self->{service});
158             } elsif (my $creator = Perlbal::Service::get_role_creator($service_role)) {
159             # was defined by a plugin, so we want to return one of these
160 0           return $creator->($self->{service}, $psock);
161             }
162             }
163              
164             sub as_string {
165 0     0 1   my Perlbal::TCPListener $self = shift;
166 0           my $ret = $self->SUPER::as_string;
167 0           my Perlbal::Service $svc = $self->{service};
168 0           $ret .= ": listening on $self->{hostport} for service '$svc->{name}'";
169 0           return $ret;
170             }
171              
172             sub as_string_html {
173 0     0 0   my Perlbal::TCPListener $self = shift;
174 0           my $ret = $self->SUPER::as_string_html;
175 0           my Perlbal::Service $svc = $self->{service};
176 0           $ret .= ": listening on $self->{hostport} for service $svc->{name}";
177 0           return $ret;
178             }
179              
180             sub die_gracefully {
181             # die off so we stop waiting for new connections
182 0     0 0   my $self = shift;
183 0           $self->close('graceful_death');
184             }
185              
186             1;
187              
188              
189             # Local Variables:
190             # mode: perl
191             # c-basic-indent: 4
192             # indent-tabs-mode: nil
193             # End: