Hi I 'borrowed' the tcp port forwarder script from the POE cookbook to use as a bidirectional port forwarder with a filter built into 1 direction. The data stream is actually SOAP, as is the response. The forward filter works perfectly, the problem is with the response. For some reason, the initiator does not recognize the response. Although the data stream is http, I wanted to treat it a strictly a data stream with out any parsing or decoding. The filter uses straight regexp to make the necessary changes. The response is suppose to be sent thru unchanged.
The script seems to work perfectly except, the originating client does't appear to accept the response. When I use a perl script based on LWP::Useragent and do a POST, the response is exactly what I would expect. What can be the difference? Anyone see a problem? My code (redirects blanked): #!/usr/bin/perl -w #################################################################################################### # Filename: EdcEchoToEwocPoe.pl # Author: Tom Lines # Description: Filters the SOAP messages from ECHO to EWOC, Script taken from the Perl POE Cookbook # Version: 1.00 # Date: 19 Jun 2009 # # Ver When Who What # ----- ------------- --------------- -------------------------------------------------------------- # 1.0 19 Jun 2009 Tom Lines Initial # 1.1 24 Jun 2009 Tom Lines Added SSL # #################################################################################################### use strict; use Socket; use POSIX qw(errno_h); use FindBin; use POE qw( Wheel::ReadWrite Wheel::SocketFactory Filter::Stream ); use Net::SSLeay; use POE::Component::SSLify qw( Client_SSLify Server_SSLify SSLify_Options ); # The redirection table. my %redirects = ( ); # Main loop. Create a new server for each record in %redirects. # Run POE until all the servers (and their forwarders) shut down. # # The client is ECHO # The server is EWOC open( LOG, ">", "/usr/ecs/OPS/CUSTOM/logs/EdcEchoToEwoc.log" ); select( LOG ); $|++; while ( my ( $from, $to ) = each %redirects ) { my ( $from_address, $from_port ) = split( /:/, $from ); my ( $to_address, $to_port ) = split( /:/, $to ); &server_create( $from_address, $from_port, $to_address, $to_port ); } $poe_kernel->run(); close( LOG ); exit; #--------------------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------------------- # Create a session that will forward data between two sockets. sub forwarder_create { my ( $handle, $peer_host, $peer_port, $remote_addr, $remote_port ) = @_; POE::Session->create( inline_states => { _start => \&forwarder_start, _stop => \&forwarder_stop, client_input => \&forwarder_client_input, # Client sent something. client_error => \&forwarder_client_error, # Error on client socket. server_connect => \&forwarder_server_connect, # Connected to server. server_input => \&forwarder_server_input, # Server sent something. server_error => \&forwarder_server_error, # Error on server socket. }, # Pass some things to forwarder_start(): # ARG0, ARG1, ARG2, ARG3, ARG4 args => [ $handle, $peer_host, $peer_port, $remote_addr, $remote_port ] ); } =for cookbook The forwarder has been created. This function sets up its initial state. Every Session instance has its own HEAP. This function stores information about the ports being redirected in its instance's heap. It then logs the redirection to STDOUT and begins two wheels. The first wheel, an instance of POE::Wheel::ReadWrite, is used to interact with the client. The second wheel is a POE::Wheel::SocketFactory instance. It's used to connect to the server. =cut #--------------------------------------------------------------------------------------------------- sub forwarder_start { my ( $heap, $session, $socket, $peer_host, $peer_port, $remote_addr, $remote_port ) = @_[ HEAP, SESSION, ARG0, ARG1, ARG2, ARG3, ARG4 ]; $heap ->{ log } = $session->ID; $peer_host = inet_ntoa( $peer_host ); $heap ->{ peer_host } = $peer_host; $heap ->{ peer_port } = $peer_port; $heap ->{ remote_addr } = $remote_addr; $heap ->{ remote_port } = $remote_port; print "[$heap->{log}] Accepted connection from $peer_host:$peer_port\n"; $heap ->{ state } = 'connecting'; $heap ->{ queue } = []; $heap ->{ wheel_client } = POE::Wheel::ReadWrite->new( Handle => $socket, Driver => POE::Driver::SysRW->new, Filter => POE::Filter::Stream->new, InputEvent => 'client_input', ErrorEvent => 'client_error', ); $heap ->{ wheel_server } = POE::Wheel::SocketFactory->new( RemoteAddress => $remote_addr, RemotePort => $remote_port, SuccessEvent => 'server_connect', FailureEvent => 'server_error', ); } #--------------------------------------------------------------------------------------------------- # The forwarder has stopped. Log that it's done. sub forwarder_stop { my $heap = $_[ HEAP ]; print "[$heap->{log}] Closing redirection session\n"; } #--------------------------------------------------------------------------------------------------- # The forwarder has received data from its client side. Pass the data through to the server # if it's connected. Otherwise hold the data in a queue until the server connects. sub forwarder_client_input { my ( $heap, $input ) = @_[ HEAP, ARG0 ]; if ( $heap ->{ state } eq 'connecting' ) { push @{ $heap ->{ queue } }, $input; } else { # Filter the input from ECHO to replace FtpPull w/DVD $input = &Filter( $heap, $input ); if ( ( exists $heap ->{ wheel_server } ) && $input ) { print "[$heap->{log}] To EWOC:\n$input\n"; $heap ->{ wheel_server }->put( $input ); } } } #--------------------------------------------------------------------------------------------------- # The forwarder has received an error from the client. Shut down both sides of the connection. # Log the error in a manner appropriate to its type. sub forwarder_client_error { my ( $kernel, $heap, $operation, $errnum, $errstr ) = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ]; if ( $errnum ) { print( "[$heap->{log}] Client connection encountered ", "$operation error $errnum: $errstr\n" ); } else { print "[$heap->{log}] Client closed connection.\n"; } delete $heap ->{ wheel_client }; delete $heap ->{ wheel_server }; } #--------------------------------------------------------------------------------------------------- # The forwarder's SocketFactory has successfully connected to the server. Log the success, # and create a ReadWrite wheel to interact with the server socket. If the client sent anything # during the connection process, pass it through to the server now. sub forwarder_server_connect { my ( $kernel, $session, $heap, $socket ) = @_[ KERNEL, SESSION, HEAP, ARG0 ]; my ( $local_port, $local_addr ) = unpack_sockaddr_in( getsockname( $socket ) ); $local_addr = inet_ntoa( $local_addr ); print( "[$heap->{log}] Established forward from local ", "$local_addr:$local_port to remote ", $heap ->{ remote_addr }, ':', $heap ->{ remote_port }, "\n" ); # Replace the SocketFactory wheel with a ReadWrite wheel. $heap ->{ wheel_server } = POE::Wheel::ReadWrite->new( Handle => $socket, Driver => POE::Driver::SysRW->new, Filter => POE::Filter::Stream->new, InputEvent => 'server_input', ErrorEvent => 'server_error', ); $heap ->{ state } = 'connected'; foreach my $pending ( @{ $heap ->{ queue } } ) { $kernel->call( $session, 'client_input', $pending ); } $heap ->{ queue } = []; } #--------------------------------------------------------------------------------------------------- # The forwarder has received data from its server side. Pass that through to the client. sub forwarder_server_input { my ( $heap, $input ) = @_[ HEAP, ARG0 ]; print "[$heap->{log}] From EWOC:\n$input\n"; exists( $heap ->{ wheel_client } ) and $heap ->{ wheel_client }->put( $input ); } #--------------------------------------------------------------------------------------------------- # The forwarder has received an error from the server. Shut down both sides of the connection. # Log the error in a manner appropriate to its type. sub forwarder_server_error { my ( $kernel, $heap, $operation, $errnum, $errstr ) = @_[ KERNEL, HEAP, ARG0, ARG1, ARG2 ]; if ( $errnum ) { print( "[$heap->{log}] Server connection encountered ", "$operation error $errnum: $errstr\n" ); } else { print "[$heap->{log}] Server closed connection.\n"; } delete $heap ->{ wheel_client }; delete $heap ->{ wheel_server }; } #--------------------------------------------------------------------------------------------------- # This is a stream-based forwarder server. It listens on TCP ports, and it spawns new forwarders # to redirect incoming connections. # Create a session that acts as the forwarder server. sub server_create { my ( $local_address, $local_port, $remote_address, $remote_port ) = @_; POE::Session->create( inline_states => { _start => \&server_start, _stop => \&server_stop, accept_success => \&server_accept_success, accept_failure => \&server_accept_failure, }, # Pass this function's parameters to the server_start(). # ARG0, ARG1, ARG2, ARG3 args => [ $local_address, $local_port, $remote_address, $remote_port ] ); } #--------------------------------------------------------------------------------------------------- # Start the server. # This records where the server should connect, and it creates the listening socket factory. sub server_start { my ( $heap, $local_addr, $local_port, $remote_addr, $remote_port ) = @_[ HEAP, ARG0, ARG1, ARG2, ARG3 ]; print "+ Redirecting $local_addr:$local_port to $remote_addr:$remote_port\n"; $heap ->{ local_addr } = $local_addr; $heap ->{ local_port } = $local_port; $heap ->{ remote_addr } = $remote_addr; $heap ->{ remote_port } = $remote_port; $heap ->{ server_wheel } = POE::Wheel::SocketFactory->new( BindAddress => $local_addr, # bind to this address BindPort => $local_port, # and bind to this port Reuse => 'yes', # reuse immediately SuccessEvent => 'accept_success', # generate this event on connection FailureEvent => 'accept_failure', # generate this event on error ); } #--------------------------------------------------------------------------------------------------- # The server is stopping. Log that fact. sub server_stop { my $heap = $_[ HEAP ]; print( "- Redirection from $heap->{local_addr}:$heap->{local_port} to ", "$heap->{remote_addr}:$heap->{remote_port} has stopped.\n" ); } #--------------------------------------------------------------------------------------------------- # The server has accepted a client connection. Pass the details about it to the function that # creates a new forwarder. This is as unnecessary step. The contents of forwarder_create() # could have been placed directly into server_accept_success(). sub server_accept_success { my ( $heap, $socket, $peer_addr, $peer_port ) = @_[ HEAP, ARG0, ARG1, ARG2 ]; &forwarder_create( $socket, $peer_addr, $peer_port, $heap ->{ remote_addr }, $heap ->{ remote_port } ); } #--------------------------------------------------------------------------------------------------- # The server encountered an error. Log the error. If we've run out of file descriptors, # we'll have to shut down the server. A serious port redirector should just restart the server here. sub server_accept_failure { my ( $heap, $operation, $errnum, $errstr ) = @_[ HEAP, ARG0, ARG1, ARG2 ]; print( "! Redirection from $heap->{local_addr}:$heap->{local_port} to ", "$heap->{remote_addr}:$heap->{remote_port} encountered $operation ", "error $errnum: $errstr\n" ); delete $heap ->{ server_wheel } if $errnum == ENFILE or $errnum == EMFILE; } #--------------------------------------------------------------------------------------------------- sub Filter { my ( $heap, $input ) = @_; my $eol = "\x0D\x0A"; # ECHO sends data in chunks so I have to re-assemble it into 1 data stream so I can filter it. if ( $input =~ /^POST\s+.*/ ) { ( $heap ->{ header }, $heap ->{ content } ) = split( /(?:$eol){2}/, $input, 2 ); if ( $heap ->{ header } =~ /.*?Content-Length:\s+(\d+).*/ ) { $heap ->{ content_length } = $1; } } else { $heap ->{ content } .= $input; } # return if I don't have the whole content return "" unless ( defined( $heap ->{ content } ) && defined( $heap ->{ content_length } ) ); return "" unless ( length( $heap ->{ content } ) == $heap ->{ content_length } ); open( IN, "<", "$FindBin::Bin/AuthorizedMediaUsers" ); my @mediaUsers = <IN>; close( IN ); chomp @mediaUsers; my $targetIds = join( "|", @mediaUsers ); if ( $heap ->{ content } =~ /.*?\<UserId\>(?:$targetIds)\<\/UserId\>.*/gs ) { $heap ->{ content } =~ s/FtpPull/DVD/gs; $heap ->{ content } =~ s/ftppull-format/dvd-format/gs; $heap ->{ content } =~ s/FILEFORMAT/RockRidge/gs; my $len = length( $heap ->{ content } ); $heap ->{ header } =~ s/(Content-Length:\s*)\d+/$1$len/s; $heap ->{ header } =~ s/(User-Agent:\s*)\S+/$1ECHO-Filtered/s; } return sprintf( "%s%s%s%s", $heap ->{ header }, $eol, $eol, $heap ->{ content } ); } Thomas G Lines NASA LP DAAC Core Systems Software Lead Stinger Ghaffarian Technologies (SGT, Inc.) Contractor to the U.S. Geological Survey (USGS) Earth Resources Observation and Science (EROS) Center 47914 252nd Street Sioux Falls, South Dakota 57198 Phone: 605-594-2602 Email: tgli...@usgs.gov A truly great computer programmer is lazy, impatient and full of hubris. Laziness drives one to work very hard to avoid future work for a future self. Impatience has the same endgame. And hubris is required for the newest Promethean fire -- inventing computer languages. - Larry Wall