| 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 Moobal::Listener::TCP; |
|---|
| 9 | |
|---|
| 10 | use Moose; |
|---|
| 11 | |
|---|
| 12 | with 'Moobal::Role::Listener'; |
|---|
| 13 | with 'Moobal::Role::WithContext'; |
|---|
| 14 | |
|---|
| 15 | around 'new' => sub { |
|---|
| 16 | my $next = shift; |
|---|
| 17 | my $self = $next->(@_); |
|---|
| 18 | $self->start_listen(); |
|---|
| 19 | return $self; |
|---|
| 20 | }; |
|---|
| 21 | |
|---|
| 22 | has 'service' => ( |
|---|
| 23 | is => 'rw', |
|---|
| 24 | isa => 'Moobal::Service' |
|---|
| 25 | ); |
|---|
| 26 | |
|---|
| 27 | has 'hostport' => ( |
|---|
| 28 | is =>'rw', |
|---|
| 29 | isa => 'Str' |
|---|
| 30 | ); |
|---|
| 31 | |
|---|
| 32 | has 'sslopts' => ( |
|---|
| 33 | is => 'rw' |
|---|
| 34 | ); |
|---|
| 35 | |
|---|
| 36 | use Danga::Socket::Callback; |
|---|
| 37 | use Moobal; |
|---|
| 38 | use IO::Socket::INET; |
|---|
| 39 | use Socket qw(IPPROTO_TCP SOL_SOCKET SO_SNDBUF); |
|---|
| 40 | use UNIVERSAL::require; |
|---|
| 41 | |
|---|
| 42 | sub start_listen |
|---|
| 43 | { |
|---|
| 44 | my $self = shift; |
|---|
| 45 | |
|---|
| 46 | my $sock = IO::Socket::INET->new( |
|---|
| 47 | LocalAddr => $self->hostport, |
|---|
| 48 | Proto => IPPROTO_TCP, |
|---|
| 49 | Listen => 1024, |
|---|
| 50 | ReuseAddr => 1, |
|---|
| 51 | ); |
|---|
| 52 | |
|---|
| 53 | return die ("Error creating listening socket: " . ($@ || $!)) |
|---|
| 54 | unless $sock; |
|---|
| 55 | |
|---|
| 56 | if (&Moobal::RUNNING_IN_HELL) { |
|---|
| 57 | # On Windows, we have to do this a bit differently. |
|---|
| 58 | # IO::Socket should really do this for us, but whatever. |
|---|
| 59 | my $do = 1; |
|---|
| 60 | ioctl($sock, 0x8004667E, \$do) or |
|---|
| 61 | die ("Unable to make listener on @{[$self->hostport]} non-blocking: $!"); |
|---|
| 62 | } |
|---|
| 63 | else { |
|---|
| 64 | # IO::Socket::INET's Blocking => 0 just doesn't seem to work |
|---|
| 65 | # on lots of perls. who knows why. |
|---|
| 66 | IO::Handle::blocking($sock, 0) or |
|---|
| 67 | die ("Unable to make listener on @{[$self->hostport]} non-blocking: $!"); |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | # Create a Danga::Socket callback |
|---|
| 71 | Danga::Socket::Callback->new( |
|---|
| 72 | handle => $sock, |
|---|
| 73 | on_read_ready => sub { $self->accept_client( @_ ) } |
|---|
| 74 | ); |
|---|
| 75 | |
|---|
| 76 | return $self; |
|---|
| 77 | } |
|---|
| 78 | |
|---|
| 79 | # TCPListener: accepts a new client connection |
|---|
| 80 | sub accept_client { |
|---|
| 81 | my ($self, $dsocket) = @_; |
|---|
| 82 | |
|---|
| 83 | # accept as many connections as we can |
|---|
| 84 | while (my ($psock, $peeraddr) = $dsocket->{sock}->accept) { |
|---|
| 85 | IO::Handle::blocking($psock, 0); |
|---|
| 86 | |
|---|
| 87 | if (my $sndbuf = $self->service->{client_sndbuf_size}) { |
|---|
| 88 | my $rv = setsockopt($psock, SOL_SOCKET, SO_SNDBUF, pack("L", $sndbuf)); |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | if (&Moobal::DEBUG >= 1) { |
|---|
| 92 | my ($pport, $pipr) = Socket::sockaddr_in($peeraddr); |
|---|
| 93 | my $pip = Socket::inet_ntoa($pipr); |
|---|
| 94 | print "Got new conn: $psock ($pip:$pport) for " . $self->service->role . "\n"; |
|---|
| 95 | } |
|---|
| 96 | |
|---|
| 97 | # SSL promotion if necessary |
|---|
| 98 | if ($self->{sslopts}) { |
|---|
| 99 | if (&Moobal::SSL_AVAILABLE) { |
|---|
| 100 | # try to upgrade to SSL, this does no IO it just reblesses |
|---|
| 101 | # and prepares the SSL engine for handling us later |
|---|
| 102 | IO::Socket::SSL->start_SSL( |
|---|
| 103 | $psock, |
|---|
| 104 | SSL_server => 1, |
|---|
| 105 | SSL_startHandshake => 0, |
|---|
| 106 | %{ $self->{sslopts} }, |
|---|
| 107 | ); |
|---|
| 108 | print " .. socket upgraded to SSL!\n" if &Moobal::DEBUG >= 1; |
|---|
| 109 | |
|---|
| 110 | # safety checking to ensure we got upgraded |
|---|
| 111 | return $psock->close |
|---|
| 112 | unless ref $psock eq 'IO::Socket::SSL'; |
|---|
| 113 | |
|---|
| 114 | # class into new package and run with it |
|---|
| 115 | my $sslsock = new Moobal::SocketSSL($psock, $self); |
|---|
| 116 | $sslsock->try_accept; |
|---|
| 117 | |
|---|
| 118 | # all done from our point of view |
|---|
| 119 | next; |
|---|
| 120 | } |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | # puts this socket into the right class |
|---|
| 124 | $self->class_new_socket($psock); |
|---|
| 125 | } |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | my %ROLE2CLIENTCLASS = ( |
|---|
| 129 | reverse_proxy => 'Moobal::Component::Proxy', |
|---|
| 130 | management => 'Moobal::Component::Management', |
|---|
| 131 | web_server => 'Moobal::Component::ClientHTTP', |
|---|
| 132 | selector => 'Moobal::Component::HTTP' |
|---|
| 133 | ); |
|---|
| 134 | |
|---|
| 135 | sub class_new_socket { |
|---|
| 136 | my ( $self, $psock ) = @_; |
|---|
| 137 | |
|---|
| 138 | my $service = $self->service; |
|---|
| 139 | my $service_role = $service->role; |
|---|
| 140 | my $client_class = $ROLE2CLIENTCLASS{ $service_role }; |
|---|
| 141 | |
|---|
| 142 | my $ret; |
|---|
| 143 | if ($client_class) { |
|---|
| 144 | if (! Class::MOP::is_class_loaded($client_class) ) { |
|---|
| 145 | $client_class->require or die; |
|---|
| 146 | } |
|---|
| 147 | $ret = $client_class->new( |
|---|
| 148 | context => $service->context, |
|---|
| 149 | service => $service, socket => $psock ); |
|---|
| 150 | # } elsif (my $creator = Moobal::Service::get_role_creator($service_role)) { |
|---|
| 151 | # # was defined by a plugin, so we want to return one of these |
|---|
| 152 | # $ret = $creator->( service => $service, socket => $psock); |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | return $ret; |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | # XXX TODO LATER |
|---|
| 159 | #sub as_string { |
|---|
| 160 | # my Moobal::TCPListener $self = shift; |
|---|
| 161 | # my $ret = $self->SUPER::as_string; |
|---|
| 162 | # my Moobal::Service $svc = $self->service; |
|---|
| 163 | # $ret .= ": listening on $self->{hostport} for service '$svc->{name}'"; |
|---|
| 164 | # return $ret; |
|---|
| 165 | #} |
|---|
| 166 | # |
|---|
| 167 | #sub as_string_html { |
|---|
| 168 | # my Moobal::TCPListener $self = shift; |
|---|
| 169 | # my $ret = $self->SUPER::as_string_html; |
|---|
| 170 | # my Moobal::Service $svc = $self->service; |
|---|
| 171 | # $ret .= ": listening on $self->{hostport} for service <b>$svc->{name}</b>"; |
|---|
| 172 | # return $ret; |
|---|
| 173 | #} |
|---|
| 174 | # |
|---|
| 175 | #sub die_gracefully { |
|---|
| 176 | # # die off so we stop waiting for new connections |
|---|
| 177 | # my $self = shift; |
|---|
| 178 | # $self->close('graceful_death'); |
|---|
| 179 | #} |
|---|
| 180 | |
|---|
| 181 | 1; |
|---|
| 182 | |
|---|
| 183 | |
|---|
| 184 | # Local Variables: |
|---|
| 185 | # mode: perl |
|---|
| 186 | # c-basic-indent: 4 |
|---|
| 187 | # indent-tabs-mode: nil |
|---|
| 188 | # End: |
|---|