Atompub

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

2007-09-12  Takeru INOUE  <takeru.inoue _ gmail.com>

	* release 0.1.1:
	* Atompub/DateTime.pm
	- rewrite POD

2007-09-10  Takeru INOUE  <takeru.inoue _ gmail.com>

	* release 0.1.0:
	* Atompub.pm
	- add some methods to HTTP::Headers, HTTP::Request, and HTTP::Response
	* Atompub/Client.pm
	- merge operations for entry and media resources internally
	- support cache mechanizem using Last-Modified header
	- refactoring drastically
	* Atompub/DateTime.pm, Atompub/MediaType.pm, Atompub/Util.pm
	- newly added

2007-08-13  Takeru INOUE  <takeru.inoue _ gmail.com>

	* release 0.0.2:

META.yml  view on Meta::CPAN

requires:
  Class::Accessor::Fast: 0
  Class::Data::Inheritable: 0
  DateTime: 0
  DateTime::Format::W3CDTF: 0
  DateTime::TimeZone: 0
  Digest::SHA: 0
  File::Slurp: 0
  HTTP::Date: 0
  HTTP::Headers: 0
  HTTP::Request: 0
  HTTP::Response: 0
  HTTP::Status: 0
  MIME::Base64: 0
  MIME::Types: 0
  Module::Build::Compat: 0
  Perl6::Export::Attrs: 0
  Test::Perl::Critic: 0
  Time::Local: 0
  URI::Escape: 0
  XML::Atom::Service: 0.016

Makefile.PL  view on Meta::CPAN


requires('Class::Accessor::Fast');
requires('Class::Data::Inheritable');
requires('DateTime');
requires('DateTime::Format::W3CDTF');
requires('DateTime::TimeZone');
requires('Digest::SHA');
requires('File::Slurp');
requires('HTTP::Date');
requires('HTTP::Headers');
requires('HTTP::Request');
requires('HTTP::Response');
requires('HTTP::Status');
requires('MIME::Base64');
requires('MIME::Types');
requires('Module::Build::Compat');
requires('Perl6::Export::Attrs');
requires('Test::Perl::Critic');
requires('Time::Local');
requires('URI::Escape');
requires('XML::Atom::Service', 0.016);

lib/Atompub.pm  view on Meta::CPAN

package Atompub;

use warnings;
use strict;

use 5.006;
use version 0.74; our $VERSION = qv('0.3.7');

use HTTP::Headers;
use HTTP::Request;
use HTTP::Response;
use XML::Atom;
use XML::Atom::Service 0.15.4;

our %REQUEST_HEADERS = (
    accept              => 'Accept',
    if_match            => 'If-Match',
    if_none_match       => 'If-None-Match',
    if_modified_since   => 'If-Modified-Since',
    if_unmodified_since => 'If-Unmodified-Since',

lib/Atompub.pm  view on Meta::CPAN


our %ENTITY_HEADERS = (
    last_modified => 'Last-Modified',
    slug          => 'Slug',
);

while (my($method, $header) = each %REQUEST_HEADERS) {
    no strict 'refs'; ## no critic
    *{"HTTP::Headers::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Headers->can($method);
    *{"HTTP::Request::$method"} = sub { shift->header($header, @_)}
        unless (HTTP::Request->can($method));
}

while (my($method, $header) = each %RESPONSE_HEADERS) {
    no strict 'refs'; ## no critic
    *{"HTTP::Headers::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Headers->can($method);
    *{"HTTP::Response::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Response->can($method);
}

while (my($method, $header) = each %ENTITY_HEADERS) {
    no strict 'refs'; ## no critic
    *{"HTTP::Headers::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Headers->can($method);
    *{"HTTP::Request::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Request->can($method);
    *{"HTTP::Response::$method"} = sub { shift->header($header, @_) }
        unless HTTP::Response->can($method);
}

1; # Magic true value required at end of module
__END__

=head1 NAME

Atompub - Atom Publishing Protocol implementation

lib/Atompub.pm  view on Meta::CPAN

XML formats used in the protocol are implemented in L<XML::Atom> and
L<XML::Atom::Service>.
Catalyst extension L<Catalyst::Controller::Atompub> is also available.

This module was tested in July2007InteropTokyo and November2007Interop,
and interoperated with other implementations.
See L<http://intertwingly.net/wiki/pie/July2007InteropTokyo> and
L<http://www.intertwingly.net/wiki/pie/November2007Interop> in detail.


=head1 METHODS of HTTP::Headers, HTTP::Request, and HTTP::Response

Some accessors for the HTTP header fields, which are used in the Atom Publishing Protocol,
are imported into L<HTTP::Headers>, L<HTTP::Request>, and L<HTTP::Response>.
See L<http://www.ietf.org/rfc/rfc2616.txt> in detail.


=head2 $headers->accept([ $value ])

An accessor for the I<Accept> header field.

This method is imported into L<HTTP::Headers> and L<HTTP::Request>.

=head2 $headers->if_match([ $value ])

An accessor for the I<If-Match> header field.

This method is imported into L<HTTP::Headers> and L<HTTP::Request>.

=head2 $headers->if_none_match([ $value ])

An accessor for the I<If-None-Match> header field.

This method is imported into L<HTTP::Headers> and L<HTTP::Request>.

=head2 $headers->if_modified_since([ $value ])

An accessor for the I<If-Modified-Since> header field.
$value MUST be UTC epoch value, like C<1167609600>.

This method is imported into L<HTTP::Headers> and L<HTTP::Request>.

=head2 $headers->if_unmodified_since([ $value ])

An accessor for the I<If-Unmodified-Since> header field.
$value MUST be UTC epoch value, like C<1167609600>.

This method is imported into L<HTTP::Headers> and L<HTTP::Request>.

=head2 $headers->content_location([ $value ])

An accessor for the I<Content-Location> header field.

This method is imported into L<HTTP::Headers> and L<HTTP::Response>.

=head2 $headers->etag([ $value ])

An accessor for the I<ETag> header field.

lib/Atompub.pm  view on Meta::CPAN

=head2 $headers->location([ $value ])

An accessor for the I<Location> header field.

This method is imported into L<HTTP::Headers> and L<HTTP::Response>.

=head2 $headers->last_modified([ $value ])

An accessor for the I<Last-Modified> header field.

This method is imported into L<HTTP::Headers>, L<HTTP::Request>, and L<HTTP::Response>.

=head2 $headers->slug([ $value ])

An accessor for the I<Slug> header field.

This method is imported into L<HTTP::Headers>, L<HTTP::Request>, and L<HTTP::Response>.


=head1 AUTHOR

Takeru INOUE, E<lt>takeru.inoue _ gmail.comE<gt>


=head1 LICENCE AND COPYRIGHT

Copyright (c) 2007, Takeru INOUE C<< <takeru.inoue _ gmail.com> >>.

lib/Atompub/Client.pm  view on Meta::CPAN

    $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;
}

lib/Atompub/Client.pm  view on Meta::CPAN

	    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;

lib/Atompub/Client.pm  view on Meta::CPAN


    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);

lib/Atompub/Client.pm  view on Meta::CPAN

    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);

lib/Atompub/Client.pm  view on Meta::CPAN


    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) = @_;

lib/Atompub/Client.pm  view on Meta::CPAN


    # 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

lib/Atompub/Client.pm  view on Meta::CPAN


=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

t/10.client.t  view on Meta::CPAN

    diag "using HTTP proxy: $proxy";
    $client->proxy( $proxy ) if $proxy;
}

# Service

ok !$client->getService('http://example.com/service'); # Not Found

like $client->errstr, qr/not found/i;

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
ok !$client->rc;

is $client->res->code, RC_NOT_FOUND;

ok $client->getService($SERVICE);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Service';

ok $client->res->is_success;

my $serv = $client->rc;
my($entry_coll, $media_coll) = $serv->workspace->collections;

isa_ok $client->info->get($entry_coll->href), 'XML::Atom::Collection';
isa_ok $client->info->get($media_coll->href), 'XML::Atom::Collection';

t/10.client.t  view on Meta::CPAN

ok !$client->rc;


$category = XML::Atom::Category->new;
$category->term('animal');
$category->scheme('http://example.com/cats/big3');
$entry->category($category);

ok $client->createEntry($entry_coll->href, $entry, 'Entry 1');

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Entry';

is $client->req->slug, 'Entry 1';
is $client->res->code, RC_CREATED;
ok my $uri = $client->res->location;

$entry = $client->rc;
is $entry->title, 'Entry 1';

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# List Entry Resources (Get Feed)

ok $client->getFeed($entry_coll->href);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Feed';

ok $client->res->is_success;


# Get Entry Resource

ok $client->getEntry($uri);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Entry';

is $client->res->code, RC_NOT_MODIFIED;

$entry = $client->rc;
is $entry->title, 'Entry 1';

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# Update Entry Resource

$entry->title('Entry 2');

ok $client->updateEntry($uri, $entry);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Entry';

ok $client->res->is_success;

$entry = $client->rc;
is $entry->title, 'Entry 2';

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# Delete Entry Resource

ok $client->deleteEntry($uri);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
ok !$client->rc;

ok $client->res->is_success;


# Create Media Resource

# Unsupported media type
ok !$client->createMedia($media_coll->href, 't/samples/media1.gif', 'text/plain', 'Media 1');

like $client->errstr, qr/unsupported media type/i;

ok !$client->req;
ok !$client->res;
ok !$client->rc;


ok $client->createMedia($media_coll->href, 't/samples/media1.gif', 'image/gif', 'Media 1');

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
isa_ok $client->rc, 'XML::Atom::Entry';

is $client->req->slug, 'Media 1';
is $client->res->code, RC_CREATED;
ok $uri = $client->res->location;

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# Get Media Resource

($uri) = map { $_->href } grep { $_->rel eq 'edit-media' } $client->rc->link;

ok $client->getMedia($uri);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
ok $client->rc;

ok $client->res->is_success;
is $client->res->content_type, 'image/gif';

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# Update Media Resource

ok $client->updateMedia($uri, 't/samples/media2.gif', 'image/gif');

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
ok $client->rc;

ok $client->res->is_success;
is $client->res->content_type, 'image/gif';

isa_ok $client->cache->get($uri), 'Atompub::Client::Cache::Resource';


# Delete Media Resource

ok $client->deleteMedia($uri);

isa_ok $client->req, 'HTTP::Request';
isa_ok $client->res, 'HTTP::Response';
ok !$client->rc;

ok $client->res->is_success;



( run in 0.541 second using v1.01-cache-2.11-cpan-de7293f3b23 )