App-Wallflower
view release on metacpan or search on metacpan
lib/Wallflower.pm view on Meta::CPAN
$segments[-1] = $self->index if $segments[-1] eq '';
# generate target file name
return Path::Tiny->new( $self->destination, grep length, @segments );
}
# Wallflower::NULL drops the streamed data
for (qw( write close )) {
no strict 'refs';
*{"Wallflower::NULL::$_"} = sub { };
}
sub _build_handle {
my ($file) = @_;
# get a file to save the content in
my $dir = $file->parent;
eval { $dir->mkpath; 1; } or die "$@\n"
if !-e $dir;
open my $fh, '> :raw', $file # no stinky crlf on Win32
or die "Can't open $file for writing: $!\n";
return $fh;
}
# save the URL to a file
sub get {
my ( $self, $uri ) = @_;
$uri = URI->new($uri) if !ref $uri;
# absolute paths have the empty string as their first path_segment
croak "$uri is not an absolute URI"
if $uri->path && length +( $uri->path_segments )[0];
# setup the environment
my $env = {
# current environment
%ENV,
# overridable defaults
'psgi.errors' => \*STDERR,
# current instance defaults
%{ $self->env },
('psgi.url_scheme' => $self->url->scheme )x!! $self->url->scheme,
# request-related environment variables
REQUEST_METHOD => 'GET',
# request attributes
SCRIPT_NAME => '',
PATH_INFO => $uri->path,
REQUEST_URI => $uri->path,
QUERY_STRING => '',
SERVER_NAME => $self->url->host,
SERVER_PORT => $self->url->port,
SERVER_PROTOCOL => "HTTP/1.0",
# wallflower defaults
'psgi.streaming' => 1,
};
# add If-Modified-Since headers if the target file exists
my $target = $self->target($uri);
$env->{HTTP_IF_MODIFIED_SINCE} = time2str( ( stat _ )[9] ) if -e $target;
# fixup URI (needed to resolve relative URLs in retrieved documents)
$uri->scheme( $env->{'psgi.url_scheme'} ) if !$uri->scheme;
$uri->host( $env->{SERVER_NAME} ) if !$uri->host;
# get the content
my ( $status, $headers, $file, $content ) = ( 500, [], '', '' );
my $res = Plack::Util::run_app( $self->application, $env );
if ( ref $res eq 'ARRAY' ) {
( $status, $headers, $content ) = @$res;
}
elsif ( ref $res eq 'CODE' ) {
# https://metacpan.org/pod/PSGI#Delayed-Response-and-Streaming-Body
$res->( sub {
my $response = shift;
# delayed response
( $status, $headers, $content ) = @$response;
# streaming
if ( !defined $content ) {
return bless {}, 'Wallflower::NULL'
if $status ne '200'; # we don't care about the body
return _build_handle( $file = $target );
}
return;
} );
}
else { croak "Unknown response from application: $res"; }
# save the content to a file
if ( $status == 200 ) {
my $fh = defined $content && do {
eval { _build_handle( $file = $target ) }
or do { warn $@; return [ 999, [], '' ]; };
};
# copy content to the file
if ( ref $content eq 'ARRAY' ) {
print $fh @$content;
}
elsif ( ref $content eq 'GLOB' ) {
local $/ = \8192;
print {$fh} $_ while <$content>;
close $content;
}
elsif ( eval { $content->can('getline') } ) {
local $/ = \8192;
while ( defined( my $line = $content->getline ) ) {
print {$fh} $line;
}
$content->close;
}
elsif ( !defined $content ) { } # already streamed
else {
croak "Don't know how to handle body: $content";
}
# finish
close $fh;
# if the app sent Last-Modified, set the local file date to that
if ( my $last_modified = HTTP::Headers::Fast->new(@$headers)
->header('Last-Modified') ) {
my $epoch = str2time( $last_modified );
utime $epoch, $epoch, $file;
}
}
elsif ( $status == 304 ) {
$file = $target;
}
return [ $status, $headers, $file ];
}
1;
__END__
=pod
( run in 0.630 second using v1.01-cache-2.11-cpan-140bd7fdf52 )