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 )