Group
Extension

App-FakeCDN/lib/App/FakeCDN.pm

package App::FakeCDN;
use 5.010001;
use strict;
use warnings;

our $VERSION = "0.01";

use Path::Tiny;
use Plack::MIME;

use Mouse;
use Mouse::Util::TypeConstraints;

subtype 'App::FakeCDN::Path' => as class_type('Path::Tiny');
coerce 'App::FakeCDN::Path'
    => from 'Str'
    => via { Path::Tiny::path($_) };

has cache => (
    is => 'ro',
    isa => 'Object',
    default => sub {
        require Cache::Memory::Simple;
        Cache::Memory::Simple->new;
    },
);

has root => (
    is       => 'ro',
    isa      => 'App::FakeCDN::Path',
    required => 1,
    coerce   => 1,
);

has expiration => (
    is  => 'ro',
    isa => 'Int',
);

no Mouse;

sub to_app {
    my $self = shift;

    sub {
        my $env = shift;

        my $path  = $env->{PATH_INFO} // '';
        my $query = $env->{QUERY_STRING} // '';

        if ($path =~ /\0/ || $query =~ /\0/) {
            return $self->res_400;
        }
        $path =~ s!^/!!;

        my ($data, $content_type) = $self->get_content($path, $query);

        return $self->res_404 unless $data;

        return [ 200, [
            'Content-Type'   => $content_type,
            'Content-Length' => length($data),
        ], [ $data ] ];
    };
}

sub res_400 {
    [400, ['Content-Type' => 'text/plain', 'Content-Length' => 11], ['Bad Request']];
}

sub res_404 {
    [404, ['Content-Type' => 'text/plain', 'Content-Length' => 9], ['not found']];
}

sub get_content {
    my ($self, $path, $query) = @_;

    my $mime_type = Plack::MIME->mime_type($path) // 'application/octet-stream';
    my $is_binary = is_binary($mime_type);

    unless ($is_binary) {
        my $encoding = $self->encoding || 'utf-8';
        $mime_type .= "; charset=$encoding";
    }

    my $content = $self->get_stuff($path, $query);

    ($content, $mime_type);
}

sub is_binary {
    my $mime_type = shift;

    $mime_type !~ /\b(?:text|xml|javascript|json)\b/;
}

sub get_stuff {
    my ($self, $path, $query) = @_;
    my $key = $path . $query;

    if (my $val = $self->cache->get($key)) {
        return $val;
    }
    else {
        my $file = $self->root->child($path);
        return unless -e -f $file;

        my $val = $file->slurp;
        $self->cache->set($key, $val, $self->expiration || ());
        return $val;
    }
}

sub parse_options {
    my ($class, @argv) = @_;

    require Getopt::Long;
    require Pod::Usage;

    my $p = Getopt::Long::Parser->new(
        config => [qw/posix_default no_ignore_case auto_help pass_through bundling/]
    );
    $p->getoptionsfromarray(\@argv, \my %opt, qw/
        root=s
        expiration=i
    /) or Pod::Usage::pod2usage();
    Pod::Usage::pod2usage() if !$opt{root};

    (\%opt, \@argv);
}

sub run {
    my $self = shift;
    my %args = @_ == 1 ? %{$_[0]} : @_;
    if (!$args{listen} && !$args{port} && !$ENV{SERVER_STARTER_PORT}) {
        $args{port} = 4907;
    }
    require Plack::Loader;
    Plack::Loader->auto(%args)->run($self->to_app);
}

1;
__END__

=encoding utf-8

=head1 NAME

App::FakeCDN - fake CDN server emulator

=head1 SYNOPSIS

    use App::FakeCDN;
    my $fake_cdn = App::FakeCDN->new(root => 'static');
    $fake_cdn->to_app;

=head1 DESCRIPTION

App::FakeCDN launches fake CDN server emulator.

B<THE SOFTWARE IS ALPHA QUALITY. API MAY CHANGE WITHOUT NOTICE.>

=head1 LICENSE

Copyright (C) Songmu.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 AUTHOR

Songmu E<lt>y.songmu@gmail.comE<gt>

=cut



Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.