Category: Networking
Author/Contact Info David Hugh-Jones (hughjonesd@yahoo.co.uk)
Description: A framework for writing distributed/P2P applications in Perl. Should be fully subclassable for a host of possible uses (see my scratchpad...).

This is my first serious open source project, and it is 1.30 in the morning, so be nice, please.

Comments on the design would be particularly welcome (I've just read Gamma etc.) So would debugging, testing etc.


package Net::Distributed;

=head1 NAME

Net::Distributed

=head1 SYNOPSIS

    use Net::Distributed;
    use Net::Distributed::Handler::Joiner;

    my $peer = new Net::Distributed (
        address => 'www.my-elite-mp3-server.com:9009',
        services => [':Joiner']
    );

    $peer->send_join_request(
        address => 'www.my-mates-mp3-server.com:6789',
    );

    $peer->run;



=head1 DESCRIPTION

Net::Distributed provides services for building distributed computing
or peer-to-peer networks with Perl. It aims to be flexible enough to c
+ope with
different (e.g. cross-language) message formats and transports,
but provides Perl-specific defaults.

Net::Distributed provides the following services:

=over 4

=item Address book

An addressbook to store information about peers,
with the ability to specify rules for required
and optional peer information, plus the ability
to load and save peer addresses from a file.

=item Message server

A message server  using HTTP::Daemon. Net::Distributed is
subclassable, should you wish to use an alternative transport

=item Message sending

Message sending using HTTP::Request. Net::Distributed is
subclassable, should you wish to use an alternative transport

=item De/serialization

Net::Distributed::Message objects provide default
methods for serialization and deserialization

=item Message handlers

Net::Distributed::Message::Handler objects provide
message handling services to the Net::Distributed object,
and may also provide utility methods.

=back

=cut

use HTTP::Daemon;
use LWP::UserAgent;
use Socket;
use Sys::Hostname;
use Net::Distributed::AddressBook;
use Net::Distributed::PeerAddress;
use Net::Distributed::Message;
use Net::Distributed::Mixin qw/configure _debug/;
use Net::Distributed::Handler::Joiner;
use Data::Dumper;
use Carp qw/cluck confess/;

use strict;
use vars qw/$AUTOLOAD $VERSION/;

$VERSION = 0.1;

=head1 METHODS

=over 4

=item new Net::Distributed(%options)

Options include

=over 6

=item address

Passed to HTTP::Daemon to provide the local IP address and port.
Net::Distributed will try to guess it if you don't provide this.

=item message_class

The class to use for messages. Should be a subclass
of Net::Distributed::Message (which is the default)

=item address_class

The class to use for peer addresses. Should be a subclass
of Net::Distributed::PeerAddress (which is the default).

=item services

An arrayref of Net::Distributed::Handler class names. Handlers
will be created as objects of these classes, and any
service methods imported. As a piece of syntactic sugar,
if you specify a name beginning with a colon (:), it will
be appended to 'Net::Distributed::Handler:' to form the full
service name.

=item email

An email address to contact.
This will be sent with messages as an HTTP header.

=item addressbook

A Net::Distributed::AddressBook object to act as the
addressbook for this peer. If this is not specified,
a new one will be created.

=item debug

Turn on debugging warning messages.

=item timeout

Sets the timeout for the HTTP::Daemon to listen. If this is set, after
timeing out the internal_processing method will be called and the
HTTP::Daemon will listen again.

Otherwise, the HTTP::Daemon blocks.

=item handlers

An arrayref to some Net::Distributed::Handler objects. You can
combine this with the (simpler) "services" option.

=item initial_addresses

An arrayref of addresses. The Distributed object will attempt to
join these networks before entering the main "run" loop. (This should
really be in N::D::H::Joiner).

=back

Many of these items can be specified later, by calling AUTOLOADed meth
+ods, e.g.

    $distributed->timeout(5);

=cut

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;
    $self->configure(@_);

    $self->{handlers} ||= [];
    foreach (@{$self->services}) {
        $_ = "Net::Distributed::Handler:$_" if $_ =~ /^:/;
        import $_ qw/:default/;
        push @{$self->handlers}, $_->new;
    }

    unless ($self->address) {
        $self->address(
                inet_ntoa(scalar gethostbyname( hostname() || 'localho
+st')) .
                '6787'    # randomly chosen
                    );
    }
    $self->{message_class}     ||= 'Net::Distributed::Message';
    $self->{address_class}     ||= 'Net::Distributed::PeerAddress';
    $self->{addressbook}     ||= new Net::Distributed::AddressBook (
            address_class => $self->{address_class}
                );

    return $self;
}

=item $distributed->run

Starts the object running, listening for messages
and performing regular internal processing.

=cut

sub run {
    my $self = shift;

    if ($self->initial_addresses) {
        $self->send_join_request(address => $_)
                            foreach @{ $self->initial_addresses};
    }

    RUN: while(1) {
        MESSAGE: while (my ($message_string, $peer_address)
                = $self->_accept_messages
                    ) {
              last MESSAGE unless $message_string;
            # parse into Message object
            my $message;
            $message = $self->deserialize_message(
                    $message_string
                        );
            $self->_debug("Received message:\n" . (Dumper $message));
            unless (ref $message) {
                warn "Could not parse message string:\n" .
                        "$message_string\nignoring"
                            ;
                next MESSAGE;
            }

            if ($peer_address) {
                $message->from($peer_address);
            }

            $self->dispatch($message);
        }

        $self->internal_processing;
    }

}

=item $distributed->handlers_for($arg)

Returns an array of Handler objects for $arg,
which can be a Message object or a message type
string.

=cut

sub handlers_for {
    my $self = shift;
    my $arg = shift;

    my $type = ref $arg ? $arg->type : $arg;
    return grep {
            $_->can_handle($type)
                } @{ $self->handlers };
}

sub dispatch {
    my $self = shift;
    my $message = shift;

    foreach (@{$self->handlers}) {
        if ($_->can_handle($message)) {
            my $continue = $_->handle($self, $message);
            return unless $continue; # pass the message to other handl
+ers
        }
    }
}

# parametrized factory methods

sub deserialize_message {
    my $self = shift;

    $self->message_class->deserialize(@_);
}

=item $distributed->create_message(%options)

Parametrized factory method to create a new Message object.
Uses $distributed->{message_class}.

=cut

sub create_message {
    my $self = shift;

    $self->message_class->new(@_);
}

=item $distributed->create_address(%options)

Parametrized factory method to create a new PeerAddress object.
Uses $distributed->{address_class}.

=cut

sub create_address {
    my $self = shift;

    $self->address_class->new(@_);
}

=item $distributed->create_address(%options)

Parametrized factory method to create a new PeerAddress object
from a message. Uses $distributed->{address_class}.

=cut

sub address_from_message {
    my $self = shift;

    $self->address_class->new_from_message(@_);
}

# delegation

=item $distributed->add_peer(@peers)

Adds one or more PeerAddress objects to the
address book.

=cut

sub add_peer {
    my $self = shift;

    foreach (@_) {
        if ($self->address eq $_->address) {
            cluck "Trying to add myself to my addressbook";
        }
    }
    $self->addressbook->add(@_);
}

sub peers {
    my $self = shift;

    $self->addressbook->get_all;
}

sub next_peer {
    my $self = shift;

    $self->addressbook->next;
}

sub any_peer {
    my $self = shift;

    my @peers = $self->peers;
    return $peers[0];
}

=item $distributed->send_message($message, @peers)

Sends a Message object to a list
of PeerAddresses. The message "originator"
value is automatically set to your peer's address (normally
"host:port"), unless it is already set.

=cut

sub send_message {
    my $self = shift;
    my $message = shift;
    my @peers = @_;

    $self->_debug('sending message');
    return 0 unless @peers;
    unless ($message->originator) {
        $message->originator($self->address);
    }
    $message->from($self->address);

    my $message_string = $message->serialize;


    my $all_succeeded = 1;
    foreach (@peers) {
        my $success = $self->_transport_message($message_string,$_);
        unless ($success) {
            $self->_debug("Could not send message to " . $_->address);
            $all_succeeded = 0;
        }
    }

    return $all_succeeded;
}

=item $distributed->internal_processing

A virtual method. Do something interesting here
in your subclasses.

=cut

# virtual method
sub internal_processing {
    my $self = shift;

    return;
}

=item AUTOLOADED methods

You can access all object attributes using AUTOLOADed methods.

E.g.
    $distributed->daemon;    # accesses the HTTP::Daemon object used f
+or receiving
 messages

    $distributed->email;

    $distributed->addressbook;

    $distributed->UserAgent;    # accesses the LWP::UserAgent object f
+or sending
 messages

You can also set these attributes in the same way, e.g.

    $distributed->UserAgent($my_special_user_agent);

    $distributed->debug(1);    # turn on debugging warnings

    $distributed->details(\%some_details);    # creates details hash w
+hich will be
 passed to accepted peers

In either case they return the old attribute.

=cut

sub AUTOLOAD {
    my $self = shift;

    my $attribute = $AUTOLOAD;
    $attribute =~ s/.*:://;
    my $old = $self->{$attribute};

    $self->{$attribute} = shift if @_;
    return $old;
}

sub DESTROY {
    # do not autoload me
}


=item $distributed->_accept_messages

This private method returns messages, one at a time, to the "run"
method. You can subclass it to provide an alternative transport.
The method should return a string which can then be parsed into a
Net::Distributed::Message object.

Actually, the method can return two values. The optional second value
is a PeerAddress object representing the
address of the sender. This is for use if you can reliably
determine the sender's address from the transport mechanism.
(The default method can get the IP address of the peer but not the lis
+tening
 port,
so it does not return a second value.) If you don't return a second
value, the sender address will be determined by parsing the message.


=cut

sub _accept_messages {# how to do timeouts n queues?
    my $self = shift;


    unless ($self->{daemon}) {
        my ($localaddr, $localport) = split /:/, $self->address;
        $self->{daemon} = new HTTP::Daemon (
            LocalAddr => $localaddr,
            LocalPort => $localport
        );
        ref $self->{daemon} or die "Could not create HTTP::Daemon $! $
+@";
        $self->{daemon}->timeout($self->timeout) if $self->timeout;
    }

    CONNECTION: while (1) {
        my $conn = $self->{daemon}->accept;    # blocks
        return undef unless $conn;
        my $req = $conn->get_request;
        if ($req->method ne 'POST') {

            my $r = new HTTP::Response (405,'POST method only' );

            $conn->send_response($r);
            $conn->close;
            next CONNECTION;
        }
        elsif (my $content = $req->content) {

            my $r = new HTTP::Response(200, 'OK');
            $conn->send_response($r);

            $conn->close;

            return $content;
        }
    }
}

=item _transport_message

This private method transports a message string
to a server. Subclass it to provide an alternative
transport:

    sub _transport_message {
    my $self = shift;
    my ($message_string, $peer) = shift;
    ...[send message]
    }

=cut

sub _transport_message { # subclassability!
    my $self = shift;
    my $message_string = shift;
    my $peer = shift;

    $peer or confess "no peer";
    my $headers = new HTTP::Headers;
    $headers->date(time);
    $headers->content_type('text/plain');
    $headers->server('Net::Distributed');

    my $request = new HTTP::Request (
        'POST',
        "http://" . $peer->address,
        $headers,
        $message_string
    );


    unless ($self->{UserAgent}) {
        $self->{UserAgent} = new LWP::UserAgent;
        $self->{UserAgent}->agent ("Net::Distributed/$VERSION");
        $self->{UserAgent}->from ($self->email);
        $self->{UserAgent}->parse_head(0);
        $self->{UserAgent}->timeout(120);
    }
    my $ua = $self->{UserAgent};

    my $response = $ua->request($request);

    if ($response->is_success) {
        $self->_debug("Sent message to " . $peer->address);
        return 1; # of course, there may be problems further down the 
+line
    } else {
        $self->_debug("Got " . $response->status_line . " from " . $pe
+er->address);
        return 0;
    }

}

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright 2001, David Hugh-Jones. All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

Bugs, comments and wishlist items to davidhj@mail.com

=head1 SEE ALSO

L<Net::Distributed::Message>

L<Net::Distributed::AddressBook>

L<Net::Distributed::PeerAddress>

L<Net::Distributed::Handler>

L<HTTP::Daemon>

=cut

1;


package Net::Distributed::Mixin;

use Carp qw/cluck/;
use Exporter;

use strict;
use vars qw/@ISA @EXPORT_OK/;

@ISA = qw/Exporter/;

@EXPORT_OK = qw/configure _debug/;

=head1 NAME

Net::Distributed::Mixin

=head1 SYNOPSIS

    use Net::Distributed::Mixin qw/configure _debug/;

=head1 DESCRIPTION

Provides generic, exportable functions for Net::Distributed
objects.

=cut

=head
sub _handle {
    my $self = shift;
    my $type = shift;

    if (ref $self->{$type} eq 'CODE') {
        &{ $self->{$type} }($self, @_);
    } else {
        my $method = $self->{$type};
        my $handler = $self->handler || $self;
        $handler->$method(@_);
    }
}
=cut

sub configure {
    my $self = shift;
    my %config = @_;
    map {
        $self->{$_} = $config{$_};
    } keys %config;

    return $self;
}


sub _debug {
    my $self = shift;

    $self->{debug} && print STDERR "*** $_[0] (from $self)\n";
    $self->{debug} > 1 && cluck '$_';
}

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

=cut

1;

package Net::Distributed::Message;

=head1 NAME

Net::Distributed::Message

=head1 SYNOPSIS

    package MyMessage;

    use strict;
    use vars qw/@ISA/;
    @ISA = qw/Net::Distributed::Message/;

    sub deserialize {
        my $class = shift;
        my $message_string = shift;

        .. deserialize a hashref

        bless $message, $class;
    }

    sub serialize {
        my $self = shift;

        ... serialize it

        return $string;
    }
=head1 DESCRIPTION

A simple class to send messages containing Perl data.
By default, it uses Storable for serialization: you can
subclass it to provide other methods.

=head1 METHODS

=over 4

=cut

use Storable qw/freeze thaw/;
use Carp qw/cluck/;

use strict;
use vars qw/$AUTOLOAD $VERSION/;

$VERSION = 0.1;

=item $message->type

Get/set the type of message? Message type is used by
Net::Distributed::Handler objects to decide whether to handle the
message.

=item $message->originator

Get/set the I<original> message sender's address.

=item $message->from

Get/set the address of the peer from which the message was
received (normally as "host:port"). Note that as messages may be
forwarded, this will probably I<not> be the same as originator

=item $message->body

Get/set the message body.
This is for client applications to use.
It can contain any serializable perl data structure.

=item new Net::Distributed::Message(%options)

Creates a new message. Options can include type, body and
originator. (Not "from" - that is automatically set by
the Net::Distributed object when it sends the message.

=cut

sub new {
    my $class = shift;
    my %options = @_;
    my $self = {};

    $self->{type} = $options{type};
    $self->{body} = $options{body};
    $self->{originator} = $options{originator};

    bless $self, $class;
}

=item Net::Distributed::Message->deserialize($frozenstring);

Uses Storable's "thaw" method to return a new message object. Returns
false on failure.

=cut

sub deserialize {
    my $class = shift;
    my $string = shift;

    my $self;
    eval {$self = thaw $string;};
    if ($@) {
        cluck "Could not deserialize string:\n$string";
        return 0;die;
    }
    unless (ref $self eq $class) {
        warn "Deserialized object from string:\n".
        "$string\n".
        "was of type " . ref $self .
        ", not of type $class";
        return 0;
    }
    unless ($self->{type}) {
        warn "Deserialized message:\n$string\nhad no type attribute";
        return 0;
    }
    unless ($self->{originator}) {
        warn "Deserialized message:\n$string\nhad no originator attrib
+ute";
        return 0;
    }

    return $self; # already blessed by the thaw mechanism!
}


=item $message->serialize;

Returns a serialization of the message using Storable's "freeze".

=cut

sub serialize {
    my $self = shift;

    my $string = freeze $self;

    return $string;
}

sub AUTOLOAD { # get or set instance variables
    my $self = shift;
    my $attribute = $AUTOLOAD;
    $attribute =~ s/.*:://;
    my $old = $self->{$attribute};

    $self->{$attribute} = shift if @_;
    return $old;
}

sub DESTROY {
    # do not autoload me
}

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

L<Net::Distributed::Handler>

=cut

1;

package Net::Distributed::PeerAddress;

=head1 NAME

Net::Distributed::PeerAddress

=head1 SYNOPSIS

    package MyAddress;

    use strict;
    use vars qw/@ISA/;

    @ISA = qw/Net::Distributed::PeerAddress/;

    ... new methods

=head1 DESCRIPTION

Class to store details of a Net::Distributed object's
peers on a given network.

=head1 METHODS

=over 4

=cut

use strict;
use vars qw/$VERSION $AUTOLOAD/;

$VERSION = 0.1;

=item Net::Distributed::PeerAddress->new(address => $address,[ $more_o
+ptions ]);

Creates a new PeerAddress object. You must specify the address.
Normally this will be in "host:port" format, but you can use other
formats if you have subclassed Net::Distributed to use a different
transport than HTTP.

=cut

sub new {
    my $class = shift;

    my %details = @_;

    my $self = {};
    while (my ($k,$v) =each %details) {
        $self->{$k} = $v;
    }

    unless ($self->{address}) {
        warn "Address details must include address => 'Address'";
        return undef;
    }

    bless $self, $class;
}

=item Net::Distributed::PeerAddress->new_from_message($message)

Attempts to create a new PeerAddress object using details embedded in
a Net::Distributed::Message. The message body may contain a "details"
key, which references a hash of address details; and either the
message "originator" must be set, or the details key must contain an
"address", element. (If both are set, the value in "details" will be
used instead of the "originator"). Returns undef on failure.

=cut

sub new_from_message {
    my $class = shift;
    my $message = shift;

    my $details;
    if ( $message->body) {
        $details = $message->body->{details};
    }
    unless ($details->{address} ||= $message->originator) {
        warn "Could not find message originator in message";
        return undef;
    }

    my $self = $class->new(
        %$details
    );

    return $self;
}

=item $peer->alter_details(%new_details)

Alters the specified details for the peer.

=cut

sub alter_details {
    my $self = shift;
    my %details = @_;

    if ( defined $details{address} and not $details{address} ) {
        warn "Address details must include a proper address";
        return undef;
    }

    while (my ($k,$v) =each %details) {
        $self->{$k} = $v;
    }

    return $self;
}


=item AUTOLOAD methods

You can access address attributes using AUTOLOADed methods.
E.g. $peer->address, $peer->details
You can    also set these attributes in the same way,
e.g. $peer->address('111.222.333.444:1234').
In either case they return the old attribute.
The only attribute that is guaranteed to be returned is "address", alt
+hough
addresses in a particular addressbook may conform to more stringent ru
+les.

=cut

sub AUTOLOAD { # get or set instance variables
    my $self = shift;

    my $attribute = $AUTOLOAD;
    $attribute =~ s/.*:://;
    my $old = $self->{$attribute};

    $self->{$attribute} = shift if @_;
    return $old;
}

sub DESTROY {
    # do not autoload me
}

=back

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

L<Net::Distributed::AddressBook>

=cut


1;


package Net::Distributed::Handler;

=head1 NAME

Net::Distributed::Handler

=head1 SYNOPSIS

    package Net::Distributed::Handler::MyHandler;

    use Net::Distributed::Handler;

    use strict;
    use vars qw/@ISA @EXPORT_OK %EXPORT_TAGS %TYPES/;

    @ISA = qw/Net::Distributed::Handler/;

    @EXPORT_OK = qw/service_method/;

    %EXPORT_TAGS = (
        default => \@EXPORT_OK
    );

    %TYPES = (
        message_type => 'handler_method',
    );

    sub handler_method {
        my $self = shift;
        my ($d, $message) = @_; # $d is a Net::Distributed object
        ...
        return 1;    # to pass the message to other handlers
                    # return false to prevent other handlers
                    # viewing the message
    }

    sub service_method {
        my $self = shift; # a Net::Distributed object
        ... send a message of the appropriate type

    }

=head1 DESCRIPTION

Net::Distributed::Handler is an abstract class for providing
message handling services to Net::Distributed objects. Messages
to handle are defined in the class variable %TYPES, where keys are
message types and values are names of class methods.

Service methods will be provided to the Net::Distributed object
if they are listed by the ":default" export tag. You still have
to "use" the class, though.

=head1 METHODS

=over 4

=cut

use Net::Distributed::Mixin qw/configure/;
use Exporter;

use strict;
use vars qw/%TYPES @EXPORT_OK %EXPORT_TAGS @ISA/;

@ISA = qw/Exporter/;
@EXPORT_OK = qw//;
%EXPORT_TAGS = (
    default => []
);

%TYPES = ();

=item $handler->new(%options)

Initializes a handler with %options as attributes, and
sets the types of messages it handles using the class
variable %TYPES

=cut

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;
    $self->configure(@_);
    no strict 'refs';
    $self->{types} = \%{"${class}::TYPES"};

    return $self;
}

=item $handler->can_handle($arg)

Returns a method name if $handler can handle $arg ($arg can
be a type name, or a Net::Distributed::Message object). Returns
undef otherwise.

=cut

sub can_handle {
    my $self = shift;
    my $arg = shift;

    my $type = ref $arg ? $arg->type : $arg;

    return $self->{types}->{$type}; # a method name or undef
}

=item $handler->handle($distributed, $message)

Handles a message for the Net::Distributed object $distributed
This is normally called from $distributed->dispatch.

=cut

sub handle {
    my $self = shift;
    my ($d,$message) = @_;

    my $method = $self->{types}->{$message->type};
    no strict 'subs';
    $self->$method($d,$message);
}

=back

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

=cut

1;


package Net::Distributed::Handler::Joiner;

=head1 NAME

Net::Distributed::Handler::Joiner

=head1 SYNOPSIS

    use Net::Distributed::Handler::Joiner;
    my $server = new Net::Distributed (
            address => '127.0.0.1:4877',
            services => [qw/:Joiner/],
            ...
                );

=head1 DESCRIPTION

A Handler object which provides basic peer acceptance
functions.

=head1 METHODS

=over 4

=cut

use Net::Distributed::Handler;

use strict;
use vars qw/@ISA %TYPES @EXPORT_OK %EXPORT_TAGS/;

@EXPORT_OK = qw/send_join_request accept_peer/;
%EXPORT_TAGS = (
    default => \@EXPORT_OK
);
@ISA = qw/Net::Distributed::Handler/;

%TYPES = (
    join_request    => 'handle_join_request',
    join_accept        => 'handle_join_accept',
);


sub new {
    my $class = shift;

    my $self = $class->SUPER::new(@_);
    $self->{policy} ||= 'accept';

    return bless $self, $class;
}

=item $joiner->policy([$policy])

Set's the acceptance policy to 'accept' (accept
all join requests), 'reject', 'forward' (forwards
join requests to a single random peer) or 'broadcast'
(forwards the request to all known peers). The
default policy is 'accept'.

=cut

sub policy {
    my $self = shift;

    my $oldpol = $self->{policy};
    if (@_) {
        $self->{policy} = shift;
    }

    return $oldpol;
}

=back

=head1 HANDLER METHODS

=over 4

=item $joiner->handle_join_request

Handles messages of type 'join_request' in accordance with the
policy specified.

=cut


sub handle_join_request {
    my $self = shift;
    my ($d,$message) = @_;

    my $policy = $self->{policy};
    if ($policy eq 'accept') {
        my $peer = $d->address_from_message($message);
        $d->accept_peer($peer) if $peer;
    }
    elsif ($policy eq 'reject') {
        return;
    }
    elsif ($policy eq 'forward') {
        my $peer = $d->any_peer;
        $d->send_message($message, $peer) if $peer;
    }
    elsif ($policy eq 'broadcast') {
        my @peers = $d->peers;
        foreach (@peers) {
            $d->send_message($message, $_);
        }
    }
    else {
        die "Policy $policy not recognized";
    }
}

=item $joiner->handle_join_accept

Adds the accepting peer to the Distributed object's addressbook

=back

=cut

sub handle_join_accept {
    my $self = shift;
    my ($d,$message) = @_;

    my $peer = $d->address_from_message($message);
    $d->add_peer($peer) if $peer;
}


=head1 SERVICE METHODS

These methods are exported for use by the Net::Distributed object.

=over 4

=item $distributed->send_join_request(
    address => $remote,
    body => $body
);

Sends a new join_request message to the remote address specified (norm
+ally
in the form "host:port", but you could use an alternative form
if you have subclassed Net::Distributed). You may not get a reply back
 immediately,
or from the same peer whom you asked, and if you get rejected, you won
+'t hear
 back at all!

Note that you don't specify a PeerAddress for this method. If you have
+ the
 PeerAddress in your addressbook,
they've accepted you already!

=cut

sub send_join_request {
    my $self = shift;
    my %options = @_;


    my $message = $self->create_message (
            type    => 'join_request',
            body    => $options{body},
                );

    my $peer = $self->create_address (
            address => $options{address}
                );
    $self->_debug('in send_join_request');
    $self->send_message($message,$peer);
}

=item accept_peer

$distributed->accept_peer($peer);

Sends a message to the PeerAddress object specified by $peer, acceptin
+g them.
 The message includes
address details so that the peer can add you to their addressbook. Als
+o adds
the peer to your own addressbook.

=cut

sub accept_peer {
    my $self = shift;
    my $peer = shift;

    $self->add_peer($peer);
    my $message = $self->create_message(
            type => 'join_accept',
            body => {
                details => $self->details
            },
                );

    $self->send_message($message,$peer);
}

=back

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

L<Net::Distributed::Handler>

=cut
1;


package Net::Distributed::Handler::Searcher;

=head1 NAME

Net::Distributed::Handler::Searcher

=head1 SYNOPSIS


    my $server = new Net::Distributed (
            address => '127.0.0.1:4877',
            services => [qw/:Joiner :Searcher/],
            ...
                );
    $server->send_search($pattern, $peer);

=head1 DESCRIPTION

A simple example handler to provide searching facilities.
This is just a demo, rather than a serious abstract class
for searching (at the moment).

=cut

use Net::Distributed::Handler;
use Data::Dumper;

use strict;
use vars qw/@ISA @EXPORT_OK %EXPORT_TAGS %TYPES/;

@ISA = qw/Net::Distributed::Handler/;

@EXPORT_OK = qw/broadcast_search send_search return_match/;

%EXPORT_TAGS = (
    default => \@EXPORT_OK
);

%TYPES = (
    search => 'handle_search',
    result => 'handle_result',
);

=head1 HANDLER METHODS

=over 4

=item $searcher->handle_search

Looks for a filename in the current directory
matching the pattern in the search request.
If a match is found, informs the requesting peer.
If none is found, forwards the search to a
random peer. (Uses a simple TTL mechanism
to prevent infinite forwarding.)

=cut

sub handle_search {
    my $self = shift;
    my ($d, $message) = @_;

    return unless $message->body->{ttl} > 0;
    my $pattern = $message->body->{pattern};
    my $found;
    opendir D, ".";
    foreach (readdir D) {
        if (/$pattern/) {
            $found = 1;
            $d->_debug("Found matching file $_");
            $self->return_match($d, $_, $d->address_from_message($mess
+age));
        }
    }
    closedir D;

    unless ($found) {
        my $peer = $d->any_peer;
        $message->body->{ttl}--;
        $d->send_message($message, $peer) if $peer;
    }
}

=item $searcher->handle_result

Whoop-de-do, we have a result, and now we can
tell the world about it!

=back

=cut

sub handle_result {
    my $self = shift;
    my ($d, $message) = @_;

    my $file = $message->body->{file};

    $d->_debug("Peer has file $file");
}

=head1 SERVICE METHODS

These methods are exported for use by the Net::Distributed object.

=over 4

=item $distributed->broadcast_search($pattern)

Sends a search for a file matching $pattern to all
peers.

=cut

sub broadcast_search {
    my $self = shift;
    my $pattern = shift;

    foreach ($self->peers) {
        $self->send_search($pattern, $_);
    }
}

=item $distributed->send_search($pattern,$peer)

Sends a search for filename matching $pattern
to $peer.

=cut

sub send_search {
    my $self = shift;
    my ($pattern, $peer) = @_;

    my $message = $self->create_message(
            body => {pattern => $pattern, ttl => 2},
            type => 'search',
                );

    $self->send_message($message, $peer);
}


sub return_match {
    my $self = shift;
    my ($d, $file, $peer) = @_;

    my $message = $d->create_message(
            type => 'result',
            body => {file => $file}
                );

    $d->send_message($message, $peer);
}

=back

=head1 AUTHOR

David Hugh-Jones (hughjonesd@yahoo.co.uk)

Copyright (c) 2002-2003 David Hugh-Jones. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

=head1 SEE ALSO

L<Net::Distributed>

L<Net::Distributed::Handler>

=cut

1;
Replies are listed 'Best First'.
Re: Net::Distributed
by Flame (Deacon) on Feb 02, 2002 at 02:56 UTC
    Well, I've only taken a brief look so far, but might I suggest using podchecker? There are several problems in your POD, for example: =head isn't a valid statement... It's a minor problem, but it makes things easier if it parses the pod properly...

    Other than that, it looks interesting at least.

    *Returns to picking it apart*



    "Weird things happen, get used to it."

    Flame ~ Lead Programmer: GMS

      Interesting, yes... I'll also dig a bit deeper.

      Meanwhile, in that particular place, it looks like you want something like =pod comment which will comment out that particular block. That was the intention, right? Actually, you don't need to use the word comment, but I've found it helps understanding the code to do such. Any =pod ... =cut block should be ignored by the pod2whatever you are using aswell as perl itself. :)
Re: Net::Distributed
by dash2 (Hermit) on Feb 04, 2002 at 00:36 UTC
    A few things I am still thinking about.

    TTL services

    I want to provide TTL services (i.e. give a message a certain time in hops to live). I can see three ways:

    • Build it in to Net::Distributed. Makes life easier, but adds weight to the core, which may not be necessary for most people.
    • Subclass Net::Distributed and change send_message to reduce ttl by one, and not send if it is 0. Simple, but has the problem of "branching subclasses". If I want to subclass Net::Distributed, and the subclass may also want to use TTL (or not), then I will have to write two subclasses with alternative parents.
    • Provide a handler which reduces TTL by one and returns true (allowing other handlers to handle the message) unless TTL is 0.Elegant, but means that messages get sent for one more hop than is necessary, because they are ignored at the final hop.
    Any ideas would be welcome.

    Handler exports

    Handlers export methods by default. This is a bit naughty, but quite helpful. However, what if I want to override an existing method (e.g. a handler which wants to use internal_processing to do something)? Should the main internal_processing method automatically call hooks to the handlers? Or...?

    Watch this space for Net::Distributed::Space... although I am a bit discouraged by the response so far. I get more replies for my dumb questions!

    dave hj~

Re: Net::Distributed
by gellyfish (Monsignor) on Feb 09, 2002 at 10:51 UTC

      I know. There are gonna be other security issues too... I have a Migrator handler which lets nodes migrate, so if you use one of those, you have to make double sure your own code is secure, because someone else might be using it. Perhaps it's fortunate that coderefs can't be serialized ;-)

      dave hj~