POE::Event::Message - A generic messaging protocol


POE-Event-Message documentation Contained in the POE-Event-Message distribution.

Index


Code Index:

NAME

Top

POE::Event::Message - A generic messaging protocol

VERSION

Top

This document describes version 0.05, released December, 2005.

SYNOPSIS

Top

 use POE;
 use POE::Event::Message;

 $MsgClass = "POE::Event::Message";




 # Creating Messages

 $body = <whatever>;              #  body can be almost anything 
 $mesg = $MsgClass->new( undef, $body );

 $mesg = $MsgClass->package( $body );   # alternative to 'new()' 




 # Creating Responses to Messages
 # Note that every message has a unique identifier. For
 # replies, the original 'message identifier' is retained
 # as an 'in response to' identifier.

 $resp = $MsgClass->new( $mesg        );
 $resp = $MsgClass->new( $mesg, $body );

 $resp = $MsgClass->new( $mesg->header()        );
 $resp = $MsgClass->new( $mesg->header(), $body );




 # Accessing Message Components

 $mesg->body( $body );            # replace message body
 $body = $mesg->body();           # get message body
 $head = $mesg->header();         # get message header

 print $mesg->dump();             # useful when debugging
 print $mesg->header()->dump();   # useful when debugging




 # Accessing Header Components

 $mesgID = $mesg->id();           # unique message identifier
 $respID = $mesg->r2id();         # 'In Response To' identifier

 $bool   = $mesg->hasRouting();
 $bool   = $mesg->hasRouteTo();
 $bool   = $mesg->hasRouteBack();

 $routing= $mesg->nextRouteType();      # post, call, remote or ''
 $bool   = $mesg->nextRouteIsRemote();  # 1 if remote    or 0 if not
 $bool   = $mesg->nextRouteIsLocal();   # 1 if post|call or 0 
 $bool   = $mesg->nextRouteIsPost();    # 1 if post      or 0 
 $bool   = $mesg->nextRouteIsCall();    # 1 if call      or 0 




 # Set default 'mode' used with 'route / routeto / routeback'

 $mesg->setMode( "call" );        # original default: "post"

 $mode = $mesg->mode();           # one of 'post' or 'call'




 # Routing Messages Within the POE environment
 # note that '$session' defaults to current session context
 # (and the first parameter ('mode') defaults to 'post'
 # unless the 'setMode()' method was used to change this)

 $mesg->addRouteTo( "post", $session, $to_event, @stateArgs );

 $mesg->addRouteBack( "post", "", 'my_handler', @stateArgs );

 ($resp) = $mesg->route( @routeArgs );




 # Routing Messages Outside the POE environment
 # 'sync' expects some sort of response message 
 # 'asynch' expects only 'delivery status' response

 $mesg->addRemoteRouteTo( $host, $port "sync" );

 $mesg->addRemoteRouteTo( $host, $port "async" );

 ($response) = $mesg->route( @routeArgs );




 # Processing Messages Within the POE environment
 # (brief example of 'object_state' event method)

 sub handle_send_response
 {   my($self, $stateArgs, $routeArgs) = @_[ OBJECT, ARG0, ARG1 ];

     my $message  = $routeArgs->[0];
     my $response = $MsgClass->new( $message, "" );

     return $response->route();
 }




 # Directly Sending and Receiving Messages Outside the POE environment
 # where '$fh' is a file handle, socket or whatever

 $MsgClass->write( $fh, $mesg );
 $mesg->write( $fh );

 $response = $MsgClass->read( $fh );
 $response = $mesg->read( $fh );




DESCRIPTION

Top

This class is a starting point for creating a generic application messaging protocol. The intent is for this to be used as a foundation when building network and/or event-driven applications such as Web services.

Features of POE::Event::Message include the following.

Messages are created using the 'new()|"new"' method or the envelope()|"envelope"' or package()|"package"' methods. A message body can be add during creation or afterward using the body()|"body"' method.

Responses to messages can be created using the same message creation methods but with the originating message as an added argument. This way, the unique message identifier on the originating message is added to the reply as an 'in reply to' identifier. This allows correlating originating messages with their replies, if necessary.

Message routing is flexible and can be either 'local' or 'remote'. The 'addRouteTo()|"addRouteTo"' and 'addRouteBack()|"addRouteBack"' methods are used to add 'local' routing to POE-based event services. This type of routing triggers POE's internal 'post' or 'call' mechanism for asynchronous or synchronous message delivery.

The 'addRemoteRouteTo()|"addRemoteRouteTo"' and 'addRemoteRouteBack()|"addRemoteRouteBack"' methods are used to add 'remote' routing of messages to services on remote hosts. This type of routing triggers 'read()|"read"' and 'write()|"write"' methods to send and receive messages across network sockets. Remote delivery can be either synchronous (expecting an immediate response message) or asynchronous (where response is only a 'delivery status' of either successful or unsuccessful).

Multiple forward and/or reverse routing destinations can be added to a given message. Additional local and/or remote routing destinations can be added at any time during processing. This is useful, for example, to interrupt the original message flow, as none of the original participants need be aware of any interruption(s).

When adding a routing destination, a list of 'StateArgs' can be included. These will be available to the receiving event method when the message is delivered (just like POE's postback/callback StateArgs list).

The 'route()|"route"' method is used to actually route messages to their destinations. When routing messages, a list of 'RouteArgs' can be included. These will be available to the receiving event method when the message is delivered (just like POE's postback/callback AdditionalArgs list). Note that the routed message object will be delivered as the first element of the 'RouteArgs' list reference.

This class does not explicitly use POE. This allows 'light weight' scripts that run outside the full POE environment to use this messaging protocol. When POE is not used, only the 'remote' mechanism is available. This is because the 'post' and 'call' routing semantics rely on the POE kernel.

Note also that 'local' routing destinations using 'post' and 'call' can be created outside of POE. If, for example, messages are sent via a 'remote routing' to a POE-based process, that process can then 'route()|"route"' messages using 'post' and/or 'call' to the specified event service destination(s).

When running outside of the POE environment either explicitly add a 'remote' routing -or- use the routeIsRemote()|"routeIsRemote" method (as shown in the Synopsis|"SYNOPSIS" section, above) to determine if the route()|"route" method can be called on a given message object.

Constructor

new ( [ Message ] [, Body ] )
new ( [ Header ] [, Body ] )

This method instantiates a new message object. Optionally it can be used to create a response to an existing message. A message body can also be specified during creation and/or added later.

Message | Header

The optional Message or Header argument, used when creating a response to an originating message, is expected to be either the originating message or the header object from the originating message.

Every message has a guaranteed unique message identifier. The response mechanism is used to include the original message identifier as an 'in response to' identifier. This 'r2id' can then be used, when necessary, to correlate original messages with any responses received.

Body

The optional Body argument, when included, can be simple text. The Body can also be any arbitrary Perl data structure. This class provides a filtering mechanism that allows messages containing Perl data structures to be passed across file handles, including sockets and parent/child stdio file descriptors.

WARN: Use caution when using messages containing Perl 'CODE references.' While, as of version 2.05, the underlying 'Storable' module does now support passing CODE refs, this has not been tested here.

Methods

envelope ( Body )
package ( Body )

This method is included to simplify creating a new message object. The Body argument is included as the body of the message.

Body

The required Body argument can be either simple text or a Perl data structure as explained in the new method, above.

call ( [ Session ], Event [, Args ] )
post ( [ Session ], Event [, Args ] )

These methods provide direct message routing within a POE application and will invoke the corresponding method on the POE Kernel for immediate, synchronous event dispatch (call) or queued for asynchronous event dispatch (post).

Session

This optional argument is the name of an active POE 'session'. When omitted, the current POE 'session context' is used.

Event

This argument specifies the 'Event' that will be generated.

Args

The Args parameter is an optional list that can be included with either of the POE 'post' or 'call' event generators.

addRouteTo ( Mode, [ Session ], Event [, StateArgs ] )
addRouteBack ( Mode, [ Session ], Event [, StateArgs ] )

These methods add local capabilities to messages of this class. These add routing entries to the message header that can then be invoked via the route methods, explained below.

Mode

The Mode must be a string equal to one of 'post' or 'call'. Later, when one of the route methods is used, will then invoke either the corresponding method on the POE Kernel for immediate, synchronous event dispatch (call) or queued for asynchronous event dispatch (post).

Session

This optional argument is the name of an active POE 'session'. When omitted, the current POE 'session context' is used.

Event

This argument is the name of an 'event' in an active POE 'Session'.

StateArgs

The StateArgs parameter is an optional list that can be included with either of the POE 'post' or 'call' event generators.

addRemoteRouteTo ( Host, Port, Mode, [ Session ], Event [, StateArgs ] )
addRemoteRouteBack ( Host, Port, Mode, [ Session ], Event [, StateArgs ] )

These methods add remote capabilities to messages of this class. These add routing entries to the message header that can then be invoked via the route methods, explained below.

Host

The remote host to which the message will be routed.

Port

The network port on the remote host.

Mode

The Mode must be a string equal to one of 'sync' or 'asynch'. Later, when one of the route methods is used, will then either expect an immediate response message ('sync') or simply return a status indicating success or failure of 'message delivery' only, and not wait for any other response. This is useful when some other process, perhaps on another host, will handle the response, if any.

The remaining arguments are identical as the corresponding addRouteTo()|"addRouteTo" and addRouteBack()|"addRouteBack" methods described above.

route ( [ RouteArgs ] )
routeto ( [ RouteArgs ] )
routeback ( [ RouteArgs ] )

These methods add auto-routing capabilities to messages of this class. These extract routing entries from the message header that are then used to generate POE events using immediate dispatch (when the routing entry was created with a Mode of 'call') or queued for delayed dispatch (when the routing entry was created with a Mode of 'post').

The route method will first extract routeTo entries and, when empty, will extract routeBack entries.

The routeto method will extract only those entries created using the addRouteTo method, shown above, while the routeback method will extract only those entries created using the addRouteBack method, also shown above.

If/when the routing list(s) are empty, these methods will silently do nothing. If this should cause an error, use the hasRouting, hasRouteTo or hasRouteBack methods to determine when an error should be generated.

NOTE: The 'local' routing mechanism is designed to be plug-compatible with POE's existing 'postback' mechanism. These methods add the ability to introduce multiple forward and/or reverse event destinations and can be used to temporarially interrupt normal message routing (and none of the handlers that expect to process the message will even be aware of the interruption).

NOTE: The 'remote' routing mechanism can be used as a simple alternative to the 'IKC' (Inter Kernel Communication) mechanism.

RouteArgs

The RouteArgs parameter is an optional list that can be added when any of the 'route()' methods are invoked. These arguments are then received by whichever event handler happens to receive the routed message. Note that the message itself is included as the first argument in the RouteArgs list. See the Synopsis|"Synopsis" section, above, and the Examples|"Examples" section, below, for usage syntax.

send ( FH [, Message ] )
write ( FH [, Message ] )

These methods provide the ability to send the message object through an open file handle. The 'FH' parameter should be an open file handle and the 'Message' parameter, when used, is expected to be an object of this class. When the 'Message' is omitted, the current object is sent through the file handle.

WARN: Use caution when using these methods with messages containing Perl 'CODE references.' While the underlying 'Storable' module does now support passing CODE refs, this has not been tested here.

FH

An open file handle.

Message

A message object of this class. Optional when these methods are called as object methods on an existing object, and required when these methods are called as class methods.

When omitted, Message defaults to the current message object.

read ( FH )
recv ( FH )

These methods provide the ability to receive a message object through an open file handle.

WARN: Use caution when using these methods with messages containing Perl 'CODE references.' While the underlying 'Storable' module does now support passing CODE refs, this has not been tested here.

FH

An open file handle.

id ()

This method is used to determine the unique message identifier of the current message object.

inResponseToId ()
r2id ()

For 'response messages' these methods are used to determine the message identifier of the original message.

Note: This is why, when creating a response message, it is important to include an originating message as the first argument to the 'new()|"new"' method. This way the original message ID can be collected and included as the 'r2id'.

hasRouting ()
hasRouteTo ()
hasRouteBack ()

Miscellaneous methods used to determine 'header' state. Each of these returns the next 'routing' header entry, if any, or an 'undef' value. The 'routing' entry returned is retained in the message header.

nextRouteType ()
nextRouteIsRemote ()
nextRouteIsLocal ()
nextRouteIsPost ()
nextRouteIsCall ()

Miscellaneous methods used to determine 'header' state. Each of these, with the exception of 'nextRouteType()', returns a boolean value where '0' indicates the test is false and '1' indicates the test is true.

The 'nextRouteType()' method returns a string value that is one of 'remote', 'call', 'post' or '' (an empty string).

status

This method returns the current error status of a message object. When the status code is non-zero, the error message will contain text of the corresponding error.

dump

This method is included for convenience when developing or debugging applications that use this class. This does not produce a 'pretty' output, but is formatted to show the contents of the message object and the message header object, when one exists.

EXAMPLES

Top

The following is an incomplete example of creating a network interface that accepts message objects from a client script and returns message responses.

For a complete working example of this see the 'TCP' and 'Cmd' server classes in the 'POE-Component-GCS' distribution available on CPAN.

 package POE::Component::GCS::Server::TCP;

 $ServerAlias = "MsgServer";
 $ServerPort  = 12345;

 sub spawn
 {   my($self) = @_;

     POE::Component::Server::TCP->new(
         Port     => $ServerPort,    # Bind Port
         Alias    => $ServerAlias,   # necessary for 'routeback' response

         ClientInput   => \&handle_msg_input,
         ClientFilter  => "POE::Filter::Reference",

         # Most of the events we need are pre-defined by POE.
         # Add one here to so we can create a "routeback"
         # event target to simplify the response mechanism.

         InlineStates => {
              client_msg_output => \&handle_msg_output,
         },
     );
     return;
 } 

 sub handle_msg_input
 {   my($kernel, $heap, $session, $input) = @_[KERNEL, HEAP, SESSION, ARG0];

     # The $input variable here is expected to be a "$MsgClass" object.
     # Create a msg routeback wormhole using "post" for asynch delivery.

     $input->addRouteBack( post => undef, "client_msg_output", undef );

     # Then "dispatch" the message to some command processor

     $CmdClass->dispatch( $input );        # (non-POE method)
     return;
 }

 sub handle_msg_output                # 'client_msg_output' event handler
 {   my($kernel, $heap, $session, $state_args, $result_args) =
     @_[ KERNEL,  HEAP,  SESSION,  ARG0,        ARG1       ];

     # This handler is the "routeback" target that returns data
     # to client ($reply is expected to be a "$MsgClass" object).
     # Here we simply return it to the waiting Msg-based client.

     my $reply = $result_args->[0];

     # The "client" attribute was added by POE to the heap to
     # facilitate sending the correct response to the correct
     # client. All we need to do is "put" the "$reply".

     $heap->{client}->put( $reply );

     my $sessionId = $session->ID;
     $Log->write(4, "$MsgAlias: client reply sent (cid=$sessionId)");
     return;
 }

Then, to continue this example for a moment more, if the '$CmdClass' containing the 'dispatch()' method were to determine that the incoming message was a valid message object but an illegal commnad, the entire response mechanism is as simple as this next method. In this case, the '$reply' sent ('put') just above contains the following error.

 package POE::Component::GCS::Server::Cmd;

 sub _invalid_request
 {   my($class, $message, $error_text) = @_;

     $error_text ||= "invalid command";

     # We must reply with something or TCP clients will hang. 
     # Simply closing the socket results in a "no response 
     # from server" error on the client side.

     my $command  = $message->body();
     my $response = $MsgClass->new( $message, $command );
     $response->setErr( -1, $error_text );

     $response->route();       # return error to waiting TCP client
     return;
 }




WARNINGS

Top

When routing message objects through file handles or sockets use caution with messages containing Perl 'CODE references.' While the underlying 'Storable' module does now support passing CODE refs, this has not been tested here.

Also note that this class does not explicitly use POE. This allows 'light weight' scripts that run outside the full POE environment to use this messaging protocol. When POE is not used, the 'route()' mechanism is only available when the next routing is to a remote host. This is because the 'post' and 'call' routing semantics rely on the POE kernel. Without POE, only the 'read' and 'write' mechanism is available to send messages across file handles or sockets.

However, the 'addRouteTo()' and 'addRouteBack()' methods are available outside of POE, and can be used to add 'post' and 'call' routing. For example, if messages are sent via a 'remote route' to a POE-based process, that process can then 'route()' messages which contain pre-defined 'post' and/or 'call' destinations.

When running outside of the POE environment either explicitly add a 'remote' routing -or- use the routeIsRemote() method (as shown in the Synopsis|"SYNOPSIS" section, above) to determine if the route() method can be called on a given message object.

DEPENDENCIES

Top

This class depends upon the following classes:

 POE::Event::Message::Header
 POE::Event::Message::UniqueID
 POE::Driver::SysRW
 POE::Filter::Reference

 POE::Kernel (optional, but required for 'post' and 'call' message routing)

SEE ALSO

Top

See POE::Event::Message::Header and POE::Event::Message::UniqueID.

See the POE-Component-GCS distribution available on CPAN for a complete working model of a Generic Client/Server that uses message-based events.

AUTHOR

Top

Chris Cobb [no dot spam at ccobb dot net]

COPYRIGHT

Top


POE-Event-Message documentation Contained in the POE-Event-Message distribution.

# -*- Perl -*-
#
# File:  POE/Event/Message.pm
# Desc:  A generic messaging protocol - to use as a starting point
# Date:  Mon Oct 10 10:35:59 2005
# Stat:  Prototype, Experimental
# Note:  Fix to remove message length limitation contributed 
#        by Martin Holste, Sun 21 Feb 2010
#
package POE::Event::Message;
use 5.006;
use strict;
use warnings;

our $PACK    = __PACKAGE__;
our $VERSION = '0.11';
### @ISA     = qw( );

# WARN:  Don't use POE or the POE::Kernel classes here. This is a
#        generic messaging class that can also be used outside of
#        the POE environment. Support is included for sending and
#        receiving messages across sockets (such as between a POE-
#        based server process and a non-POE client process), and
#        when sending messages across file handles (as in the case 
#        of a POE-based parent process communicating with a non-POE 
#        child process).
#

### POE::Kernel;                      # Don't use POE here!
use POE::Event::Message::Header;      # Header class wraps attributes
### POE::Event::Message::Body;        # ((not yet created))
use POE::Filter::Reference;           # Perl object filtering
use POE::Driver::SysRW;               # sysread/syswrite driver

my $HeaderClass = "POE::Event::Message::Header";
## $BodyClass   = "POE::Event::Message::Body";  # ((not yet created))
our $Driver     = new POE::Driver::SysRW;
our $Filter     = new POE::Filter::Reference;

#-------------------------------------------------------------------------
# Message Creation (new message and reply message)
# Envelope method to create new message with body data

sub new
{   my($class,$header,$body) = @_;

    # This method allows for the following usage:
    # . Creating a new message
    #     $message = $msgClass->new();
    #     $message = $msgClass->new( undef, $msgBody );  # see 'package' method
    #
    # . Creating a reply to an existing message
    #     $response = $msgClass->new( $message->header() );    # header obj
    #     $response = $msgClass->new( $message           );    # message obj
    #
    if (ref($header) and $header =~ /=HASH/ and $header->can("new") ) {
	my $msg = $header;
	$header = $HeaderClass->new( $msg->header() ),

    } else {
	$header = $HeaderClass->new( $header ),
    }

    return( bless my $self = {
	     header => $header,
	       body => $body,
	    ## body => $BodyClass->new( $body ),    # ((no BodyClass yet))
	}, ref($class)||$class 
    );
}

*envelope = \&package;

sub package
{   my($class,$msgBody) = @_;
    return $class->new("", $msgBody);        # create new message "envelope"
}

#-----------------------------------------------------------------------
# Pass through methods for message header object
# and a method to get/set the message body

sub set    { $_[0]->{header} and $_[0]->{header}->set   ( $_[1], $_[2] ) }
sub get    { $_[0]->{header} and $_[0]->{header}->get   ( $_[1]        ) }
sub del    { $_[0]->{header} and $_[0]->{header}->del   ( $_[1]        ) }
sub param  { $_[0]->{header} and $_[0]->{header}->param ( $_[1], $_[2] ) }
sub setErr { $_[0]->{header} and $_[0]->{header}->setErr( $_[1], $_[2] ) }
sub status { $_[0]->{header} and $_[0]->{header}->status()               }
sub stat   { $_[0]->{header} and $_[0]->{header}->stat()                 }
sub err    { $_[0]->{header} and $_[0]->{header}->err()                  }

sub getMode      { $_[0]->{header} and $_[0]->{header}->mode()           }
sub setMode      { $_[0]->{header} and $_[0]->{header}->mode( $_[1] )    }
sub delRouteBack { $_[0]->{header} and $_[0]->{header}->delRouteBack()   }
sub delRouteTo   { $_[0]->{header} and $_[0]->{header}->delRouteTo()     }

*delete = \&del;
*reset  = \&del;
*inResponseToId = \&r2id;

sub hasRouting   { $_[0]->{header} and $_[0]->{header}->hasRouting()     }
sub hasRouteTo   { $_[0]->{header} and $_[0]->{header}->hasRouteTo()     }
sub hasRouteBack { $_[0]->{header} and $_[0]->{header}->hasRouteBack()   }

*getRouting   = \&hasRouting;
*getRouteTo   = \&hasRouteTo;
*getRouteBack = \&hasRouteBack;

sub nextRouteType     { $_[0]->{header}->nextRouteType()     if $_[0]->{header}}
sub nextRouteIsRemote { $_[0]->{header}->nextRouteIsRemote() if $_[0]->{header}}
sub nextRouteIsLocal  { $_[0]->{header}->nextRouteIsLocal()  if $_[0]->{header}}
sub nextRouteIsPost   { $_[0]->{header}->nextRouteIsPost()   if $_[0]->{header}}
sub nextRouteIsCall   { $_[0]->{header}->nextRouteIsCall()   if $_[0]->{header}}

sub id     { $_[0]->{header}->get('id')  }    # unique message ID
sub r2id   { $_[0]->{header}->get('r2id')}    # orig. unique message ID
sub header { $_[0]->{header} }                                 # get only
sub body   { $_[1] ? $_[0]->{body} = $_[1] : $_[0]->{body} }   # get or set

sub addRouteTo
{   my($self, @args) = @_;
    return undef unless $self->{header};
    return $self->{header}->addRouteTo( @args );
}

sub addRouteBack
{   my($self, @args) = @_;
    return undef unless $self->{header};
    return $self->{header}->addRouteBack( @args );
}

sub addRemoteRouteTo
{   my($self, @args) = @_;
    return undef unless $self->{header};
    return $self->{header}->addRemoteRouteTo( @args );
}

sub addRemoteRouteBack
{   my($self, @args) = @_;
    return undef unless $self->{header};
    return $self->{header}->addRemoteRouteBack( @args );
}

#-----------------------------------------------------------------------
# Message Routing and Auto-Replies
# This is an experimental interface to provide a flexible,
# semi-automatic message routing mechanism. It provides 
# the following features as an alternative to "postbacks"
# and extends the concept somewhat.
#
# . immediate routing via "post"  (queued, asynchronous) - post method
# . immediate routing via "call"  (direct,  synchronous) - call method
#
# . forward routing, delayed      (via "post" or "call)  - addRouteTo
# . return routing,  delayed      (via "post" or "call)  - addRouteBack
# . combinations of these two
#
# . forward routing, delayed remote (via socket "write") -addRemoteRouteTo
# . return  routing, delayed remote (via socket "write") -addRemoteRouteBack
# . combinations of these two
#
# In addition, this allows the "stacking" of both forward and 
# return destinations. This is the basis of the "semi-automatic 
# routing" feature. The "route" method DOES need to be called 
# on a message, but the caller DOESN'T need to know anything 
# about the next destination(s).
#
# Some experimentation and experience will determine whether
# these are useful as designed, or if changes are needed.
# The INTENT is that intermediate destinations can be added
# to interrupt the original routing, TEMPORARIALY redirecting
# a message, while STILL allowing it to return as intended.

#---------------------------------------------
# Direct message routing, via "post" or "call"

sub post { shift->_postOrCall( "post", @_ ) }
sub call { shift->_postOrCall( "call", @_ ) }

sub _postOrCall                                      # For immediate routing
{   my($self, $mode, $session, $event, @args) = @_;

    if (! defined $INC{'POE/Kernel.pm'}) {
	return $self->setErr(-1, "'POE::Kernel' module is not loaded in '_postOrCall' method of '$PACK'");
    }

    $session ||= POE::Kernel->get_active_session()->ID();

    return unless ($session and $event);
    $mode = lc $mode;                            # a mode arg?
    $mode ||= lc $self->getMode();               # use prior setting?
    $mode = "post" unless ($mode eq "call");     # default value!

    # NOTE that this changes the default behavior of POE's post/call.
    # Here, arguments are bundled into list references so the result
    # is "more compatible" with POE's postback/callback mechanism.
    #
    # Here, we ensure that the arguments passed here are sent to the
    # target event handler as a list reference to be received in ARG0.
    # The "$self" var (current message) is sent as the only element in
    # a list reference to be received in ARG1. This allows "$self" to 
    # be "received" as if it was sent by the method where post/call was 
    # invoked. In addition, this maintains a compatible result with 
    # the "addRouteTo()/addRouteBack()" method, below. Clear as mud?
    # This is either a good idea or really cludgy. Time will tell.
    #
    # All message events sent via "post()", "call()" or "route()" will
    # result in ONLY two parameters received by the target event handler.

    # issue one of post/call

    my $argRef;

    if ( ($#args == 0) and (ref($args[0]) eq "ARRAY") ) {
	$argRef = $args[0];
    } else {
	$argRef = [ @args ];
    }

    # FIX: handle 'call' return here as done in sub '_autoRouting'

    if (! POE::Kernel->$mode( $session, $event, $argRef, [ $self ] ) ) {

	my $msg = "Error(1): POE_Kernel->$mode( $session, $event, [ $argRef ], [ $self ] )";

	$self->setErr(-1, "$msg: $! in '$PACK'");
	## warn $msg;
    }

  # warn "DEBUG: Route: POE_Kernel->$mode( $session, $event, @args )\n";

    return;
}

#---------------------------------------------
# Automatic message routing, via "post" or "call"

*autoroute = \&route;
*autoRoute = \&route;

sub route                              # forward, if possible, or reply
{   my($self,@newArgs) = @_;

    # routeTo() until empty, then routeBack()
    # 'post' returns:   1=routed,  0=nowhere to route
    # 'call' returns:   whatever 'called' method returns

    my(@list) = ();

    if ( $self->hasRouteTo() ) {
	(@list) = $self->routeTo( @newArgs );
    } elsif ( $self->hasRouteBack() ) {
	(@list) = $self->routeBack( @newArgs );
    }

    return( @list );
}

#---------------------------------------------
# Forward message routing, via "post" or "call"

*forward = \&routeTo;
*routeto = \&routeTo;

sub routeTo                            # activate "auto-forward", if any
{   my($self,@newArgs) = @_;

    my(@list) = $self->_autoRouting( "delRouteTo", @newArgs );
    return( @list );
}

#---------------------------------------------
# Return message routing, via "post" or "call"

*reply     = \&routeBack;
*routeback = \&routeBack;

sub routeBack                          # activate "auto-reply", if any
{   my($self,@newArgs) = @_;

    my(@list) = $self->_autoRouting( "delRouteBack", @newArgs );
    return( @list );
}

#---------------------------------------------
# Generic message routing method

sub _autoRouting                       # activate "auto-routing," if any
{   my($self,$type,@newArgs) = @_;

    # This "generic" method is intended to be called by
    # either the "routeTo" or "routeBack" method, which
    # in turn might be invoked by the "route" method.

    return undef unless $self->{header};

    #---------------------------------------------------------------
    # Depending on the "$type" collect a "RouteTo" or a "RouteBack"
    # Note: "@origArgs" is now included for 'delRouteTo' type.

    $type = "delRouteBack" unless $type eq "delRouteTo";

    my($host, $port, $mode, $session, $event, @origArgs) = $self->$type();

    ##warn "($host, $port, $mode, $session, $event, @origArgs)\n";

    # Must have something to post/call and somewhere to send it!
    # FIX: differentiate between call when "no routing"
    # and call when "incomplete routing" attributes.
    #
    if (! ($session and $event) ) {
	## warn "DEBUG: session='$session'  event='$event'";
	return;
    }

    #---------------------------------------------------------------
    # FIX: ToDo: complete remote routing via '$host', '$port'
    # using the "write" method, below. First, fix up message such
    # that, upon being received, a "route()" will forward it on.
    #
 ###if ($host or $port) {

    if ($port) {
	$host ||= 'localhost';

	my $socket = $self->createSocket( $host, $port );
	if (! $socket) {
	    my $err = "Error: connection to '${host}:$port' refused in '$PACK'";

	    # FIX: Do we overwrite the original error message here? or not??
	    # YES. This is a better (clearer) message here for the user.
	    #
	  # $self->setErr(-1, $err)  unless $self->status();
	    $self->setErr(-1, $err);

	    return $self;
	}

	my($stat,$err) = $self->write( $socket );
	if ($stat) {
	    $self->setErr( $stat, $err );
	    return $self;
	}

	my $response;

	# With a "call" or "sync" mode we expect an immediate
	# response message from the server. With any other
	# mode, such as "async", only a "successful send"
	# response is generated here--this assumes that if
	# an actual response message will be generated, an
	# appropriate "addRemoteRouteBack" was added earlier.

	if (($mode eq "call") or ($mode eq "sync")) {
	    $response = $self->read( $socket );
	} else {
            # FIX: Should this be handled differently??
	    $response = $self->new( $self );
	    $response->setErr( 0, "message sent successfully" );
        }

	$self->shutdownSocket();

	return $response;
    }

    #---------------------------------------------------------------
    # Determine whether we "post" (asych) or "call" (synch)

    $mode ||= lc $self->mode();                  # use prior setting?
    $mode = "post" unless ($mode eq "call");     # default value!

    #---------------------------------------------------------------
    # If we're going to rely on POE, make sure it's available.

    if (! defined $INC{'POE/Kernel.pm'}) {
	$self->setErr(-1, "'POE::Kernel' module is not loaded in '_autoRouting' method of '$PACK'");
	return undef;
    }

    #---------------------------------------------------------------
    # This now works as does 'post/call', when using 'routeTo'.
    # STRANGE SYNTAX here, but it allows for reasonably compatible
    # syntax (and a plug-compatible result) to POE's "postback" 
    # mechanism.
    #
    # NOTE that "$self" is prepended to the "@args" list here! This
    # allows "$self" (current message) to be "received" as if it was 
    # sent by the method where the 'post/call/postback' was invoked.
    # --- This obviously needs some well documented examples. ---

    my (@args);
    if ($type eq "delRouteTo") {                  # issue a post/call
	# This is kind of funky usage but, at the moment, it 
	# seems to be "the right thing." Feedback is welcome.
	# Currently we PREPEND "$self" (current message) to
	# any "new args" when "route()" method is called.
	#
	# Adding "$self" AFTER the orig args and BEFORE added args 
	# is either really good or really cludgy. Time will tell.
	#
	(@args) = ( [ @origArgs ], [ $self, @newArgs ] );

    } else {                                      # emulate postback
	# Currently, these are the same. Should the above change?
	#
	(@args) = ( [ @origArgs ], [ $self, @newArgs ] );
    }

    my(@list) = POE::Kernel->$mode( $session, $event, @args );

    if (! @list ) {

	if ($!) {
	    my $err = "Error(2): POE_Kernel->$mode( $session, $event, @args )";

	    $self->setErr(-1, "$err: $! in '$PACK'");
	    ## warn $err;
	}
    }

    ## warn "DEBUG: Route: POE_Kernel->$mode( $session, $event, @args )\n";

    # Here we return the same result as the POE::Kernel
    # .  for 'post' return a boolean indicating success
    # .  for 'call' return what the called method returns
    #
    return( @list )  if ($mode eq 'call');
    return 1;
}

#-----------------------------------------------------------------------
# Reading and Writing messages, via POE::Filter::Reference,
# for use with file/socket handles. Note that the "$message"
# argument defaults to "$self" -- this allows for nice clean
# syntax in most situations:     $message->write( $fh );

*send     = \&write;
*recv     = \&read;

sub write
{   my($self,$fh,$message) = @_;

    (defined($fh) and length($fh)) or return (1,"nowhere to write");

    # Support class OR object method
    $message = $self   if ((! $message) and ref($self));
    return (1,"nothing to write") unless $message;

    ## warn "DEBUG: write message='$message'\n";

    my $tmp = delete $self->{_socket_}   if (ref $self);
    ## warn "DEBUG fh='$fh' in 'write()'";

    $Driver->put( $Filter->put( [ $message ] ) );    # queue the message
    $Driver->flush( $fh );                           # send the message

    $! and warn "OUCH: Driver/flush failed: $!";
 ## $!  or warn "OKAY: Driver/flush succeeded";

    $self->{_socket_} = $tmp   if ($tmp and ref $self);
    return(0,"");                                    # return success
}

sub read
{   my($self,$fh) = @_;

    ## warn "DEBUG: fh='$fh' in 'read()'";

    (defined($fh) and length($fh)) or return (1,"nowhere to read");

    my $response = $Driver->get( $fh );              # fetch a reply

    if (! $response) {
	$response = $self->new();
	$response->setErr(-1, "no response from server");
	return $response;
    }

    #-------------------------------------------------------------------
    # Fix to remove message length limitation contributed 
    # by Martin Holste, Sun 21 Feb 2010:
    # Parse expected length from the beginning of the first payload
    unless (ref($response) eq 'ARRAY' and $response->[0] =~ /^(\d+)\0/){
        $response = $self->new();
        $response->setErr(-1, 'Error: Did not receive expected payload length at beginning of payload');
        return $response;
    }   
    my $expected_length = $1 + length($1) + 1;
    my $received = length($response->[0]);
    READ_LOOP: while ($received < $expected_length){
        my $buf = $Driver->get($fh);
        foreach (@$buf){
	    push @$response, $_; 
	    $received += length($_);
        }   
    }
    #-------------------------------------------------------------------
    # filter the reply, or report error at this line:
    ($response) = @{ $Filter->get( $response ) };    my $line = __LINE__;

    if (($response) and ($response =~ /=HASH/) and ($response->can( "body" ))) {
	# response is okay
    } else {
	my $body = $response ||"";
	$response = $self->new();
	$response->body( $body );
	my $err  = "Error: unknown message type in 'read' in '$PACK' at line $line";
	   $err .= "\nmessage = '$body'";
	   $err .= "  (was message too big to send?)"  if (! $body);
	$response->setErr(-1, $err );
    }

    return $response;                                # return the reply
}

sub createSocket
{   my($self,$host,$port) = @_;

    $host ||= 'localhost';
    if (! $port) {
	$self->setErr(-1, "Error: port is undefined in 'socket' in '$PACK'" );
	return undef;

    } elsif (! defined $INC{'IO/Socket.pm'} ) {
	$self->setErr(-1, "'IO::Socket' module is not loaded in 'socket' method of '$PACK'");
	return undef;
    }

    my $socket = IO::Socket::INET->new( 'PeerAddr' => $host,
                                        'PeerPort' => $port,
                                        'Proto'    => 'tcp', );
    if (! $socket) {
	$self->setErr(-1, "Error: IO_Socket_INET failed in '$PACK': $!" );
	return undef;
    }

    $self->{_socket_} = $socket;
    return $socket;
}

sub getOpenSocket
{   my($self) = @_;
    return $self->{_socket_} || undef;
}

sub shutdownSocket
{   my($self, $socket) = @_;

    $socket ||= $self->getOpenSocket();

    $socket->shutdown(2);         # proper socket etiquette:
    $socket->close();             #  a shutdown then a close.

    return;
}

#-----------------------------------------------------------------------
# Somewhat useful for debugging ... replace with a better dumper.

sub dump {
    my($self)= @_;
    my($pack,$file,$line)=caller();
    my $text  = "DEBUG: ($PACK\:\:dump)\n  self='$self'\n";
       $text .= "CALLER $pack at line $line\n  ($file)\n";
    my $value;
    foreach my $param (sort keys %$self) {
	$value = $self->{$param};
	$value = $self->zeroStr( $value, "" );  # handles value of "0"
	$text .= " $param = $value\n";
    }
    $text .= "_" x 25 ."\n";

    if (ref($self->{header})) {
	$text .= "header:\n";
	$text .= $self->{header}->dump("nohead");
    }
    return($text);
}

sub zeroStr
{   my($self,$value,$undef) = @_;
    return $undef unless defined $value;
    return "0"    if (length($value) and ! $value);
    return $value;
}
#_________________________
1; # Required by require()

__END__