PAGI
view release on metacpan or search on metacpan
lib/PAGI/Middleware/Maintenance.pm view on Meta::CPAN
return async sub {
my ($scope, $receive, $send) = @_;
if ($scope->{type} ne 'http') {
await $app->($scope, $receive, $send);
return;
}
# Check if maintenance is enabled
my $enabled = ref $self->{enabled} eq 'CODE'
? $self->{enabled}->()
: $self->{enabled};
unless ($enabled) {
await $app->($scope, $receive, $send);
return;
}
# Check bypass conditions
if ($self->_should_bypass($scope)) {
await $app->($scope, $receive, $send);
return;
}
# Serve maintenance page
await $self->_send_maintenance($send);
};
}
sub _should_bypass {
my ($self, $scope) = @_;
# Check bypass paths
my $path = $scope->{path} // '';
for my $bypass_path (@{$self->{bypass_paths}}) {
if (ref $bypass_path eq 'Regexp') {
return 1 if $path =~ $bypass_path;
} else {
return 1 if $path eq $bypass_path;
}
}
# Check bypass IPs
my $client_ip = exists $scope->{client} ? ($scope->{client}[0] // '') : '';
for my $bypass_ip (@{$self->{bypass_ips}}) {
if ($bypass_ip =~ m{/}) {
# CIDR notation
return 1 if $self->_ip_in_cidr($client_ip, $bypass_ip);
} else {
# Exact match
return 1 if $client_ip eq $bypass_ip;
}
}
return 0;
}
sub _ip_in_cidr {
my ($self, $ip, $cidr) = @_;
my ($network, $bits) = split m{/}, $cidr;
# Simple IPv4 check
return 0 unless $ip =~ /^[\d.]+$/ && $network =~ /^[\d.]+$/;
my $ip_num = $self->_ip_to_num($ip);
my $net_num = $self->_ip_to_num($network);
return 0 unless defined $ip_num && defined $net_num;
my $mask = ~((1 << (32 - $bits)) - 1) & 0xFFFFFFFF;
return ($ip_num & $mask) == ($net_num & $mask);
}
sub _ip_to_num {
my ($self, $ip) = @_;
my @octets = split /\./, $ip;
return unless @octets == 4;
return unless _all_valid_octets(@octets);
return ($octets[0] << 24) + ($octets[1] << 16) + ($octets[2] << 8) + $octets[3];
}
sub _all_valid_octets {
for (@_) {
return 0 unless /^\d+$/ && $_ >= 0 && $_ <= 255;
}
return 1;
}
async sub _send_maintenance {
my ($self, $send) = @_;
my $body = $self->{body};
my @headers = (
['Content-Type', $self->{content_type}],
['Content-Length', length($body)],
);
if (defined $self->{retry_after}) {
push @headers, ['Retry-After', $self->{retry_after}];
}
await $send->({
type => 'http.response.start',
status => 503,
headers => \@headers,
});
await $send->({
type => 'http.response.body',
body => $body,
more => 0,
});
}
sub _default_body {
my ($self) = @_;
return <<'HTML';
<!DOCTYPE html>
( run in 0.554 second using v1.01-cache-2.11-cpan-71847e10f99 )