Net-Blossom-Server
view release on metacpan or search on metacpan
lib/Net/Blossom/Server.pm view on Meta::CPAN
same status behavior as C<handle_head_upload>.
=head2 handle_mirror
my $response = $server->handle_mirror($request, %opts);
Handles a normalized C<PUT /mirror> request. The request body must be a JSON
object with a C<url> field. The URL must use C<http> or C<https>, have a host,
must not include userinfo, and must not include a fragment.
The server calls the configured C<mirror_fetcher> with a streaming sink and stores
origin bytes as the fetcher writes them. SHA-256 calculation and storage commit
remain owned by the server core. A missing C<mirror_fetcher> returns C<503>.
Malformed mirror requests return C<400>. Origin fetch failures, unusable fetch
results, or origin content-length mismatches return C<502>.
The fetcher may be a code reference called with C<$url> and C<< sink => $sink >>,
or an object called as C<< $fetcher->fetch_blob($url, sink => $sink) >>. It must
call C<< $sink->start(%metadata) >> before writing bytes, then call
C<< $sink->write($chunk) >> for each scalar byte chunk. C<%metadata> may include
C<type> and C<content_length>; missing C<type> defaults to
lib/Net/Blossom/Server/MirrorFetcher/HTTP.pm view on Meta::CPAN
=head1 DESCRIPTION
C<Net::Blossom::Server::MirrorFetcher::HTTP> is a small allowlist-only fetcher
for C<PUT /mirror>. It is intentionally conservative: it does not provide public
internet mirroring by default.
The fetcher validates the URL before making a request, requires the URL host to
match one of the configured C<allowed_hosts>, disables redirects in the default
HTTP client, clears common proxy environment variables while fetching, and
enforces a maximum response size while streaming into the supplied sink.
=head1 CONSTRUCTOR
=head2 new
my $fetcher = Net::Blossom::Server::MirrorFetcher::HTTP->new(%args);
Required arguments:
=over 4
t/04-StorageContract.t view on Meta::CPAN
like(dies { run_storage_contract_tests(name => 'storage', factory => sub { Local::ContractStorage->new }, bogus => 1) },
qr/unknown argument\(s\): bogus/, 'unknown argument rejected');
};
run_storage_contract_tests(
name => 'in-memory contract storage',
factory => sub { Local::ContractStorage->new },
);
run_storage_contract_tests(
name => 'streaming body contract storage',
factory => sub { Local::StreamingContractStorage->new },
);
done_testing;
t/19-MirrorFetcherHTTP.t view on Meta::CPAN
like(dies { Net::Blossom::Server::MirrorFetcher::HTTP->new(allowed_hosts => ['cdn.example'], max_bytes => 0) },
qr/max_bytes must be a positive integer/, 'max_bytes positive');
like(dies { Net::Blossom::Server::MirrorFetcher::HTTP->new(allowed_hosts => ['cdn.example'], max_bytes => 1024, timeout => 0) },
qr/timeout must be a positive integer/, 'timeout positive');
like(dies { Net::Blossom::Server::MirrorFetcher::HTTP->new(allowed_hosts => ['cdn.example'], max_bytes => 1024, user_agent => bless {}, 'Local::NoRequest') },
qr/user_agent must provide request/, 'user agent contract required');
like(dies { Net::Blossom::Server::MirrorFetcher::HTTP->new(allowed_hosts => ['cdn.example'], max_bytes => 1024, bogus => 1) },
qr/unknown argument\(s\): bogus/, 'unknown arguments rejected');
};
subtest 'fetch_blob requires a streaming sink' => sub {
my $fetcher = Net::Blossom::Server::MirrorFetcher::HTTP->new(
allowed_hosts => ['cdn.example'],
max_bytes => 1024,
user_agent => Local::UA->new,
);
like(dies { $fetcher->fetch_blob('https://cdn.example/blob.bin') },
qr/sink is required/, 'sink required');
like(dies { $fetcher->fetch_blob('https://cdn.example/blob.bin', sink => bless {}, 'Local::NoSink') },
qr/sink must provide start and write/, 'sink contract required');
t/19-MirrorFetcherHTTP.t view on Meta::CPAN
status => 200,
reason => 'OK',
headers => { 'content-length' => 5 },
content => 'hello',
}),
);
my $sink = Local::Sink->new;
is(error_status { $fetcher->fetch_blob('https://cdn.example/blob.bin', sink => $sink) },
413, 'Content-Length over max rejected');
is_deeply($sink->{chunks}, [], 'oversized Content-Length is rejected before streaming body');
$fetcher = Net::Blossom::Server::MirrorFetcher::HTTP->new(
allowed_hosts => ['cdn.example'],
max_bytes => 4,
user_agent => Local::UA->new(response => {
success => 1,
status => 200,
reason => 'OK',
headers => {},
body_chunks => ['he', 'llo'],
( run in 2.637 seconds using v1.01-cache-2.11-cpan-995e09ba956 )