Skip to content
Browse files

Updated CHANGES. Added new files to MANIFEST. Added ReadWrite code

to Server::TCP.  Added extra-data dumping to Filter::HTTPD.
  • Loading branch information...
1 parent 076ca22 commit f7dba964afc2eada2b1ca1db87e644750c6bbe67 @rcaputo committed Nov 13, 2001
Showing with 909 additions and 93 deletions.
  1. +2 −0 MANIFEST
  2. +396 −0 lib/POE/Component/Client/TCP.pm
  3. +319 −90 lib/POE/Component/Server/TCP.pm
  4. +26 −3 lib/POE/Filter/HTTPD.pm
  5. +166 −0 tests/26_comp_tcp.t
View
2 MANIFEST
@@ -5,6 +5,7 @@ Makefile.PL
NEEDS
POE.pm
POE/Component.pm
+POE/Component/Client/TCP.pm
POE/Component/Server/TCP.pm
POE/Driver.pm
POE/Driver/SysRW.pm
@@ -100,3 +101,4 @@ t/22_wheel_run.t
t/23_nfa.t
t/24_filter_stack.t
t/25_detach.t
+t/26_comp_tcp.t
View
396 lib/POE/Component/Client/TCP.pm
@@ -0,0 +1,396 @@
+# $Id$
+
+package POE::Component::Client::TCP;
+
+use strict;
+
+use Carp qw(carp croak);
+
+# Explicit use to import the parameter constants;
+use POE::Session;
+use POE::Wheel::SocketFactory;
+use POE::Wheel::ReadWrite;
+
+# Create the client. This is just a handy way to encapsulate
+# POE::Session->create(). Because the states are so small, it uses
+# real inline coderefs.
+
+sub new {
+ my $type = shift;
+
+ # Helper so we don't have to type it all day. $mi is a name I call
+ # myself.
+ my $mi = $type . '->new()';
+
+ # If they give us lemons, tell them to make their own damn
+ # lemonade.
+ croak "$mi requires an even number of parameters" if (@_ & 1);
+ my %param = @_;
+
+ # Validate what we're given.
+ croak "$mi needs a RemoteAddress parameter"
+ unless exists $param{RemoteAddress};
+ croak "$mi needs a RemotePort parameter"
+ unless exists $param{RemotePort};
+
+ # Extract parameters.
+ my $alias = delete $param{Alias};
+ my $address = delete $param{RemoteAddress};
+ my $port = delete $param{RemotePort};
+
+ foreach ( qw( Connected ConnectError Disconnected ServerInput
+ ServerError ServerFlushed Filter
+ )
+ ) {
+ croak "$_ must be a coderef"
+ if defined($param{$_}) and ref($param{$_}) ne 'CODE';
+ }
+
+ my $conn_callback = delete $param{Connected};
+ my $conn_error_callback = delete $param{ConnectError};
+ my $disc_callback = delete $param{Disconnected};
+ my $input_callback = delete $param{ServerInput};
+ my $error_callback = delete $param{ServerError};
+ my $flush_callback = delete $param{ServerFlushed};
+ my $filter = delete $param{Filter};
+
+ # Errors.
+
+ croak "$mi requires a ServerInput parameter" unless defined $input_callback;
+
+ # Defaults.
+
+ $address = '127.0.0.1' unless defined $address;
+ $filter = POE::Filter::Line->new() unless defined $filter;
+
+ $conn_error_callback = \&_default_error unless defined $conn_error_callback;
+ $error_callback = \&_default_error unless defined $error_callback;
+
+ $disc_callback = sub {} unless defined $disc_callback;
+ $conn_callback = sub {} unless defined $conn_callback;
+ $error_callback = sub {} unless defined $error_callback;
+ $flush_callback = sub {} unless defined $flush_callback;
+
+ # Spawn the session that makes the connection and then interacts
+ # with what was connected to.
+
+ POE::Session->create
+ ( inline_states =>
+ { _start => sub {
+ my $kernel = $_[KERNEL];
+ $kernel->alias_set( $alias ) if defined $alias;
+ $kernel->yield( 'reconnect' );
+ },
+
+ # To quiet ASSERT_STATES.
+ _stop => sub { },
+ _child => sub { },
+ _signal => sub { 0 },
+
+ reconnect => sub {
+ my $heap = $_[HEAP];
+
+ $heap->{shutdown} = 0;
+
+ $heap->{server} = POE::Wheel::SocketFactory->new
+ ( RemoteAddress => $address,
+ RemotePort => $port,
+ SuccessEvent => 'got_connect_success',
+ FailureEvent => 'got_connect_error',
+ );
+ },
+
+ got_connect_success => sub {
+ my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
+
+ # Ok to overwrite like this as of 0.13.
+ $_[HEAP]->{server} = POE::Wheel::ReadWrite->new
+ ( Handle => $socket,
+ Driver => POE::Driver::SysRW->new(),
+ Filter => $filter,
+ InputEvent => 'got_server_input',
+ ErrorEvent => 'got_server_error',
+ FlushedEvent => 'got_server_flush',
+ );
+
+ $conn_callback->(@_);
+ },
+
+ got_connect_error => sub {
+ delete $_[HEAP]->{server};
+ $conn_error_callback->(@_);
+ },
+
+ got_server_error => sub {
+ my ($heap, $operation, $errnum) = @_[HEAP, ARG0, ARG1];
+
+ # Read error 0 is disconnect.
+ if ($operation eq 'read' and $errnum == 0) {
+ $disc_callback->(@_);
+ }
+ else {
+ $error_callback->(@_);
+ }
+ delete $heap->{server};
+ },
+
+ got_server_input => sub {
+ my $heap = $_[HEAP];
+ return if $heap->{shutdown};
+ $input_callback->(@_);
+ },
+
+ got_server_flush => sub {
+ my $heap = $_[HEAP];
+ $flush_callback->(@_);
+ delete $heap->{server} if $heap->{shutdown};
+ },
+
+ shutdown => sub {
+ $_[HEAP]->{shutdown} = 1;
+ },
+ },
+ );
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+POE::Component::Client::TCP - a simplified TCP client
+
+=head1 SYNOPSIS
+
+ use POE qw(Component::Client::TCP);
+
+ # Basic usage.
+
+ POE::Component::Client::TCP->new
+ ( RemoteAddress => "127.0.0.1",
+ RemotePort => "chargen",
+ ServerInput => sub {
+ my $input = $_[ARG0];
+ print "from server: $input\n";
+ }
+ );
+
+ # Complete usage.
+
+ POE::Component::Client::TCP->new
+ ( RemoteAddress => "127.0.0.1",
+ RemotePort => "chargen",
+
+ Connected => \&handle_connect,
+ ConnectError => \&handle_connect_error,
+ Disconnected => \&handle_disconnect,
+
+ ServerInput => \&handle_server_input,
+ ServerError => \&handle_server_error,
+ ServerFlushed => \&handle_server_flush,
+
+ Filter => POE::Filter::Something->new()
+ );
+
+ # Sample callbacks.
+
+ sub handle_connect {
+ my ($socket, $peer_address, $peer_port) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub handle_connect_error {
+ my ($syscall_name, $error_number, $error_string) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub handle_disconnect {
+ # no special parameters
+ }
+
+ sub handle_server_input {
+ my $input_record = $_[ARG0];
+ }
+
+ sub handle_server_error {
+ my ($syscall_name, $error_number, $error_string) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub handle_server_flush {
+ # no special parameters
+ }
+
+ # Reserved HEAP variables:
+
+ $heap->{shutdown} = shutdown flag (set true to close a connection)
+ $heap->{server} = ReadWrite wheel representing the server
+
+ # Accepted public events.
+
+ $kernel->yield( "shutdown" ) initiate shutdown (sets $heap->{shutdown})
+ $kernel->yield( "reconnect" ) attempts to reconnect to the server
+
+=head1 DESCRIPTION
+
+The TCP client component hides the steps needed to create a client
+using Wheel::SocketFactory and Wheel::ReadWrite. The steps aren't
+many, but they're still tiresome after a while.
+
+POE::Component::Client::TCP supplies common defaults for most
+callbacks and handlers. The authors hope that clients can be created
+with as little work as possible.
+
+=head 1 Constructor Parameters
+
+=over 2
+
+=item Alias
+
+Alias is an optional component alias. It's used to post events to the
+TCP client component from other sessions. The most common use of
+Alias is to allow a client component to receive "shutdown" and
+"reconnect" events from a user interface session.
+
+=item ConnectError
+
+ConnectError is an optional callback to handle SocketFactory errors.
+These errors happen when a socket can't be created or connected to a
+remote host.
+
+ConnectError must contain a subroutine reference. The subroutine will
+be called as a SocketFactory error handler. In addition to the usual
+POE event parameters, ARG0 will contain the name of the syscall that
+failed. ARG1 will contain the numeric version of $! after the
+failure, and ARG2 will contain $!'s string version.
+
+Depending on the nature of the error and the type of client, it may be
+useful to post a reconnect event from ConnectError's callback.
+
+ sub handle_connect_error {
+ $_[KERNEL]->delay( reconnect => 60 );
+ }
+
+The component will shut down after ConnectError if a reconnect isn't
+requested.
+
+=item Connected
+
+Connected is an optional callback to notify a program that
+SocketFactory succeeded. This is an advisory callback, and it should
+not create a ReadWrite wheel itself. The component will handle
+setting up ReadWrite.
+
+ARG0 contains a socket handle. It's not necessary to save this under
+most circumstances. ARG1 and ARG2 contain the peer address and port
+as returned from getpeername().
+
+=item Disconnected
+
+Disconnected is an optional callback to notify a program that an
+established server connection has shut down. It has no special
+parameters.
+
+For persistent connections, such as MUD bots or long-running services,
+a useful thing to do from a Disconnected handler is reconnect. For
+example, this reconnects after waiting a minute:
+
+ sub handle_disconnect {
+ $_[KERNEL]->delay( reconnect => 60 );
+ }
+
+The component will shut down after disconnecting if a reconnect isn't
+requested.
+
+=item Filter
+
+Filter contains a POE::Filter object reference. It is optional, and
+the component will default to POE::Filter::Line->new() if a Filter is
+omitted.
+
+=item RemoteAddress
+
+RemoteAddress contains the address to connect to. It is required and
+may be a host name ("poe.perl.org") a dotted quad ("127.0.0.1") or a
+packed socket address.
+
+=item RemotePort
+
+RemotePort contains the port to connect to. It is required and may be
+a service name ("echo") or number (7).
+
+=item ServerError
+
+ServerError is an optional callback to notify a program that an
+established server connection has encountered some kind of error.
+Like with ConnectError, it accepts the traditional error parameters:
+
+ARG0 contains the name of the syscall that failed. ARG1 contains the
+numeric failure code from $!. ARG2 contains the string version of $!.
+
+The component will shut down after a server error if a reconnect isn't
+requested.
+
+=item ServerFlushed
+
+ServerFlushed is an optional callback to notify a program that
+ReadWrite's output buffers have completely flushed. It has no special
+parameters.
+
+The component will shut down after a server flush if $heap->{shutdown}
+is set.
+
+=item ServerInput
+
+ServerInput is a required callback. It is called for each input
+record received from a server. ARG0 contains the input record, the
+format of which is determined by POE::Component::Client::TCP's Filter
+parameter.
+
+The ServerInput function will stop being called when $heap->{shutdown}
+is true.
+
+=back
+
+=head 1 Public Events
+
+=over 2
+
+=item reconnect
+
+Instruct the TCP client component to reconnect to the server. If it's
+already connected, it will disconnect harshly, discarding any pending
+input or output data.
+
+=item shutdown
+
+When a Client::TCP component receives a shutdown event, it initiates a
+graceful shutdown. Any subsequent server input will be ignored, and
+any pending output data will be flushed. Once the connection is dealt
+with, the component will self-destruct.
+
+=back
+
+=head1 SEE ALSO
+
+POE::Component::Server::TCP, POE::Wheel::SocketFactory,
+POE::Wheel::ReadWrite, POE::Filter
+
+=head1 CAVEATS
+
+This may not be suitable for complex client tasks.
+
+This looks nothing like what Ann envisioned.
+
+=head1 AUTHORS & COPYRIGHTS
+
+POE::Component::Client::TCP is Copyright 2001 by Rocco Caputo. All
+rights are reserved. POE::Component::Client::TCP is free software,
+and it may be redistributed and/or modified under the same terms as
+Perl itself.
+
+POE::Component::Client::TCP is based on code, used with permission,
+from Ann Barcomb E<lt>ann@domaintje.comE<gt>.
+
+POE::Component::Client::TCP is based on code, used with permission,
+from Jos Boumans E<lt>kane@cpan.orgE<gt>.
+
+=cut
View
409 lib/POE/Component/Server/TCP.pm
@@ -5,10 +5,7 @@ package POE::Component::Server::TCP;
use strict;
use Carp qw(carp croak);
-use Socket qw(INADDR_ANY);
-use vars qw($VERSION);
-
-$VERSION = 1.01;
+use Socket qw(INADDR_ANY inet_ntoa);
# Explicit use to import the parameter constants.
use POE::Session;
@@ -32,80 +29,193 @@ sub new {
# Validate what we're given.
croak "$mi needs a Port parameter" unless exists $param{Port};
- croak "$mi needs an Acceptor parameter" unless exists $param{Acceptor};
# Extract parameters.
- my $alias = delete $param{Alias};
- my $address = delete $param{Address};
- my $port = delete $param{Port};
+ my $alias = delete $param{Alias};
+ my $address = delete $param{Address};
+ my $port = delete $param{Port};
+
+ foreach ( qw( Acceptor Error ClientInput ClientConnected
+ ClientDisconnected ClientError ClientFlushed
+ )
+ ) {
+ croak "$_ must be a coderef"
+ if defined($param{$_}) and ref($param{$_}) ne 'CODE';
+ }
+
my $accept_callback = delete $param{Acceptor};
my $error_callback = delete $param{Error};
+ my $client_input = delete $param{ClientInput};
+
+ # Acceptor and ClientInput are mutually exclusive.
+ croak "$mi needs either an Acceptor or a ClientInput but not both"
+ unless defined($accept_callback) xor defined($client_input);
+
+ # Make sure ClientXyz are accompanied by ClientInput.
+ unless (defined($client_input)) {
+ foreach (grep /^Client/, keys %param) {
+ croak "$_ not permitted without ClientInput";
+ }
+ }
+
+ my $client_connected = delete $param{ClientConnected};
+ my $client_disconnected = delete $param{ClientDisconnected};
+ my $client_error = delete $param{ClientError};
+ my $client_filter = delete $param{ClientFilter};
+ my $client_flushed = delete $param{ClientFlushed};
+
# Defaults.
+
$address = INADDR_ANY unless defined $address;
+ $error_callback = \&_default_server_error unless defined $error_callback;
+
+ if (defined $client_input) {
+ $client_filter = POE::Filter::Line->new() unless defined $client_filter;
+ $client_error = \&_default_client_error unless defined $client_error;
+ $client_connected = sub {} unless defined $client_connected;
+ $client_disconnected = sub {} unless defined $client_disconnected;
+ $client_flushed = sub {} unless defined $client_flushed;
+
+ # Revise the acceptor callback so it spawns a session.
+
+ $accept_callback = sub {
+ my ($socket, $remote_addr, $remote_port) = @_[ARG0, ARG1, ARG2];
+ POE::Session->create
+ ( inline_states =>
+ { _start => sub {
+ my ( $kernel, $session, $heap ) = @_[KERNEL, SESSION, HEAP];
+
+ $heap->{shutdown} = 0;
+ $heap->{remote_ip} = inet_ntoa($remote_addr);
+ $heap->{remote_port} = $remote_port;
+
+ $heap->{client} = POE::Wheel::ReadWrite->new
+ ( Handle => $socket,
+ Driver => POE::Driver::SysRW->new(),
+ Filter => $client_filter,
+ InputEvent => 'got_input',
+ ErrorEvent => 'got_error',
+ FlushedEvent => 'got_flush',
+ );
+
+ $kernel->call( $session, 'got_connect' );
+ },
+
+ # To quiet ASSERT_STATES.
+ _child => sub { },
+ _signal => sub { 0 },
+
+ got_connect => $client_connected,
+ got_input => sub {
+ my $heap = $_[HEAP];
+ return if $heap->{shutdown};
+ $client_input->(@_);
+ },
+ got_error => sub {
+ my ($heap, $operation, $errnum) = @_[HEAP, ARG0, ARG1];
+
+ # Read error 0 is disconnect.
+ if ($operation eq 'read' and $errnum == 0) {
+ $client_disconnected->(@_);
+ }
+ else {
+ $client_error->(@_);
+ }
+ delete $heap->{client};
+ },
+ got_flush => sub {
+ my $heap = $_[HEAP];
+ $client_flushed->(@_);
+ delete $heap->{client} if $heap->{shutdown};
+ },
+ shutdown => sub {
+ $_[HEAP]->{shutdown} = 1;
+ },
+ _stop => $client_disconnected,
+
+ got_flushed => sub {
+ my ($kernel, $heap) = @_[KERNEL, HEAP];
+ delete $heap->{client} if $heap->{shutdown};
+ },
+ },
+ );
+ };
+ };
+
# Complain about strange things we're given.
foreach (sort keys %param) {
carp "$mi doesn't recognize \"$_\" as a parameter";
}
- # Create the session, at long last.
-
- POE::Session->new
-
- # The POE::Session has been set up. Create a listening socket
- # factory which will call back $callback with accepted client
- # sockets.
- ( _start =>
- sub {
- if (defined $alias) {
- $_[HEAP]->{alias} = $alias;
- $_[KERNEL]->alias_set( $alias );
- }
-
- $_[HEAP]->{listener} = POE::Wheel::SocketFactory->new
- ( BindPort => $port,
- BindAddress => $address,
- Reuse => 'yes',
- SuccessState => 'got_connection',
- FailureState => 'got_error',
- );
- },
-
- # Catch an error.
- got_error => ( defined($error_callback)
- ? $error_callback
- : \&default_error_handler
- ),
-
- # We accepted a connection. Do something with it.
- got_connection => $accept_callback,
-
- # Shut down.
- shutdown => sub {
- delete $_[HEAP]->{listener};
- $_[KERNEL]->alias_remove( $_[HEAP]->{alias} )
- if defined $_[HEAP]->{alias};
+ # Create the session, at long last. This is done inline so that
+ # closures can customize it.
+
+ POE::Session->create
+ ( inline_states =>
+ { _start =>
+ sub {
+ if (defined $alias) {
+ $_[HEAP]->{alias} = $alias;
+ $_[KERNEL]->alias_set( $alias );
+ }
+
+ $_[HEAP]->{listener} = POE::Wheel::SocketFactory->new
+ ( BindPort => $port,
+ BindAddress => $address,
+ Reuse => 'yes',
+ SuccessState => 'got_connection',
+ FailureState => 'got_error',
+ );
+ },
+
+ # Catch an error.
+ got_error => $error_callback,
+
+ # We accepted a connection. Do something with it.
+ got_connection => $accept_callback,
+
+ # Shut down.
+ shutdown => sub {
+ delete $_[HEAP]->{listener};
+ $_[KERNEL]->alias_remove( $_[HEAP]->{alias} )
+ if defined $_[HEAP]->{alias};
+ },
+
+ # Dummy states to prevent warnings.
+ _signal => sub { return 0 },
+ _stop => sub { return 0 },
+ _child => sub { },
+ _signal => sub { 0 },
},
-
- # Dummy states to prevent warnings.
- _signal => sub { return 0 },
- _stop => sub { return 0 },
);
# Return undef so nobody can use the POE::Session reference. This
# isn't very friendly, but it saves grief later.
undef;
}
-# The default error handler logs to STDERR and shuts down the server.
-sub default_error_handler {
+# The default server error handler logs to STDERR and shuts down the
+# server.
+
+sub _default_server_error {
warn( 'Server ', $_[SESSION]->ID,
" got $_[ARG0] error $_[ARG1] ($_[ARG2])\n"
);
delete $_[HEAP]->{listener};
}
+# The default client error handler logs to STDERR and shuts down the
+# server.
+
+sub _default_client_error {
+ warn( 'Client ', $_[SESSION]->ID,
+ " got $_[ARG0] error $_[ARG1] ($_[ARG2])\n"
+ );
+ delete $_[HEAP]->{client};
+}
+
1;
__END__
@@ -116,18 +226,9 @@ POE::Component::Server::TCP - a simplified TCP server
=head1 SYNOPSIS
- use POE;
+ use POE qw(Component::Server::TCP);
- sub accept_handler {
- my ($socket, $remote_address, $remote_port) = @_[ARG0, ARG1, ARG2];
- # code goes here to handle the accepted socket
- }
-
- sub error_handler {
- my ($op, $errnum, $errstr) = @_[ARG0, ARG1, ARG2];
- warn "server encountered $op error $errnum: $errstr";
- # possibly shut down the server
- }
+ # First form just accepts connections.
POE::Component::Server::TCP->new
( Port => $bind_port,
@@ -136,31 +237,102 @@ POE::Component::Server::TCP - a simplified TCP server
Error => \&error_handler, # Optional.
);
+ # Second form accepts and handles connections.
+
+ POE::Component::Server::TCP->new
+ ( Port => $bind_port,
+ Address => $bind_address, # Optional.
+ Acceptor => \&accept_handler, # Optional.
+ Error => \&error_handler, # Optional.
+
+ ClientInput => \&handle_client_input, # Required.
+ ClientConnected => \&handle_client_connect, # Optional.
+ ClientDisconnected => \&handle_client_disconnect, # Optional.
+ ClientError => \&handle_client_error, # Optional.
+ ClientFlushed => \&handle_client_flush, # Optional.
+ ClientFilter => POE::Filter::Xyz->new(), # Optional.
+ );
+
+ # Call signatures for handlers.
+
+ sub accept_handler {
+ my ($socket, $remote_address, $remote_port) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub error_handler {
+ my ($syscall_name, $error_number, $error_string) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub handle_client_input {
+ my $input_record = $_[ARG0];
+ }
+
+ sub handle_client_error {
+ my ($syscall_name, $error_number, $error_string) = @_[ARG0, ARG1, ARG2];
+ }
+
+ sub handle_client_connect {
+ # no special parameters
+ }
+
+ sub handle_client_disconnect {
+ # no special parameters
+ }
+
+ sub handle_client_flush {
+ # no special parameters
+ }
+
+ # Reserved HEAP variables:
+
+ $heap->{listener} = SocketFactory (only Acceptor and Error callbacks)
+ $heap->{client} = ReadWrite (only in ClientXyz callbacks)
+ $heap->{remote_ip} = remote IP address in dotted form
+ $heap->{remote_port} = remote port
+ $heap->{remote_addr} = packed remote address and port
+ $heap->{shutdown} = shutdown flag (set true to close a connection)
+
+ # Accepted public events.
+
+ $kernel->yield( "shutdown" ) initiate shutdown (sets $heap->{shutdown})
+
=head1 DESCRIPTION
The TCP server component hides the steps needed to create a server
using Wheel::SocketFactory. The steps aren't many, but they're still
-repetitive and thus boring.
+tiresome after a while.
-POE::Component::Server::TCP helps out by supplying a default error
-handler. This handler will write an error message on STDERR and shut
-the server down.
+POE::Component::Server::TCP supplies common defaults for most
+callbacks and handlers. The authors hope that servers can be created
+with as little work as possible.
-The TCP server component takes three named arguments. It's expected
-to accept other parameters as it evolves.
+Constructor parameters:
=over 2
+=item Acceptor
+
+Acceptor is a coderef which will be called to handle accepted sockets.
+The coderef receives its parameters directly from SocketFactory's
+SuccessEvent. ARG0 is the accepted socket handle, suitable for giving
+to a ReadWrite wheel. ARG1 and ARG2 contain the packed remote address
+and numeric port, respectively. ARG3 is the SocketFactory wheel's ID.
+
+ Acceptor => \&accept_handler
+
+Acceptor and ClientInput are mutually exclusive. Enabling one
+prohibits the other.
+
=item Address
-Address is the optional interface address the listening socket will be
-bound to. When omitted, it defaults to INADDR_ANY.
+Address is the optional interface address the TCP server will bind to.
+It defaults to INADDR_ANY.
Address => '127.0.0.1'
-It's passed directly to SocketFactory's BindAddress parameter, and so
-it can be in whatever form SocketFactory supports. At the time of
-this writing, that's a dotted quad, a host name, or a packed Internet
+It's passed directly to SocketFactory's BindAddress parameter, so it
+can be in whatever form SocketFactory supports. At the time of this
+writing, that's a dotted quad, a host name, or a packed Internet
address.
=item Alias
@@ -174,28 +346,64 @@ Later on, the 'chargen' service can be shut down with:
$kernel->post( chargen => 'shutdown' );
-=item Port
+=item ClientConnected
-Port is the port the listening socket will be bound to.
+ClientConnected is a coderef that will be called for each new client
+connection. ClientConnected callbacks receive the usual POE
+parameters, but nothing special is included.
- Port => 30023
+=item ClientDisconnected
-=item Acceptor
+ClientDisconnected is a coderef that will be called for each client
+disconnection. ClientDisconnected callbacks receive the usual POE
+parameters, but nothing special is included.
-Acceptor is a coderef which will be called to handle accepted sockets.
-The coderef is used as POE::Wheel::SocketFactory's SuccessState, so it
-accepts the same parameters.
+=item ClientError
+
+ClientError is a coderef that will be called whenever an error occurs
+on a socket. It receives the usual error handler parameters: ARG0 is
+the name of the function that failed. ARG1 is the numeric failure
+code ($! in numeric context). ARG2 is the string failure code ($! in
+string context).
+
+If ClientError is omitted, a default one will be provided. The
+default error handler logs the error to STDERR and closes the
+connection.
+
+=item ClientFilter
+
+ClientFilter is a reference to a POE::Filter instance that will be
+used to interpret and serialize data for the client socket.
+POE::Component::Server::TCP will provide a generic Line filter by
+default.
- Acceptor => \&success_state
+=item ClientInput
+
+ClientInput is a coderef that will be called to handle client input.
+The callback receives its parameters directyl from ReadWrite's
+InputEvent. ARG0 is the input record, and ARG1 is the wheel's unique
+ID.
+
+ ClientInput => \&input_handler
+
+ClientInput and Acceptor are mutually exclusive. Enabling one
+prohibits the other.
=item Error
Error is an optional coderef which will be called to handle server
socket errors. The coderef is used as POE::Wheel::SocketFactory's
-FailureState, so it accepts the same parameters. If it is omitted, a
+FailureEvent, so it accepts the same parameters. If it is omitted, a
default error handler will be provided. The default handler will log
the error to STDERR and shut down the server.
+=item Port
+
+Port is the port the listening socket will be bound to. It defaults
+to INADDR_ANY, which usually lets the operating system pick a port.
+
+ Port => 30023
+
=back
=head1 EVENTS
@@ -216,19 +424,40 @@ if one is set.
=head1 SEE ALSO
-POE::Wheel::SocketFactory
+POE::Component::Client::TCP, POE::Wheel::SocketFactory,
+POE::Wheel::ReadWrite, POE::Filter
+
+=head1 CAVEATS
+
+This is not suitable for complex tasks. For example, you cannot
+engage in a challenge-response with the client-- you can only reply to
+the one message a client sends.
=head1 BUGS
-POE::Component::Server::TCP currently does not accept many of the
-options that POE::Wheel::SocketFactory does, but it can be expanded
-easily to do so.
+This looks nothing like what Ann envisioned.
+
+This component currently does not accept many of the options that
+POE::Wheel::SocketFactory does.
+
+This component will not bind to several addresses. This may be a
+limitation in SocketFactory.
+
+This component needs more complex error handling which appends for
+construction errors and replaces for runtime errors, instead of
+replacing for all.
=head1 AUTHORS & COPYRIGHTS
-POE::Component::Server::TCP is Copyright 2000 by Rocco Caputo. All
-rights are reserved. POE::Component::Server::TCP is free software,
-and it may be redistributed and/or modified under the same terms as
-Perl itself.
+POE::Component::Server::TCP is Copyright 2000-2001 by Rocco Caputo.
+All rights are reserved. POE::Component::Server::TCP is free
+software, and it may be redistributed and/or modified under the same
+terms as Perl itself.
+
+POE::Component::Server::TCP is based on code, used with permission,
+from Ann Barcomb E<lt>ann@domaintje.comE<gt>.
+
+POE::Component::Server::TCP is based on code, used with permission,
+from Jos Boumans E<lt>kane@cpan.orgE<gt>.
=cut
View
29 lib/POE/Filter/HTTPD.pm
@@ -47,9 +47,32 @@ sub get {
# happen. -><- Maybe this should return [] instead of dying?
if($self->{'finish'}) {
- return [ $self->build_error( RC_BAD_REQUEST,
- "Did not want any more data"
- )
+
+ # This works around a request length vs. actual content length
+ # error. Looks like some browsers (mozilla!) sometimes add on an
+ # extra newline?
+
+ # return [] unless @$stream and grep /\S/, @$stream;
+
+ my (@dump, $offset);
+ $stream = join("", @$stream);
+ while (length $stream) {
+ my $line = substr($stream, 0, 16);
+ substr($stream, 0, 16) = '';
+
+ my $hexdump = unpack 'H*', $line;
+ $hexdump =~ s/(..)/$1 /g;
+
+ $line =~ tr[ -~][.]c;
+ push @dump, sprintf( "%x %s- %s\n", $offset, $hexdump, $line );
+ $offset += 16;
+ }
+
+ return [ $self->build_error
+ ( RC_BAD_REQUEST,
+ "Did not want any more data. Got this:" .
+ "<p>" . join("<br>", @dump) . "</p>"
+ )
];
}
View
166 tests/26_comp_tcp.t
@@ -0,0 +1,166 @@
+#!/usr/bin/perl -w
+# $Id$
+
+# Exercise Server::TCP and later, when it's available, Client::TCP.
+
+use strict;
+use lib qw(./lib ../lib);
+use TestSetup;
+
+test_setup(16);
+
+# Turn on all asserts.
+sub POE::Kernel::ASSERT_DEFAULT () { 1 }
+use POE qw( Component::Server::TCP Wheel::ReadWrite Component::Client::TCP );
+
+# Create a server. This one uses Acceptor to create a session of the
+# program's devising.
+
+POE::Component::Server::TCP->new
+ ( Port => 31401,
+ Alias => 'acceptor_server',
+ Acceptor => sub {
+ my ($socket, $peer_addr, $peer_port) = @_[ARG0..ARG2];
+ POE::Session->create
+ ( inline_states =>
+ { _start => sub {
+ my $heap = $_[HEAP];
+ $heap->{wheel} = POE::Wheel::ReadWrite->new
+ ( Handle => $socket,
+ InputEvent => 'got_input',
+ ErrorEvent => 'got_error',
+ FlushedEvent => 'got_flush',
+ );
+ ok(1);
+ },
+ _stop => sub {
+ ok(2);
+ },
+ got_input => sub {
+ my ($heap, $input) = @_[HEAP, ARG0];
+ ok(3);
+ $heap->{wheel}->put("echo: $input");
+ $heap->{shutdown} = 1 if $input eq "quit";
+ },
+ got_error => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "server got $operation error $errnum: $errstr\n";
+ },
+ got_flush => sub {
+ my $heap = $_[HEAP];
+ ok(4);
+ delete $heap->{wheel} if $heap->{shutdown};
+ },
+ },
+ );
+ },
+ );
+
+# Create a server. This one uses ClientXyz to process clients instead
+# of a user-defined session.
+
+POE::Component::Server::TCP->new
+ ( Port => 31402,
+ Alias => 'input_server',
+ ClientInput => sub {
+ my ($heap, $input) = @_[HEAP, ARG0];
+ ok(5);
+ $heap->{client}->put("echo: $input");
+ $heap->{shutdown} = 1 if $input eq "quit";
+ },
+ ClientError => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "server got $operation error $errnum: $errstr\n";
+ delete $heap->{client};
+ },
+ ClientFlushed => sub {
+ ok(6);
+ },
+ ClientConnected => sub {
+ ok(7);
+ },
+ ClientDisconnected => sub {
+ ok(8);
+ },
+ );
+
+# A client to connect to acceptor_server.
+
+POE::Component::Client::TCP->new
+ ( RemoteAddress => '127.0.0.1',
+ RemotePort => 31401,
+
+ Connected => sub {
+ ok(9);
+ $_[HEAP]->{server}->put( "quit" );
+ },
+
+ ConnectError => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "server got $operation error $errnum: $errstr\n";
+ },
+
+ Disconnected => sub {
+ ok(10);
+ $_[KERNEL]->post( acceptor_server => 'shutdown' );
+ },
+
+ ServerInput => sub {
+ my ($heap, $input) = @_[HEAP, ARG0];
+ ok(11);
+ },
+
+ ServerError => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "$operation error $errnum: $errstr\n";
+ delete $heap->{server};
+ },
+
+ ServerFlushed => sub {
+ ok(12);
+ },
+ );
+
+# A client to connect to input_server.
+
+POE::Component::Client::TCP->new
+ ( RemoteAddress => '127.0.0.1',
+ RemotePort => 31402,
+
+ Connected => sub {
+ ok(13);
+ $_[HEAP]->{server}->put( "quit" );
+ },
+
+ ConnectError => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "client got $operation error $errnum: $errstr\n";
+ },
+
+ Disconnected => sub {
+ ok(14);
+ $_[KERNEL]->post( input_server => 'shutdown' );
+ },
+
+ ServerInput => sub {
+ my ($heap, $input) = @_[HEAP, ARG0];
+ ok(15);
+ },
+
+ ServerError => sub {
+ my ($heap, $operation, $errnum, $errstr) = @_[HEAP, ARG0..ARG2];
+ print "$operation error $errnum: $errstr\n";
+ delete $heap->{server};
+ },
+
+ ServerFlushed => sub {
+ ok(16);
+ },
+ );
+
+# Run the tests.
+
+$poe_kernel->run();
+
+results();
+exit 0;

0 comments on commit f7dba96

Please sign in to comment.
Something went wrong with that request. Please try again.