NAME

PAGI::App::File - Serve static files

SYNOPSIS

use PAGI::App::File;

my $app = PAGI::App::File->new(
    root => '/var/www/static',
)->to_app;

DESCRIPTION

PAGI::App::File serves static files from a configured root directory.

Features

  • Efficient streaming (no memory bloat for large files)

  • ETag caching with If-None-Match support (304 Not Modified)

  • Range requests (HTTP 206 Partial Content)

  • Automatic MIME type detection for common file types

  • Index file resolution (index.html, index.htm)

Security

This module implements multiple layers of path traversal protection:

  • Null byte injection blocking

  • Double-dot and triple-dot component blocking

  • Backslash normalization (Windows path separator)

  • Hidden file blocking (dotfiles like .htaccess, .env)

  • Symlink escape detection via realpath verification

CONFIGURATION

  • root - Root directory for files

  • default_type - Default MIME type (default: application/octet-stream)

  • index - Index file names (default: [index.html, index.htm])

  • handle_ranges - Process Range headers (default: 1)

    When enabled (default), the app processes Range request headers and returns 206 Partial Content responses. Set to 0 to ignore Range headers and always return the full file.

    When to disable Range handling:

    When using PAGI::Middleware::XSendfile with a reverse proxy (Nginx, Apache), you should disable range handling. The proxy will handle Range requests more efficiently using its native sendfile implementation:

    my $app = PAGI::App::File->new(
        root          => '/var/www/files',
        handle_ranges => 0,  # Let proxy handle Range requests
    )->to_app;
    
    my $wrapped = builder {
        enable 'XSendfile',
            type    => 'X-Accel-Redirect',
            mapping => { '/var/www/files/' => '/protected/' };
        $app;
    };

    With this setup, your app always sends the full file path via X-Sendfile header, and Nginx handles Range requests natively (which is faster than doing it in Perl).