package Net::OpenDHT; use strict; use warnings; use HTTP::Request; use List::Util qw(shuffle); use LWP::UserAgent; use MIME::Base64; use Time::HiRes qw(time); use XML::LibXML; use base 'Class::Accessor::Chained::Fast'; __PACKAGE__->mk_accessors(qw(ttl application server)); our $VERSION = '0.33'; our $VALUES = 100; my $ua = LWP::UserAgent->new(); $ua->timeout(10); $ua->agent("Net::OpenDHT $VERSION"); sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->server('opendht.nyuld.net') unless $self->server; return $self; } sub _make_request { my ( $self, $xml ) = @_; my $response; eval { $response = $self->_make_request_aux($xml); }; return $response unless $@; eval { $response = $self->_make_request_aux($xml); }; return $response unless $@; die "Error making request: $@"; } sub _make_request_aux { my ( $self, $xml ) = @_; my $server = $self->server || die "No server"; my $request = HTTP::Request->new( POST => "http://$server:5851/" ); $request->header( Content_Type => 'text/xml' ); $request->protocol('HTTP/1.0'); $request->content($xml); $request->content_length( length($xml) ); my $response = $ua->request($request); die $response->status_line unless $response->is_success; return $response; } sub fetch { my ( $self, $key ) = @_; die "Key '$key' is longer than 20 bytes" if length($key) > 20; return $self->_fetch( $key, $VALUES, undef ); } sub _fetch { my ( $self, $key, $values, $placemark ) = @_; my $xml = $self->_fetch_xml( $key, $values, $placemark ); my $response = $self->_make_request($xml); my $parser = XML::LibXML->new(); my $doc = $parser->parse_string( $response->content ); my @nodes = $doc->findnodes( "/methodResponse/params/param/value/array/data/value/array/data/*/base64" ); my @values = map { decode_base64( $_->textContent ) } @nodes; $placemark = $doc->findvalue( "/methodResponse/params/param/value/array/data/value[2]/base64"); if ($placemark) { chomp $placemark; push @values, $self->_fetch( $key, $values, $placemark ); } if (wantarray) { return @values; } else { return $values[0]; } } sub put { my ( $self, $key, $value, $ttl ) = @_; die "Key '$key' is longer than 20 bytes" if length($key) > 20; die "Value '$value' is longer than 1024 bytes" if length($value) > 1024; my $xml = $self->_put_xml( $key, $value, $ttl ); my $response = $self->_make_request($xml); my $parser = XML::LibXML->new(); my $doc = $parser->parse_string( $response->content ); my $status = $doc->findvalue("/methodResponse/params/param/value/int"); if ( $status == 0 ) { return; } elsif ( $status == 1 ) { die "Status 1 returned: over capacity"; } elsif ( $status == 2 ) { die "Status 2 returned: try again"; } else { die "Unknown status $status"; } } sub _put_xml { my ( $self, $key, $value, $ttl ) = @_; $key = encode_base64($key); chomp $key; $value = encode_base64($value); chomp $value; my $doc = XML::LibXML::Document->new( "1.0", "utf8" ); my $method_call = $doc->createElement("methodCall"); $method_call->appendTextChild( methodName => "put" ); my $params = $doc->createElement("params"); my $key_param = $doc->createElement("param"); my $key_value = $doc->createElement("value"); $key_value->appendTextChild( "base64" => $key ); $key_param->addChild($key_value); $method_call->addChild($key_param); my $value_param = $doc->createElement("param"); my $value_value = $doc->createElement("value"); $value_value->appendTextChild( "base64" => $value ); $value_param->addChild($value_value); $method_call->addChild($value_param); my $ttl_param = $doc->createElement("param"); my $ttl_value = $doc->createElement("value"); $ttl_value->appendTextChild( "int" => $ttl ); $ttl_param->addChild($ttl_value); $method_call->addChild($ttl_param); my $app_param = $doc->createElement("param"); $app_param->appendTextChild( "value" => $self->application ); $method_call->addChild($app_param); $method_call->addChild($params); $doc->setDocumentElement($method_call); return $doc->toString(1); } sub _fetch_xml { my ( $self, $key, $values, $placemark ) = @_; $key = encode_base64($key); chomp $key; $values ||= 1; $placemark ||= ""; my $doc = XML::LibXML::Document->new( "1.0", "utf8" ); my $method_call = $doc->createElement("methodCall"); $method_call->appendTextChild( methodName => "get" ); my $params = $doc->createElement("params"); my $key_param = $doc->createElement("param"); my $key_value = $doc->createElement("value"); $key_value->appendTextChild( "base64" => $key ); $key_param->addChild($key_value); $method_call->addChild($key_param); my $ttl_param = $doc->createElement("param"); my $ttl_value = $doc->createElement("value"); $ttl_value->appendTextChild( "int" => $values ); $ttl_param->addChild($ttl_value); $method_call->addChild($ttl_param); my $value_param = $doc->createElement("param"); my $value_value = $doc->createElement("value"); $value_value->appendTextChild( "base64" => $placemark ); $value_param->addChild($value_value); $method_call->addChild($value_param); my $app_param = $doc->createElement("param"); $app_param->appendTextChild( "value" => $self->application ); $method_call->addChild($app_param); $method_call->addChild($params); $doc->setDocumentElement($method_call); return $doc->toString(1); } 1; __END__ =head1 NAME Net::OpenDHT - Access the Open Distributed Hash Table (Open DHT) =head1 SYNOPSIS my $dht = Net::OpenDHT->new(); $dht->application("My Application"); $dht->server($server); # see below $dht->put($key, $value, $ttl); my $value = $dht->fetch($key); my @values = $dht->fetch($key); =head1 DESCRIPTION The Net::OpenDHT module provides a simple interface to the Open DHT service. Open DHT is a publicly accessible distributed hash table (DHT) service. In contrast to the usual DHT model, clients of Open DHT do not need to run a DHT node in order to use the service. Instead, they can issue put and get operations to any DHT node, which processes the operations on their behalf. No credentials or accounts are required to use the service, and the available storage is fairly shared across all active clients. This service model of DHT usage greatly simplifies deploying client applications. By using Open DHT as a highly-available naming and storage service, clients can ignore the complexities of deploying and maintaining a DHT and instead concentrate on developing more sophisticated distributed applications. What this essentially gives you as a Perl author is robust storage for a small amount of data. This can be used as a distributed cache or data store. Read the following for full semantics about the Open DHT: http://opendht.org/users-guide.html =head1 METHODS =head2 new The constructor: my $dht = Net::OpenDHT->new(); =head2 application The application method sets the name of the application. You should set this as a courtesy to the Open DHT developers: $dht->application("My Application"); =head2 fetch The get method fetches data from the Open DHT. Note that multiple values can be set for a key: my $value = $dht->fetch($key); my @values = $dht->fetch($key); =head2 put The put method puts data into the Open DHT. The key has a maximum length of 20 bytes, the value a maximum length of 1024 bytes. You must also pass in a time to live in seconds: $dht->put($key, $value, $ttl); =head2 server The module automatically finds a topologically-close gateway to the DHT via the CoralCDN OASIS service. You may override this and provide your own gateway with this method: $dht->server($server); =head1 AUTHOR Leon Brocard . =head1 COPYRIGHT Copyright (C) 2005-6, Leon Brocard This module is free software; you can redistribute it or modify it under the same terms as Perl itself.