NAME

Kubernetes::REST::Role::IO - Interface role for HTTP backends

VERSION

version 1.110

SYNOPSIS

package My::AsyncIO;
use Moo;
with 'Kubernetes::REST::Role::IO';

sub call {
    my ($self, $req) = @_;
    # Execute HTTP request, return Kubernetes::REST::HTTPResponse
    ...
}

sub call_streaming {
    my ($self, $req, $data_callback) = @_;
    # Execute HTTP request with streaming callback
    ...
}

# Optional: full-duplex transport (WebSocket/SPDY)
sub call_duplex {
    my ($self, $req, %callbacks) = @_;
    ...
}

DESCRIPTION

This role defines the interface that HTTP backends must implement. Kubernetes::REST delegates all HTTP communication through this interface, making it possible to swap out the transport layer.

The default backend is Kubernetes::REST::LWPIO (using LWP::UserAgent). An alternative Kubernetes::REST::HTTPTinyIO (using HTTP::Tiny) is provided. To use an async event loop, implement this role with e.g. Net::Async::HTTP.

This is not how Net::Async::Kubernetes, this distribution's own async client, integrates: it does not consume this role or call call/call_streaming at all. Instead it drives Kubernetes::REST's request pipeline directly, through the published build_path, prepare_request, check_response, inflate_object, inflate_list, process_watch_chunk and process_log_chunk methods, running its own HTTP transport underneath them. Two more published methods, prepare_discovery_requests and absorb_discovery, let such a client read the cluster's discovery through its own transport too, instead of the synchronous "call" that name resolution otherwise uses for it (see "BUILDING BLOCKS FOR ASYNC WRAPPERS" in Kubernetes::REST).

Both shipped backends are synchronous, request/response-only transports: neither implements call_duplex (see "supports_duplex" below), so Kubernetes::REST methods that need full-duplex transport (port_forward, exec, attach) croak against them by design. A backend that wants to support those needs to implement call_duplex($req, %callbacks) itself, as sketched in the "SYNOPSIS".

Encoding contract

Request and response bodies are bytes, never character strings - this holds for call and call_streaming alike, whatever kind of request they carry.

A backend receives $req->content already UTF-8 encoded (the core client's JSON encoder runs with utf8 => 1) and must put it on the wire unchanged. It must hand back $res->content - and every streaming chunk - as the bytes it received, undoing Content-Encoding (gzip) but not the charset.

Two different callers rely on this, for two different reasons:

  • For get/list/watch, Kubernetes::REST decodes UTF-8 itself once the JSON is parsed, together with IO::K8s. A backend that decodes the charset first causes silent double decoding (mojibake) on any non-ASCII value.

  • For log (streamed via call_streaming just like watch), the bytes are handed straight back to the caller undecoded, on purpose: container output is not guaranteed to be UTF-8, or even text, so there is no safe charset for the backend to assume. See "log" in Kubernetes::REST.

With LWP::UserAgent that means $res->decoded_content(charset => 'none') rather than $res->decoded_content. See also "ENCODING" in Kubernetes::REST for the full picture from the caller's side of this module.

Error contract

call and call_streaming tell three kinds of failure apart, and a backend must keep them apart. A status code is the only thing Kubernetes::REST reads to do so.

  • A transport failure is a response with status 599. When no answer from the API server exists, the backend returns a Kubernetes::REST::HTTPResponse with status 599 and the error message as content; it does not die. That covers a refused or failed connect, a TLS error, a timeout, an unsupported URL scheme, a missing protocol module (LWP::Protocol::https not installed) and a transfer that breaks off in the middle of the body, in call and call_streaming alike. A backend whose library reports such a failure as an ordinary-looking response, or as a truncated 200, must map it to 599 itself.

  • Every other status is the API server's answer and is returned unchanged, 500 and 502 included. The backend does not retry it, rewrite it or turn it into an exception. Only the status values from the HTTP library's own internal failure reporting are the backend's to translate.

  • An exception thrown by the streaming callback is not a transport failure. If $data_callback dies, call_streaming aborts the transfer and dies with that same exception, a string or an object, untouched. It returns no response, and in particular no 599. A library that catches the exception and reports it as an error status (HTTP::Tiny turns it into a 599) must have it captured first and rethrown. The callback is not called again after it has thrown. This is how an exception from the on_event or on_line handler of "watch" in Kubernetes::REST and "log" in Kubernetes::REST reaches the caller of those methods.

Kubernetes::REST treats the 599 like any other status of 400 and up: its response check dies with a Kubernetes::REST::APIError whose code is 599 and whose body is the message the backend put in content. A caller tells a transport failure from an answer of the server by that code.

call

my $response = $io->call($req);

Required. Execute an HTTP request. Receives a Kubernetes::REST::HTTPRequest with method, url, headers, and optionally content already set.

Must return a Kubernetes::REST::HTTPResponse with status and content, the latter as bytes - see "Encoding contract". A transport failure (no answer from the API server) is returned as status 599 with the error message as content, never thrown; every other status is the server's and stays as it is - see "Error contract".

call_streaming

my $response = $io->call_streaming($req, $data_callback);

Required. Execute an HTTP request with streaming response. The $data_callback is called with each chunk of data as it arrives: $data_callback->($chunk). Chunks are bytes - see "Encoding contract".

Must return a Kubernetes::REST::HTTPResponse when the stream ends. A transport failure, including a break in the middle of the body, is returned as status 599 with the error message as content, as in call.

If $data_callback dies, the backend aborts the transfer and dies with the same exception, unchanged and without a response; that is not a 599. See "Error contract".

supports_duplex

if ($io->supports_duplex) {
    ...
}

Optional capability probe for full-duplex protocols used by Kubernetes subresources such as pod port-forward and exec/attach streams.

Returns true if the backend implements call_duplex, false otherwise.

SEE ALSO

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/pplu/kubernetes-rest/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.

AUTHORS

  • Torsten Raudssus <getty@cpan.org>

  • Jose Luis Martinez Torres <jlmartin@cpan.org>

COPYRIGHT AND LICENSE

This software is Copyright (c) 2019-2026 by Jose Luis Martinez Torres <jlmartin@cpan.org>.

This is free software, licensed under:

The Apache License, Version 2.0, January 2004