Atompub
view release on metacpan or search on metacpan
lib/Atompub/Client.pm view on Meta::CPAN
return $client->error('Response is not Atom Entry')
unless UNIVERSAL::isa($client->rc, 'XML::Atom::Entry');
$client->rc;
}
sub getMedia {
my($client, $uri) = @_;
return $client->error('No URI') unless $uri;
$client->_get_resource({ uri => $uri }) or return;
return $client->error('Response is not Media Resource')
if UNIVERSAL::isa($client->rc, 'XML::Atom::Entry');
wantarray ? ($client->rc, $client->res->content_type) : $client->rc;
}
sub updateEntry {
my($client, $uri, $entry) = @_;
return $client->error('No URI') unless $uri;
return $client->error('No Entry') unless $entry;
unless (UNIVERSAL::isa( $entry, 'XML::Atom::Entry')) {
$entry = XML::Atom::Entry->new($entry)
or return $client->error(XML::Atom::Entry->errstr);
}
my $headers = HTTP::Headers->new;
$headers->content_type(media_type('entry'));
$client->_update_resource({
uri => $uri,
rc => $entry,
headers => $headers,
});
}
sub updateMedia {
my($client, $uri, $stream, $content_type) = @_;
return $client->error('No URI') unless $uri;
return $client->error('No stream') unless $stream;
return $client->error('No Content-Type') unless $content_type;
my $media = ref $stream ? $$stream : read_file($stream, binmode => ':raw')
or return $client->error('No media resource');
my $headers = HTTP::Headers->new;
$headers->content_type($content_type);
$client->_update_resource({
uri => $uri,
rc => \$media,
headers => $headers,
});
}
sub deleteEntry {
my($client, $uri) = @_;
return $client->error('No URI') unless $uri;
$client->_delete_resource({ uri => $uri });
}
*deleteMedia = \&deleteEntry;
sub _get_service {
my($client, $args) = @_;
my $uri = $args->{uri};
$client->_clear;
return $client->error('No URI') unless $uri;
$client->req(HTTP::Request->new(GET => $uri));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
warn 'Bad Content-Type: '.$client->res->content_type
unless media_type($client->res->content_type)->is_a('service');
$client->rc(XML::Atom::Service->new(\$client->res->content))
or return $client->error(XML::Atom::Service->errstr);
for my $work ($client->rc->workspaces) {
$client->info->put($_->href, $_) for $work->collections;
}
$client;
}
sub _get_categories {
my($client, $args) = @_;
my $uri = $args->{uri};
$client->_clear;
return $client->error('No URI') unless $uri;
$client->req(HTTP::Request->new(GET => $uri));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
warn 'Bad Content-Type: '.$client->res->content_type
unless media_type($client->res->content_type)->is_a('categories');
$client->rc(XML::Atom::Categories->new(\$client->res->content))
or return $client->error(XML::Atom::Categories->errstr);
$client;
}
sub _get_feed {
my($client, $args) = @_;
my $uri = $args->{uri};
$client->_clear;
return $client->error('No URI') unless $uri;
$client->req(HTTP::Request->new(GET => $uri));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
warn 'Bad Content-Type: '.$client->res->content_type
unless media_type($client->res->content_type)->is_a('feed');
$client->rc(XML::Atom::Feed->new(\$client->res->content))
or return $client->error(XML::Atom::Feed->errstr);
$client;
}
sub _create_resource {
my($client, $args) = @_;
my $uri = $args->{uri};
my $rc = $args->{resource} || $args->{rc};
my $headers = $args->{headers};
$client->_clear;
return $client->error('No URI') unless $uri;
return $client->error('No resource') unless $rc;
return $client->error('No headers') unless $headers;
my $content_type = $headers->content_type;
my $info = $client->info->get($uri);
return $client->error("Unsupported media type: $content_type")
unless is_acceptable_media_type($info, $content_type);
my $content;
if (UNIVERSAL::isa($rc, 'XML::Atom::Entry')) {
my $entry = $rc;
return $client->error('Forbidden category')
unless is_allowed_category($info, $entry->category);
$content = $entry->as_xml;
XML::Atom::Client::_utf8_off($content);
$headers->content_type(media_type('entry'));
$headers->content_length(length $content);
}
elsif (UNIVERSAL::isa($rc, 'SCALAR')) {
$content = $$rc;
}
$client->req(HTTP::Request->new(POST => $uri, $headers, $content));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
warn 'Bad status code: '.$client->res->code
unless $client->res->code == RC_CREATED;
return $client->error('No Locaiton') unless $client->res->location;
# warn 'No Content-Locaiton' unless $client->res->content_location;
return $client unless $client->res->content;
warn 'Bad Content-Type: '.$client->res->content_type
unless media_type($client->res->content_type)->is_a('entry');
$client->rc(XML::Atom::Entry->new(\$client->res->content))
or return $client->error(XML::Atom::Entry->errstr);
my $last_modified = $client->res->last_modified;
my $etag = $client->res->etag;
$client->cache->put($client->res->location, {
rc => $client->rc,
last_modified => $last_modified,
etag => $etag,
});
$client;
}
sub _get_resource {
my($client, $args) = @_;
my $uri = $args->{uri};
$client->_clear;
return $client->error('No URI') unless $uri;
my $headers = HTTP::Headers->new;
my $cache = $client->cache->get($uri);
if ($cache) {
$headers->if_modified_since(datetime($cache->last_modified)->epoch)
if $cache->last_modified;
$headers->if_none_match($cache->etag) if defined $cache->etag;
}
$client->req(HTTP::Request->new(GET => $uri, $headers));
$client->res($client->make_request($client->req));
if (is_success $client->res->code) {
if (media_type($client->res->content_type)->is_a('entry')) {
$client->rc(XML::Atom::Entry->new(\$client->res->content))
or return $client->error(XML::Atom::Entry->errstr);
}
else {
$client->rc($client->res->content);
}
my $last_modified = $client->res->last_modified;
my $etag = $client->res->etag;
$client->cache->put($uri, {
rc => $client->rc,
last_modified => $last_modified,
etag => $etag,
});
}
elsif ($client->res->code == RC_NOT_MODIFIED) {
$client->rc($cache->rc);
}
else {
return $client->error(join "\n", $client->res->status_line, $client->res->content);
}
$client;
}
sub _update_resource {
my($client, $args) = @_;
my $uri = $args->{uri};
my $rc = $args->{resource} || $args->{rc};
my $headers = $args->{headers};
$client->_clear;
return $client->error('No URI') unless $uri;
return $client->error('No resource') unless $rc;
return $client->error('No headers') unless $headers;
my $content;
if (UNIVERSAL::isa($rc, 'XML::Atom::Entry')) {
my $entry = $rc;
$content = $entry->as_xml;
XML::Atom::Client::_utf8_off($content);
$headers->content_type(media_type('entry'));
$headers->content_length(length $content);
}
elsif (UNIVERSAL::isa($rc, 'SCALAR')) {
$content = $$rc;
}
if (my $cache = $client->cache->get($uri)) {
$headers->if_unmodified_since(datetime($cache->last_modified)->epoch)
if $cache->last_modified;
$headers->if_match($cache->etag) if defined $cache->etag;
}
$client->req(HTTP::Request->new(PUT => $uri, $headers, $content));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
return $client unless $client->res->content;
if (media_type($client->res->content_type)->is_a('entry')) {
$client->rc(XML::Atom::Entry->new(\$client->res->content))
or return $client->error(XML::Atom::Entry->errstr);
}
else {
$client->rc($client->res->content);
}
my $last_modified = $client->res->last_modified;
my $etag = $client->res->etag;
$client->cache->put($uri, {
rc => $client->rc,
last_modified => $last_modified,
etag => $etag,
});
$client;
}
sub _delete_resource {
my($client, $args) = @_;
my $uri = $args->{uri};
$client->_clear;
return $client->error('No URI') unless $uri;
my $headers = HTTP::Headers->new;
# If-Match nor If-Unmodified-Since header is not required on DELETE
# if (my $cache = $client->cache->get($uri)) {
# $headers->if_unmodified_since(datetime($cache->last_modified)->epoch)
# if $cache->last_modified;
# $headers->if_match($cache->etag) if defined $cache->etag;
# }
$client->req(HTTP::Request->new(DELETE => $uri, $headers));
$client->res($client->make_request($client->req));
return $client->error(join "\n", $client->res->status_line, $client->res->content)
unless is_success $client->res->code;
$client;
}
sub _clear {
my($client) = @_;
$client->error('');
$client->{$_} = undef for @ATTRS;
}
sub munge_request {
my($client, $req) = @_;
$req->accept(join(',',
media_type('entry')->without_parameters,
media_type('service'), media_type('categories'),
'*/*',
));
return unless $client->username;
my $nonce = sha1(sha1(time.{}.rand().$$));
my $now = datetime->w3cz;
my $wsse = sprintf(
qq{UsernameToken Username="%s", PasswordDigest="%s", Nonce="%s", Created="%s"},
($client->username || ''),
encode_base64(sha1($nonce.$now.($client->password || '')), ''),
encode_base64($nonce, ''),
$now,
);
$req->header('X-WSSE' => $wsse);
$req->authorization('WSSE profile="UsernameToken"');
}
# see 9.7.1 in RFC 5023
sub _escape {
my ($slug) = @_;
return uri_escape(encode_utf8($slug), "\x00-\x19\x25-\x25\x7e-\xff");
}
package Atompub::Client::Info;
my $Info;
sub instance {
my($class) = @_;
$Info ||= bless { info => {} }, $class;
$Info;
}
sub put {
my($self, $uri, @args) = @_;
return unless $uri;
if (@args) {
lib/Atompub/Client.pm view on Meta::CPAN
=head1 NAME
Atompub::Client - A client for the Atom Publishing Protocol
=head1 SYNOPSIS
use Atompub::Client;
my $client = Atompub::Client->new;
$client->username('Melody');
$client->password('Nelson');
#$client->proxy( $proxy_uri );
# Get a Service Document
my $service = $client->getService($service_uri);
my @workspaces = $service->workspaces;
my @collections = $workspaces[0]->collections;
# CRUD an Entry Resource; assuming that the 0-th collection supports
# Entry Resources
my $collection_uri = $collections[0]->href;
my $name = 'New Post';
my $entry = XML::Atom::Entry->new;
$entry->title($name);
$entry->content('Content of my post.');
my $edit_uri = $client->createEntry($collection_uri, $entry, $name);
my $feed = $client->getFeed($collection_uri);
my @entries = $feed->entries;
$entry = $client->getEntry($edit_uri);
$client->updateEntry($edit_uri, $entry);
$client->deleteEntry($edit_uri);
# CRUD a Media Resource; assuming that the 1-st collection supports
# Media Resources
my $collection_uri = $collections[1]->href;
my $name = 'My Photo';
my $edit_uri = $client->createMedia($collection_uri, 'sample1.png',
'image/png', $name);
# Get a href attribute of an "edit-media" link
my $edit_media_uri = $client->resource->edit_media_link;
my $binary = $client->getMedia($edit_media_uri);
$client->updateMedia($edit_media_uri, 'sample2.png', 'image/png');
$client->deleteEntry($edit_media_uri);
# Access to the requested HTTP::Request object
my $request = $client->request;
# Access to the received HTTP::Response object
my $response = $client->response;
# Access to the received resource (XML::Atom object or binary data)
my $resource = $client->resource;
=head1 DESCRIPTION
L<Atompub::Client> implements a client for the Atom Publishing Protocol
described at L<http://www.ietf.org/rfc/rfc5023.txt>.
The client supports the following features:
=over 4
=item * Authentication
L<Atompub::Client> supports the Basic and WSSE Authentication described in
L<http://www.intertwingly.net/wiki/pie/DifferentlyAbledClients>.
=item * Service Document
L<Atompub::Client> understands Service Documents,
in which information of collections are described,
such as URIs, acceptable media types, and allowable categories.
=item * Media Resource support
Media Resources (binary data) as well as Entry Resources are supported.
You can create and edit Media Resources such as image and video
by using L<Atompub::Client>.
=item * Media type check
L<Atompub::Client> checks media types of resources
before creating and editing them to the collection.
Acceptable media types are shown in I<app:accept> elements in the Service Document.
=item * Category check
L<Atompub::Client> checks categories in Entry Resources
before creating and editing them to the collection.
Allowable categories are shown in I<app:categories> elements in the Service Document.
=item * Cache controll and versioning
On-memory cache and versioning, which are controlled by I<ETag> and I<Last-Modified> header,
are implemented in L<Atompub::Client>.
=item * Naming resources by I<Slug> header
The client can specify I<Slug> header when creating a resource,
which may be used as part of the resource URI.
=back
lib/Atompub/Client.pm view on Meta::CPAN
Updates the Media Resource at URI $edit_uri with the $media.
If $media is a reference to a scalar, it is treated as the binary.
If a scalar, treated as a file containing the Media Resource.
$media_type is the media type of the Media Resource, such as 'image/png'.
Returns true on success, false otherwise.
=head2 $client->deleteEntry($edit_uri)
Deletes the Entry Document at URI $edit_uri.
Returns true on success, false otherwise.
=head2 $client->deleteMedia($edit_uri)
Deletes the Media Resource at URI $edit_uri and related Media Link Entry.
Returns true on success, false otherwise.
=head1 Accessors
=head2 $client->username([ $username ])
If called with an argument, sets the username for login to $username.
Returns the current username that will be used when logging in to the
Atompub server.
=head2 $client->password([ $password ])
If called with an argument, sets the password for login to $password.
Returns the current password that will be used when logging in to the
Atompub server.
=head2 $client->proxy([ $proxy_uri ])
If called with an argument, sets URI of proxy server like 'http://proxy.example.com:8080'.
Returns the current URI of the proxy server.
=head2 $client->resource
=head2 $client->rc
An accessor for Entry or Media Resource, which was retrieved in the previous action.
=head2 $client->request
=head2 $client->req
An accessor for an L<HTTP::Request> object, which was used in the previous action.
=head2 $client->response
=head2 $client->res
An accessor for an L<HTTP::Response> object, which was used in the previous action.
=head1 INTERNAL INTERFACES
=head2 $client->init
=head2 $client->ua
Accessor to the UserAgent.
=head2 $client->info
An accessor to information of Collections described in a Service Document.
=head2 $client->cache
An accessor to the resource cache.
=head2 $client->munge_request($req)
=head2 $client->_clear
=head2 $client->_get_service(\%args)
=head2 $client->_get_categories(\%args)
=head2 $client->_get_feed(\%args)
=head2 $client->_create_resource(\%args)
=head2 $client->_get_resource(\%args)
=head2 $client->_update_resource(\%args)
=head2 $client->_delete_resource(\%args)
=head1 ERROR HANDLING
Methods return C<undef> on error, and the error message can be retrieved
using the I<errstr> method.
=head1 SEE ALSO
L<XML::Atom>
L<XML::Atom::Service>
L<Atompub>
=head1 AUTHOR
Takeru INOUE, E<lt>takeru.inoue _ gmail.comE<gt>
( run in 0.818 second using v1.01-cache-2.11-cpan-39bf76dae61 )