use strict; use warnings; package Jifty::CAS::Store; use Any::Moose; =head1 NAME Jifty::CAS::Store - Abstract class for Jifty's Content-Addressed Storage =head1 DESCRIPTION This is the abstract base class for a backend store for L. For more information, see L. =cut use Jifty::CAS::Blob; =head2 publish DOMAIN NAME CONTENT METADATA Publishes the given C at the address C and C. C is an arbitrary hash; see L for more. Returns the key. =cut sub publish { my ($self, $domain, $name, $content, $opt) = @_; $opt ||= {}; my $blob = Jifty::CAS::Blob->new( { content => $content, metadata => $opt, } ); return $self->_store( $domain, $name, $blob ); } =head2 _store DOMAIN NAME BLOB Stores the BLOB (a L) in the backend. Returns the key. Subclasses should override this, but it should not be called directly -- use L instead. =cut sub _store { die "This is an abstract base class; use one of the provided subclasses instead\n"; } =head2 key DOMAIN NAME Returns the most recent key for the given pair of C and C, or undef if none such exists. Subclasses should override this. =cut sub key { die "This is an abstract base class; use one of the provided subclasses instead\n"; } =head2 retrieve DOMAIN KEY Returns a L for the given pair of C and C, or undef if none such exists. Subclasses should override this. =cut sub retrieve { die "This is an abstract base class; use one of the provided subclasses instead\n"; } =head2 uri DOMAIN NAME Returns a URL where the given C and C can be accessed. =cut sub uri { my $self = shift; my ($domain, $name) = @_; my $CDN = Jifty->config->framework('Web')->{'CDN'} || ""; return "$CDN/__jifty/cas/$domain/" . $self->key($domain, $name); } =head2 serve DOMAIN ARGUMENT ENV Serves a plack request in C, given a C and an C, which may wither be a key or a name. This correctly uses the C and C headers to send HTTP 304 responses to unchanged content. Additionally, the C key in the requested object's metadata is expected to be set and is used for the HTTP response. This method is usually only called by L, which calls this method as appropriate for requests under C. =cut sub serve { my ($self, $domain, $arg, $env) = @_; my $key; if ($arg =~ /^[a-f0-9]{32}$/) { $key = $arg; } else { $key = $self->key($domain, $arg); return $self->_serve_404( $domain, $arg, "Unable to lookup key." ) if not defined $key; } my $req = Plack::Request->new($env); my $etag = $req->header('If-None-Match'); if ( defined $etag and $etag eq qq["$key"] ) { Jifty->log->info("Returning 304 for CAS cached $domain:$key"); return Plack::Response->new(304)->finalize; } my $obj = Jifty::CAS->retrieve($domain, $key); return $self->_serve_404( $domain, $key, "Unable to retrieve blob." ) if not defined $obj; my $res = Plack::Response->new(200); my $length = length($obj->content); $res->content_type($obj->metadata->{content_type}); $res->header( 'Cache-Control' => 'max-age=31536000, public' ); $res->header( 'Expires' => HTTP::Date::time2str( time() + 31536000 ) ); $res->header( 'ETag' => '"'.$obj->key.'"' ); $res->header( 'Content-Length' => $length ); $res->header( 'Last-Modified' => HTTP::Date::time2str( $obj->metadata->{time} ) ); Jifty->log->info("Sending $domain:$key from CAS ($length bytes)"); $res->body($obj->content); return $res->finalize; } sub _serve_404 { my ($self, $domain, $name, $msg) = @_; $msg ||= ''; Jifty->log->error("Returning 404 for CAS cached $domain:$name. $msg"); return Plack::Response->new(404)->finalize; } =head2 durable Returns true if the backing store is durable -- that is, if there is a guarantee that data placed there will be accessible from all processes at all later times. =cut sub durable { 0 } no Any::Moose; __PACKAGE__->meta->make_immutable(inline_constructor => 0); 1;