NAME
Net::Async::Kubernetes::PortForwardSession - Duplex websocket session for pod port-forward, exec and attach
VERSION
version 0.011
SYNOPSIS
my $session = $kube->exec('Pod', 'my-pod',
namespace => 'default',
command => ['sh'],
tty => 1,
on_frame => sub {
my ($channel, $payload) = @_;
print $payload if $channel == 1; # stdout
},
)->get;
$session->write_stdin("id\n");
$session->resize(width => 120, height => 40);
$session->close(code => 1000);
# port-forward: payload only, preamble stripped by the library
my $pf = $kube->port_forward('Pod', 'web',
ports => [8080],
on_data => sub { my ($session, $port, $bytes) = @_; print $bytes },
)->get;
$pf->write_port(8080, "GET / HTTP/1.0\r\n\r\n");
DESCRIPTION
A duplex session handle for the Kubernetes exec/attach/port-forward websocket sub-protocol (v4.channel.k8s.io by default). Every frame on the wire carries a channel number as its first byte, identifying which logical stream the rest of the frame belongs to. write_channel is the primitive that builds such a frame. There are two kinds of session, told apart by "is_port_forward".
Exec and attach sessions have the channels stdin (0), stdout (1), stderr (2), error/status (3) and TTY resize (4). write_stdin and resize are the convenience wrappers over channels 0 and 4.
Port-forward sessions have a data and an error channel per forwarded port (2 * $i and 2 * $i + 1), each opened by a two-byte port-number frame. write_port writes to the data channel of a port; write_stdin and resize croak. The library strips and checks the preamble and hands the rest to the on_data and on_port_error callbacks of "port_forward" in Net::Async::Kubernetes.
Callers do not normally construct this class themselves: it is what "port_forward" in Net::Async::Kubernetes, "exec" in Net::Async::Kubernetes, and "attach" in Net::Async::Kubernetes resolve their returned Future with, and what they pass to an on_open callback if one was given. Incoming frames are not read through this object; they arrive via the callbacks passed to those methods (on_frame with raw ($channel, $payload) pairs).
new
my $session = Net::Async::Kubernetes::PortForwardSession->new(
ws_client => $ws_client,
);
Wraps an already-connected websocket client (normally a Net::Async::WebSocket::Client) as a session. ws_client is required.
With ports (an arrayref of the requested remote ports, in the order of the port-forward request) the session is a port-forward session: it knows the channel layout of the port-forward sub-protocol and offers "write_port"; "write_stdin" and "resize" croak on it. Without ports it is an exec/attach session.
Callers do not normally build a session this way: the Future returned by "port_forward" in Net::Async::Kubernetes, "exec" in Net::Async::Kubernetes, and "attach" in Net::Async::Kubernetes resolves to one, already bound to the open connection, and it is what those methods pass to an on_open callback.
is_port_forward
True for a session of "port_forward" in Net::Async::Kubernetes, false for exec and attach. The first kind has "write_port" and refuses "write_stdin" and "resize"; the second is the other way round.
ports
The remote ports of a port-forward session in request order, as a fresh arrayref; channel 2 * $i and 2 * $i + 1 belong to the $ith of them. undef for an exec or attach session.
write_port
$session->write_port(8080, "GET / HTTP/1.1\r\nHost: x\r\n\r\n");
Port-forward sessions only. Send $bytes to the forwarded TCP connection of remote $port: one binary frame on the port's data channel, 2 * $i for the $ith requested port. It croaks on a port the session was not opened for and on an exec/attach session.
Returns the Future from "write_channel".
write_channel
$session->write_channel(0, "id\n");
Send $payload as a single binary websocket frame on the given Kubernetes stream $channel, with the channel number prepended as the frame's first byte (chr($channel) . $payload). $channel is required and must be an integer between 0 and 255; $payload defaults to the empty string.
write_channel itself does not know what any channel number means, it only frames whatever it is given; the meaning comes from the Kubernetes sub-protocol of the session.
For an exec or attach session, 0 is stdin, 1 is stdout, 2 is stderr, 3 carries an error/status JSON document, and 4 carries TTY resize events (see "resize").
A port-forward session has a different layout: each requested port $i (counted from 0, in the order of ports) owns two channels, 2 * $i for data and 2 * $i + 1 for errors, so with two ports channel 3 is the error channel of the second one. The first frame the server sends on each of these channels is the port number as two little-endian bytes, not payload. On a port-forward session prefer "write_port", which finds the channel for a port; write_channel stays the raw primitive.
on_frame, passed to "exec" in Net::Async::Kubernetes and friends, splits incoming frames into ($channel, $payload) the same way in reverse; it does not interpret the channel number and, on a port-forward session, does not strip the preamble (on_data and on_port_error of "port_forward" in Net::Async::Kubernetes do).
Returns the Future from the underlying ws_client's send_binary_frame - an IO::Async::Stream write-future that completes with no value once the frame has been flushed to the connection.
Aliased as write.
write_stdin
$session->write_stdin("id\n");
Shortcut for $session->write_channel(0, $payload) - writes to channel 0, the stdin stream of an exec or attach session's remote process. It croaks on a port-forward session, where channel 0 is the data channel of the first port, not stdin; use "write_port" there.
Returns the Future from the underlying write_channel call.
Aliased as stdin.
resize
$session->resize(width => 120, height => 40);
$session->resize(cols => 120, rows => 40); # cols/rows are accepted too
Send a TTY resize event on channel 4, as a {"Width":$width,"Height":$height} JSON payload. width and height (or their cols/rows aliases) are required and must be positive integers. Only meaningful for a session opened with tty => 1. It croaks on a port-forward session, where channel 4 is the data channel of the third port.
Returns the Future from the underlying write_channel call.
close
$session->close(code => 1000, payload => 'bye');
$session->close; # close frame with no code
Send a websocket close frame, ending the session. code, if given, must be a websocket close code between 1000 and 4999; it is packed as a 16-bit big-endian prefix (pack('n', $code)) ahead of payload, which defaults to the empty string. Once the close frame is sent, the underlying transport is also asked to close the connection once its write buffer drains (close_when_empty), if the transport supports that method.
Returns the Future from the underlying ws_client's send_close_frame - an IO::Async::Stream write-future that completes with no value once the close frame has been flushed to the connection.
SEE ALSO
Net::Async::Kubernetes, Net::Async::WebSocket::Client, IO::Async::Stream
SUPPORT
Issues
Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-net-async-kubernetes/issues.
IRC
Join #kubernetes on irc.perl.org or message Getty directly.
CONTRIBUTING
Contributions are welcome! Please fork the repository and submit a pull request.
AUTHOR
Torsten Raudssus <torsten@raudssus.de>
COPYRIGHT AND LICENSE
This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.