NAME
PAGI::Server::ConnectionState - Connection state tracking for HTTP requests
SYNOPSIS
my $conn = $scope->{'pagi.connection'};
# Synchronous, non-destructive check
if ($conn->is_connected) {
# Client still connected
}
# Get disconnect reason (undef while connected)
my $reason = $conn->disconnect_reason;
# Register cleanup callback
$conn->on_disconnect(sub {
my ($reason) = @_;
cleanup_resources();
});
# Await disconnect (if Future provided)
if (my $future = $conn->disconnect_future) {
my $reason = await $future;
}
DESCRIPTION
PAGI::Server::ConnectionState provides a mechanism for applications to detect client disconnection without consuming messages from the receive queue.
This addresses a fundamental limitation in the PAGI (and ASGI) model where checking for disconnect via receive() may inadvertently consume request body data.
See PAGI Specification 0.3 for the full specification.
METHODS
new
my $conn = PAGI::Server::ConnectionState->new();
my $conn = PAGI::Server::ConnectionState->new(future => $disconnect_future);
Creates a new connection state object. The optional future argument provides a Future that will be resolved when disconnect occurs.
is_connected
my $connected = $conn->is_connected; # Boolean
Returns true if the connection is still open, false if disconnected.
This is a synchronous, non-destructive check that does not consume messages from the receive queue.
disconnect_reason
my $reason = $conn->disconnect_reason; # String or undef
Returns the disconnect reason string, or undef if still connected.
Standard reason strings:
client_closed- Client initiated clean close (TCP FIN)client_timeout- Client stopped responding (read timeout)idle_timeout- Connection idle too long between requestswrite_timeout- Response write timed outwrite_error- Socket write failed (EPIPE, ECONNRESET)read_error- Socket read failedprotocol_error- HTTP parse error, invalid requestserver_shutdown- Server shutting down gracefullybody_too_large- Request body exceeded limit
disconnect_future
my $future = $conn->disconnect_future; # Future or undef
my $reason = await $future;
Returns a Future that resolves when the connection closes, or undef if the server does not support this feature.
The Future resolves with the disconnect reason string.
This is useful for racing against other async operations:
await Future->wait_any($disconnect_future, $event_future);
on_disconnect
$conn->on_disconnect(sub {
my ($reason) = @_;
# cleanup code
});
Registers a callback to be invoked when disconnect occurs.
May be called multiple times to register multiple callbacks
Callbacks are invoked in registration order
Callbacks receive the disconnect reason as the first argument
If registered after disconnect already occurred, callback is invoked immediately with the reason
One callback's failure does not prevent other callbacks from being invoked
_mark_disconnected
$conn->_mark_disconnected($reason);
Internal method - Called by the server when disconnect is detected.
Updates the connection state and invokes all registered callbacks. Applications should not call this method directly.
State transitions occur in this order:
- 1.
is_connected()returns false - 2.
disconnect_reason()returns the reason string - 3.
disconnect_future()resolves with the reason (if provided) - 4.
on_disconnectcallbacks are invoked in registration order
USAGE WITH PAGI::Request
The PAGI::Request class provides convenience methods that delegate to the connection object:
my $req = PAGI::Request->new($scope, $receive);
# Access connection object directly
my $conn = $req->connection;
# Convenience delegates
$req->is_connected; # $conn->is_connected
$req->is_disconnected; # !$conn->is_connected
$req->disconnect_reason; # $conn->disconnect_reason
$req->on_disconnect(sub { ... }); # $conn->on_disconnect(...)
$req->disconnect_future; # $conn->disconnect_future
EXAMPLE: Basic Connection Check
async sub handler {
my ($scope, $receive, $send) = @_;
my $conn = $scope->{'pagi.connection'};
# Check before expensive work
return unless $conn->is_connected;
my $result = await expensive_operation();
# Check again before responding
return unless $conn->is_connected;
await $send->({ type => 'http.response.start', status => 200, headers => [] });
await $send->({ type => 'http.response.body', body => $result, more => 0 });
}
EXAMPLE: Cleanup on Disconnect
async sub handler {
my ($scope, $receive, $send) = @_;
my $conn = $scope->{'pagi.connection'};
my $temp_file = create_temp_file();
my $lock = acquire_lock();
# Register cleanup - runs automatically if client disconnects
$conn->on_disconnect(sub {
my ($reason) = @_;
$temp_file->unlink;
$lock->release;
log_info("Client disconnected: $reason");
});
# Do work - cleanup happens automatically if client leaves
my $result = await process_data($temp_file);
# Normal cleanup on success
$temp_file->unlink;
$lock->release;
await send_response($send, $result);
}
SEE ALSO
PAGI::Request - High-level request API with connection convenience methods
PAGI::Server - Reference server implementation
PAGI::Server::Connection - Per-connection state machine (internal)