PAGI

 view release on metacpan or  search on metacpan

lib/PAGI/Cookbook.pod  view on Meta::CPAN

              ['content-type', 'application/octet-stream'],
              ['content-disposition', qq{attachment; filename="$filename"}],
          ],
      });
      await $send->({
          type => 'http.response.body',
          file => $filepath,
      });
  });

  my $app = builder {
      enable 'XSendfile',
          type    => 'X-Accel-Redirect',
          mapping => { '/var/www/files/' => '/protected/' };
      $router->to_app;
  };

=head3 Apache Configuration (mod_xsendfile)

Enable mod_xsendfile and whitelist your file directory:

  # Apache config
  XSendFile On
  XSendFilePath /var/www/files

  # Application
  my $app = builder {
      enable 'XSendfile', type => 'X-Sendfile';
      $my_app;
  };

=head3 Filehandle Support

The middleware also works with filehandle responses, but only if the
filehandle object has a C<path()> method. L<IO::File::WithPath> from CPAN
is an easy drop-in for this:

  use IO::File::WithPath;

  # This works - IO::File::WithPath provides the path method
  my $fh = IO::File::WithPath->new('/path/to/file.bin', 'r');
  await $send->({ type => 'http.response.body', fh => $fh });

  # This does NOT trigger X-Sendfile (no path method)
  open my $plain_fh, '<', '/path/to/file.bin';
  await $send->({ type => 'http.response.body', fh => $plain_fh });

For plain filehandles, either use C<file =E<gt> $path> directly, or use
L<IO::File::WithPath>. See L<PAGI::Middleware::XSendfile> for details.

=head1 TESTING

Use L<PAGI::Test::Client> to test apps directly without a running server:

  use strict;
  use warnings;
  use Test2::V0;
  use PAGI::Test::Client;

  # Load your app
  my $app = require './app.pl';
  my $client = PAGI::Test::Client->new(app => $app);

  subtest 'GET /' => sub {
      my $res = $client->get('/');
      is $res->status, 200, 'status is 200';
      like $res->text, qr/Hello/, 'body contains Hello';
  };

  subtest 'POST /api/users' => sub {
      my $res = $client->post('/api/users',
          json => { name => 'Alice' },
      );
      is $res->status, 201, 'status is 201';
      is $res->json->{id}, 1, 'returns user id';
  };

  subtest 'form submission' => sub {
      my $res = $client->post('/login',
          form => { user => 'admin', pass => 'secret' },
      );
      is $res->status, 302, 'redirects after login';

      # Session cookies persist across requests
      my $dashboard = $client->get('/dashboard');
      is $dashboard->status, 200, 'authenticated access';
  };

  subtest 'custom headers' => sub {
      my $res = $client->get('/api/data',
          headers => { Authorization => 'Bearer token123' },
      );
      is $res->status, 200;
  };

  done_testing;

See L<PAGI::Test::Client> for the full API including WebSocket and SSE testing.

=head1 SEE ALSO

=over 4

=item * L<PAGI::Tutorial> - Learn PAGI from the ground up

=item * L<PAGI::App::Router> - Functional routing

=item * L<PAGI::Endpoint::Router> - Class-based routing

=item * L<PAGI::Middleware::Session> - Session management

=item * L<PAGI::App::WebSocket::Chat> - Multi-room chat application

=item * L<PAGI::Middleware::XSendfile> - Delegate file serving to reverse proxy

=item * L<PAGI::Test::Client> - Test apps without a running server

=back

=head1 AUTHOR



( run in 2.423 seconds using v1.01-cache-2.11-cpan-5837b0d9d2c )