PAGI

 view release on metacpan or  search on metacpan

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

      my ($scope, $receive, $send) = @_;
      my $ws = PAGI::WebSocket->new($scope, $receive, $send);

      await $ws->accept;

      await $ws->each_text(async sub {
          my ($text) = @_;
          await $ws->send_text("Echo: $text");
      });
  });

  # WebSocket with path parameters
  $router->websocket('/ws/chat/:room' => async sub {
      my ($scope, $receive, $send) = @_;
      my $ws = PAGI::WebSocket->new($scope, $receive, $send);

      # Access room parameter
      my $room = $scope->{path_params}{room};

      await $ws->accept;
      await $ws->send_text("Joined room: $room");

      # ... chat logic ...
  });

  $router->to_app;

=head3 SSE Routes

Route Server-Sent Events by path:

  use strict;
  use warnings;
  use Future::AsyncAwait;
  use PAGI::App::Router;
  use PAGI::SSE;

  my $router = PAGI::App::Router->new;

  # SSE stream
  $router->sse('/events' => async sub {
      my ($scope, $receive, $send) = @_;
      my $sse = PAGI::SSE->new($scope, $receive, $send);

      await $sse->keepalive(30);  # Prevent proxy timeouts
      await $sse->send_event("Connected at: " . time);
      await $sse->run;  # Wait for disconnect
  });

  # SSE with path parameters
  $router->sse('/events/:channel' => async sub {
      my ($scope, $receive, $send) = @_;
      my $sse = PAGI::SSE->new($scope, $receive, $send);

      # Access channel parameter
      my $channel = $scope->{path_params}{channel};

      await $sse->start;
      await $sse->send_event("Subscribed to: $channel");

      # ... streaming logic ...
  });

  $router->to_app;

=head2 Route-Level Middleware

Apply middleware to specific routes by passing an arrayref:

  use strict;
  use warnings;
  use Future::AsyncAwait;
  use PAGI::App::Router;
  use PAGI::Request;
  use PAGI::Response;

  my $router = PAGI::App::Router->new;

  # Define middleware
  my $auth_mw = async sub {
      my ($scope, $receive, $send, $next) = @_;

      # Check authorization
      my $token = '';
      for my $h (@{$scope->{headers}}) {
          if (lc($h->[0]) eq 'authorization') {
              $token = $h->[1];
              last;
          }
      }

      unless ($token eq 'Bearer secret123') {
          my $res = PAGI::Response->new($scope, $send);
          await $res->status(401)->json({ error => 'Unauthorized' });
          return;  # Don't call $next->()
      }

      # Authorized - continue to handler
      await $next->();
  };

  # Apply middleware to specific routes
  $router->get('/admin' => [$auth_mw] => async sub {
      my ($scope, $receive, $send) = @_;
      my $res = PAGI::Response->new($scope, $send);
      await $res->text('Admin panel');
  });

  # Multiple middleware
  my $log_mw = async sub {
      my ($scope, $receive, $send, $next) = @_;
      warn "Request: $scope->{method} $scope->{path}\n";
      await $next->();
  };

  $router->post('/admin/users' => [$log_mw, $auth_mw] => async sub {
      my ($scope, $receive, $send) = @_;
      # ... handler ...
  });

  $router->to_app;

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

      });
  }

  # WebSocket with path parameters
  async sub handle_chat {
      my ($self, $ws) = @_;

      # Access path parameter via $ws->path_param()
      my $room = $ws->path_param('room');

      await $ws->accept;
      await $ws->send_text("Joined room: $room");

      # ... chat logic ...
  }

  1;

=head3 SSE Handlers

SSE handlers receive C<($self, $sse)>:

  package MyApp;
  use parent 'PAGI::Endpoint::Router';
  use strict;
  use warnings;
  use Future::AsyncAwait;

  sub routes {
      my ($self, $r) = @_;

      $r->get('/' => 'home');
      $r->sse('/events' => 'handle_events');
      $r->sse('/events/:channel' => 'handle_channel');
  }

  async sub home {
      my ($self, $req, $res) = @_;
      await $res->html('<h1>Home</h1>');
  }

  # SSE handler receives ($self, $sse)
  async sub handle_events {
      my ($self, $sse) = @_;

      await $sse->keepalive(30);
      await $sse->send_event("Connected at: " . time);
      await $sse->run;
  }

  # SSE with path parameters
  async sub handle_channel {
      my ($self, $sse) = @_;

      # Access path parameter
      my $channel = $sse->scope->{'pagi.params'}{channel};

      await $sse->start;
      await $sse->send_event("Channel: $channel");

      # ... streaming logic ...
  }

  1;

=head3 Lifecycle Hooks

Override C<on_startup> and C<on_shutdown> for application lifecycle management:

  package MyApp;
  use parent 'PAGI::Endpoint::Router';
  use strict;
  use warnings;
  use Future::AsyncAwait;

  # Called when application starts
  async sub on_startup {
      my ($self) = @_;

      # Initialize resources
      $self->state->{db} = DBI->connect('dbi:SQLite:dbname=app.db');
      $self->state->{started_at} = time();

      warn "Application started\n";
  }

  # Called when application shuts down
  async sub on_shutdown {
      my ($self) = @_;

      # Clean up resources
      $self->state->{db}->disconnect if $self->state->{db};

      warn "Application shut down\n";
  }

  sub routes {
      my ($self, $r) = @_;

      $r->get('/' => 'home');
  }

  async sub home {
      my ($self, $req, $res) = @_;

      # Access state initialized in on_startup
      my $db = $self->state->{db};
      my $uptime = time() - $self->state->{started_at};

      await $res->json({
          uptime => $uptime,
          database => defined($db) ? 'connected' : 'disconnected',
      });
  }

  1;

Note: C<$self-E<gt>state> is per-worker in multi-worker mode. For shared state, use an external store (Redis, database, etc.).

=head3 Route-Level Middleware with Method Names



( run in 0.582 second using v1.01-cache-2.11-cpan-140bd7fdf52 )