line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package AnyPAN::ReverseProxy; |
2
|
1
|
|
|
1
|
|
6188
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
29
|
|
3
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
26
|
|
4
|
|
|
|
|
|
|
|
5
|
1
|
|
|
1
|
|
6
|
use parent qw/Plack::Component/; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
5
|
|
6
|
|
|
|
|
|
|
|
7
|
1
|
|
|
1
|
|
5211
|
use Plack::Util::Accessor qw/storage logger source_cache/; |
|
1
|
|
|
|
|
285
|
|
|
1
|
|
|
|
|
6
|
|
8
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
570
|
use Plack::Request; |
|
1
|
|
|
|
|
46947
|
|
|
1
|
|
|
|
|
59
|
|
10
|
1
|
|
|
1
|
|
476
|
use Plack::Response; |
|
1
|
|
|
|
|
1233
|
|
|
1
|
|
|
|
|
30
|
|
11
|
1
|
|
|
1
|
|
499
|
use Plack::MIME; |
|
1
|
|
|
|
|
859
|
|
|
1
|
|
|
|
|
46
|
|
12
|
1
|
|
|
1
|
|
8
|
use Path::Tiny; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
71
|
|
13
|
|
|
|
|
|
|
|
14
|
1
|
|
|
1
|
|
6
|
use AnyPAN; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
53
|
|
15
|
1
|
|
|
1
|
|
29
|
use AnyPAN::Merger; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
21
|
|
16
|
1
|
|
|
1
|
|
5
|
use AnyPAN::Source; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
16
|
|
17
|
1
|
|
|
1
|
|
6
|
use AnyPAN::SourceCache; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
38
|
|
18
|
1
|
|
|
1
|
|
517
|
use AnyPAN::RetryPolicy::NoRetry; |
|
1
|
|
|
|
|
7
|
|
|
1
|
|
|
|
|
24
|
|
19
|
1
|
|
|
1
|
|
6
|
use AnyPAN::PackageInfo; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
16
|
|
20
|
1
|
|
|
1
|
|
14
|
use AnyPAN::Logger::Stderr; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1352
|
|
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
our $DEFAULT_LOGGER = AnyPAN::Logger::Stderr->new(level => $ENV{ANYPAN_REVERSE_PROXY_LOG_LEVEL} || 'warn'); |
23
|
|
|
|
|
|
|
our $DEFAULT_REQUEST_TIMEOUT = 10; |
24
|
|
|
|
|
|
|
our $DEFAULT_RETRY_POLICY = AnyPAN::RetryPolicy::NoRetry->instance(); |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub new { |
27
|
0
|
|
|
0
|
1
|
|
my $class = shift; |
28
|
0
|
|
|
|
|
|
my $self = $class->SUPER::new(@_); |
29
|
0
|
|
|
|
|
|
$self->{sources} = []; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# set default |
32
|
0
|
|
0
|
|
|
|
$self->{logger} ||= $DEFAULT_LOGGER; |
33
|
0
|
|
0
|
|
|
|
$self->{source_cache} ||= _get_default_source_cache($self->{logger}); |
34
|
|
|
|
|
|
|
|
35
|
0
|
|
|
|
|
|
return $self; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub prepare_app { |
39
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
40
|
0
|
0
|
|
|
|
|
unless ($self->storage) { |
41
|
0
|
|
|
|
|
|
die "storage is required"; |
42
|
|
|
|
|
|
|
} |
43
|
|
|
|
|
|
|
|
44
|
0
|
0
|
|
|
|
|
unless (@{ $self->{sources} }) { |
|
0
|
|
|
|
|
|
|
45
|
0
|
|
|
|
|
|
die "sources are required"; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
} |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub add_source { |
50
|
0
|
|
|
0
|
0
|
|
my ($self, $source_url) = @_; |
51
|
0
|
|
|
|
|
|
my $source = AnyPAN::Source->new($source_url); |
52
|
0
|
|
|
|
|
|
$self->logger->debug("add @{[ $source->name ]} as source"); |
|
0
|
|
|
|
|
|
|
53
|
0
|
|
|
|
|
|
push @{ $self->{sources} } => $source; |
|
0
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub call { |
57
|
0
|
|
|
0
|
1
|
|
my ($self, $env) = @_; |
58
|
0
|
|
|
|
|
|
my $req = Plack::Request->new($env); |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# GET/HEAD request only |
61
|
0
|
0
|
0
|
|
|
|
unless ($req->method eq 'GET' || $req->method eq 'HEAD') { |
62
|
0
|
|
|
|
|
|
return $self->_res_405()->finalize(); |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
# path routing |
66
|
0
|
0
|
|
|
|
|
if ($req->path =~ m!^/authors/id/([A-Z])/(\1[A-Z])/\2[-A-Z0-9]*/.+$!o) { |
|
|
0
|
|
|
|
|
|
67
|
0
|
|
|
|
|
|
return $self->proxt_to_storage_or_sources($req)->finalize(); |
68
|
|
|
|
|
|
|
} elsif ($req->path eq '/modules/02packages.details.txt.gz') { |
69
|
0
|
|
|
|
|
|
return $self->proxy_to_storage($req)->finalize(); |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
|
|
|
return $self->_res_404()->finalize(); |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
sub proxt_to_storage_or_sources { |
76
|
0
|
|
|
0
|
0
|
|
my ($self, $req) = @_; |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# try proxy to storage |
79
|
0
|
|
|
|
|
|
my $res = $self->proxy_to_storage($req); |
80
|
0
|
0
|
|
|
|
|
return $res if $res->status == 200; |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
# remove "/authors/id/" (e.g. D/DU/DUMMY/Foo.tar.gz) |
83
|
0
|
|
|
|
|
|
my $path = substr $req->path, length "/authors/id/"; |
84
|
0
|
|
|
|
|
|
my $package_info = AnyPAN::PackageInfo->new(path => $path); |
85
|
0
|
|
|
|
|
|
my $content_type = Plack::MIME->mime_type($path); |
86
|
0
|
|
|
|
|
|
for my $source (@{ $self->{sources} }) { |
|
0
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# fetch from source |
88
|
0
|
|
|
|
|
|
my $package_path = eval { |
89
|
0
|
|
|
|
|
|
$self->source_cache->get_or_fetch_package($source, $package_info) |
90
|
|
|
|
|
|
|
}; |
91
|
0
|
0
|
|
|
|
|
if (my $e = $@) { |
92
|
0
|
0
|
|
|
|
|
if (AnyPAN::Agent::Exception::NotFound->caught($e)) { |
93
|
0
|
|
|
|
|
|
$self->logger->debug("skip package $path on @{[ $source->name ]}"); |
|
0
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
next; # skip it |
95
|
|
|
|
|
|
|
} |
96
|
0
|
|
|
|
|
|
$self->logger->error("failed to fetch package $path on @{[ $source->name ]}"); |
|
0
|
|
|
|
|
|
|
97
|
0
|
|
|
|
|
|
die $e; |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
$self->logger->debug("found @{[ $req->path ]} from @{[ $source->name ]}"); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# save to storage |
103
|
0
|
|
|
|
|
|
my $save_key = $source->package_path($package_info->canonicalized_path); |
104
|
0
|
|
|
|
|
|
$self->storage->copy($package_path, $save_key); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# create response |
107
|
0
|
|
|
|
|
|
my $fh = $package_path->openr_raw(); |
108
|
0
|
|
|
|
|
|
my $res = Plack::Response->new(200, [ |
109
|
|
|
|
|
|
|
'Content-Type' => $content_type, |
110
|
|
|
|
|
|
|
'Content-Length' => $package_path->stat->size, |
111
|
|
|
|
|
|
|
'Cache-Control' => 'private', |
112
|
|
|
|
|
|
|
], $fh); |
113
|
0
|
|
|
|
|
|
return $res; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
0
|
|
|
|
|
|
return $self->_res_404(); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
sub proxy_to_storage { |
120
|
0
|
|
|
0
|
0
|
|
my ($self, $req) = @_; |
121
|
0
|
|
|
|
|
|
my $storage_key = substr $req->path, 1; # remove first slash |
122
|
0
|
|
|
|
|
|
my $content_type = Plack::MIME->mime_type($storage_key); |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# check from storage |
125
|
0
|
|
|
|
|
|
my $storage_path = $self->storage->fetch($storage_key); |
126
|
0
|
0
|
|
|
|
|
return $self->_res_404() unless $storage_path; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# create response |
129
|
0
|
|
|
|
|
|
$self->logger->debug("found $storage_key from storage"); |
130
|
0
|
|
|
|
|
|
my $fh = $storage_path->openr_raw(); |
131
|
0
|
|
|
|
|
|
my $res = Plack::Response->new(200, [ |
132
|
|
|
|
|
|
|
'Content-Type' => $content_type, |
133
|
|
|
|
|
|
|
'Content-Length' => $storage_path->stat->size, |
134
|
|
|
|
|
|
|
'Cache-Control' => 'private', |
135
|
|
|
|
|
|
|
], $fh); |
136
|
0
|
|
|
|
|
|
return $res; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
0
|
|
|
0
|
|
|
sub _res_404 { shift->_res_simple(404, 'Not Found') } |
140
|
0
|
|
|
0
|
|
|
sub _res_405 { shift->_res_simple(405, 'Method Not Allowed') } |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub _res_simple { |
143
|
0
|
|
|
0
|
|
|
my ($self, $status, $content) = @_; |
144
|
0
|
|
|
|
|
|
return Plack::Response->new($status, [ |
145
|
|
|
|
|
|
|
'Content-Type' => 'text/plain', |
146
|
|
|
|
|
|
|
'Content-Length' => length $content, |
147
|
|
|
|
|
|
|
'Cache-Control' => 'no-cache,no-store', |
148
|
|
|
|
|
|
|
], $content); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub _get_default_source_cache { |
152
|
0
|
|
|
0
|
|
|
my $logger = shift; |
153
|
0
|
|
|
|
|
|
return AnyPAN::SourceCache->new( |
154
|
|
|
|
|
|
|
cache_dir => $AnyPAN::Merger::DEFAULT_SOURCE_CACHE_DIR, |
155
|
|
|
|
|
|
|
index_cache_timeout => $AnyPAN::Merger::DEFAULT_SOURCE_INDEX_CACHE_TIMEOUT, |
156
|
|
|
|
|
|
|
agent => _get_default_agent($logger), |
157
|
|
|
|
|
|
|
logger => $logger, |
158
|
|
|
|
|
|
|
); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
sub _get_default_agent { |
162
|
0
|
|
|
0
|
|
|
my $logger = shift; |
163
|
0
|
|
|
|
|
|
return AnyPAN::Agent->new( |
164
|
|
|
|
|
|
|
agent => __PACKAGE__."/$AnyPAN::VERSION", |
165
|
|
|
|
|
|
|
timeout => $DEFAULT_REQUEST_TIMEOUT, |
166
|
|
|
|
|
|
|
logger => $logger, |
167
|
|
|
|
|
|
|
retry_policy => $DEFAULT_RETRY_POLICY, |
168
|
|
|
|
|
|
|
); |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
1; |
172
|
|
|
|
|
|
|
__END__ |