PAGI-Server

 view release on metacpan or  search on metacpan

lib/PAGI/Server/Runner.pm  view on Meta::CPAN

    }
    # If Future::IO::Impl::IOAsync not installed, that's fine - user just
    # won't have Future::IO integration. Apps that need it will get a
    # helpful error message from PAGI::SSE->every() or similar.
}

sub _is_module_name {
    my ($self, $spec) = @_;
    return $spec =~ /::/;
}

sub _is_file_path {
    my ($self, $spec) = @_;
    return $spec =~ m{/} || $spec =~ /\.(?:pl|psgi)$/i;
}

sub _load_module {
    my ($self, $module, %args) = @_;

    # Validate module name (basic security check)
    die "Invalid module name: $module\n"
        unless $module =~ /^[A-Za-z_][A-Za-z0-9_]*(?:::[A-Za-z_][A-Za-z0-9_]*)*$/;

    # Try to load the module
    my $file = $module;
    $file =~ s{::}{/}g;
    $file .= '.pm';

    eval { require $file };
    if ($@) {
        die "Cannot find module '$module': $@\n";
    }

    die "Module '$module' does not have a new() method\n"
        unless $module->can('new');

    # Get the module's actual file path for correct home directory detection
    my $module_file = $INC{$file};

    # Instantiate, then normalize the resulting value. Discovery may produce
    # either a native PAGI coderef or an application-provider object.
    my $instance = eval { $module->new(%args, _caller_file => $module_file) };
    die "Error constructing module '$module': $@\n" if $@;

    return PAGI::Server::AppNormalizer::normalize_app(
        $instance,
        "Module '$module' constructor",
    );
}

sub _load_file {
    my ($self, $file) = @_;

    # Convert to absolute path
    $file = File::Spec->rel2abs($file);

    die "App file not found: $file\n" unless -f $file;

    # Match plackup behavior so FindBin::Bin resolves to the app file directory
    local $0 = $file;
    local @ARGV = ($file);
    if (exists $INC{'FindBin.pm'}) {
        FindBin::again();
    }

    my $app = do $file;

    if ($@) {
        die "Error loading $file: $@\n";
    }
    if (!defined $app && $!) {
        die "Error reading $file: $!\n";
    }
    return PAGI::Server::AppNormalizer::normalize_app($app, 'App file');
}

sub _parse_app_args {
    my ($self, @args) = @_;

    my %result;
    for my $arg (@args) {
        if ($arg =~ /^([^=]+)=(.*)$/) {
            $result{$1} = $2;
        }
        else {
            $self->_log(warn => "Ignoring argument without '=': $arg");
        }
    }
    return %result;
}

sub _show_help {
    my ($self) = @_;

    print <<'HELP';
Usage: pagi-server [options] [app] [key=value ...]

Common Options (handled by Runner):
    -I, --lib PATH      Add PATH to @INC (repeatable, like perl -I)
    -a, --app FILE      Load app from file (legacy option)
    -o, --host HOST     Bind address (default: 127.0.0.1)
    -p, --port PORT     Bind port (default: 5000)
    -s, --server CLASS  Server class (default: PAGI::Server)
    -E, --env MODE      Environment mode (development, production, none)
    -l, --loop BACKEND  Event loop backend (EV, Epoll, UV, Poll)
    --access-log FILE   Access log file (default: STDERR)
    --no-access-log     Disable access logging
    --error-log FILE    Diagnostic log file (default: STDERR)
    -D, --daemonize     Run as background daemon
    --pid FILE          Write PID to file
    --user USER         Run as specified user (after binding)
    --group GROUP       Run as specified group (after binding)
    -q, --quiet         Report errors only (shorthand for --log-level error)
    --no-default-middleware  Disable mode-based middleware
    -v, --version       Show version info
    --help              Show this help

Environment Modes:
    development    Auto-enable Lint middleware if PAGI-Tools installed (default if TTY)
    production     No auto-middleware (default if no TTY)
    none           Explicit opt-out of all auto-middleware



( run in 0.806 second using v1.01-cache-2.11-cpan-d80b1682f3f )