#!/usr/bin/perl # vim:ts=4 sw=4 et: package Apache::Blob; use strict; use File::Path; use Fcntl ':flock'; use Apache::Constants qw(:common HTTP_BAD_REQUEST HTTP_NO_CONTENT M_GET M_PUT M_DELETE); use lib "$ENV{'BLOBHOME'}"; my $ROOT = "$ENV{'BLOBHOME'}/root"; sub handler { my $r = shift; $r->set_handlers(PerlTransHandler => [ \&trans ]); return OK; } sub trans { my $r = shift; my $uri = $r->uri; my $path = $ROOT . $uri; if ($r->method_number == M_GET) { # get requests just go through to the file system. $r->handler("perl-script"); $r->push_handlers(PerlHandler => sub { my $r = shift; # let apache handle it. $r->filename($path); return DECLINED; }); return OK; } elsif ($r->method_number == M_PUT || $r->method_number == M_DELETE) { # /cluster/u1/u2/u3/type/m1/m2 # 1 2 3 4 5 6 7 return HTTP_BAD_REQUEST unless $uri =~ m#^/\d+/\d+/\d+/\d+/\w+/\d+/\d+\.\w+$#; $r->handler("perl-script"); $r->push_handlers(PerlHandler => sub { my $r = shift; return delete_blob($r) if $r->method_number == M_DELETE; return HTTP_NO_CONTENT if $r->method_number == M_PUT && save_blob($r, $path); return SERVER_ERROR; }); return OK; } return HTTP_BAD_REQUEST; } # directory listing # sub dir_trans # { # my ($r, $uri) = @_; # if ($uri =~ m#^/(\d+)/(\d+)/(\w+)/?$#) { # my ($cid, $uid) = ($1, $2, $3); # $r->handler("perl-script"); # $r->notes(dir => make_path($cid, $uid)); # $r->push_handlers(PerlHandler => \&dirlisting); # return OK; # } # if ($uri =~ m#^/(\d+)/(\d+)/?$#) { # my ($cid, $uid) = ($1, $2); # $r->handler("perl-script"); # $r->notes(dir => make_path($cid, $uid)); # $r->push_handlers(PerlHandler => \&dirlisting); # return OK; # } # return 400; # } # sub dirlisting # { # my $r = shift; # return 404 unless (opendir(DIR, $r->notes('dir'))); # $r->content_type("text/plain"); # $r->send_http_header(); # foreach my $f (readdir(DIR)) { # next if $f eq '.' or $f eq '..'; # $r->print("$f\n"); # } # closedir(DIR); # return OK; # } # blob access # sub blob_trans # { # my ($r, $uri, $cid, $uid, $mid) = @_; # my $path = make_path($cid, $uid, $mid); # # if ($r->method_number == M_PUT) { # } else { # return 404 unless -r $path; # $r->handler("perl-script"); # $r->push_handlers(PerlHandler => sub { # my $r = shift; # # # these content-types aren't exactly correct. # if ($blobtype eq 'audio') { # $r->content_type("audio/mp3"); # } else { # $r->content_type("application/octet-stream"); # } # $r->send_http_header(); # # # let apache handle sending the file. # $r->filename($path); # return DECLINED; # }); # } # } sub make_dirs { my $filename = shift; my $dir = File::Basename::dirname($filename); eval { File::Path::mkpath($dir, 0, 0775); }; return $@ ? 0 : 1; } sub save_blob { my ($r, $path) = @_; my $length = $r->header_in("Content-Length"); make_dirs($path); open(FILE, ">$path.tmp") or die "couldn't make $path"; binmode(FILE); flock(FILE, LOCK_EX) or die "couldn't lock"; my ($buff, $lastsize); my $got = 0; my $nextread = 4096; $r->soft_timeout("save_blob"); # ? while ($got <= $length && ($lastsize = $r->read_client_block($buff, $nextread))) { $r->reset_timeout; $got += $lastsize; print FILE $buff; if ($length - $got < 4096) { $nextread = $length - $got; } } $r->kill_timeout; flock(FILE, LOCK_UN) or die "couldn't unlock"; close(FILE) or die "couldn't close"; if ($got != $length) { unlink("$path.tmp"); return 0; } if (-s "$path.tmp" == $length) { return 1 if rename("$path.tmp", $path); } unlink("$path.tmp"); return 0; } sub delete_blob { my $r = shift; my $uri = $r->uri; my $path = $ROOT . $uri; return NOT_FOUND unless -e $path; unlink($path) or return SERVER_ERROR; for (1..2) { next unless $uri =~ s!/[^/]+$!!; $path = $ROOT . $uri; last unless rmdir $path; } return HTTP_NO_CONTENT; }