Protoc...:Server(3pm)

πŸŽ€ NAME πŸŽ€

okay bestie, gather round, because this is Protocol::HTTP2::Server β€” it's the HTTP/2 server situation, from the User Contributed Perl Documentation, section 3pm. Yes, that's the whole name. Slay.


✨ SYNOPSIS ✨

so first things first, you gotta bring the whole friend group β€” here's the vibe:

use Protocol::HTTP2::Server;

# You must create tcp server yourself
use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::Handle;

my $w = AnyEvent->condvar;

# Plain-text HTTP/2 connection
tcp_server 'localhost', 8000, sub {
    my ( $fh, $peer_host, $peer_port ) = @_;
    my $handle;
    $handle = AnyEvent::Handle->new(
        fh       => $fh,
        autocork => 1,
        on_error => sub {
            $_[0]->destroy;
            print "connection error\n";
        },
        on_eof => sub {
            $handle->destroy;
        }
    );

    # Create Protocol::HTTP2::Server object
    my $server;
    $server = Protocol::HTTP2::Server->new(
        on_request => sub {
            my ( $stream_id, $headers, $data ) = @_;
            my $message = "hello, world!";

            # Response to client
            $server->response(
                ':status' => 200,
                stream_id => $stream_id,

                # HTTP/1.1 Headers
                headers   => [
                    'server'         => 'perl-Protocol-HTTP2/0.13',
                    'content-length' => length($message),
                    'cache-control'  => 'max-age=3600',
                    'date'           => 'Fri, 18 Apr 2014 07:27:11 GMT',
                    'last-modified'  => 'Thu, 27 Feb 2014 10:30:37 GMT',
                ],

                # Content
                data => $message,
            );
        },
    );

    # First send settings to peer
    while ( my $frame = $server->next_frame ) {
        $handle->push_write($frame);
    }

    # Receive clients frames
    # Reply to client
    $handle->on_read(
        sub {
            my $handle = shift;

            $server->feed( $handle->{rbuf} );

            $handle->{rbuf} = undef;
            while ( my $frame = $server->next_frame ) {
                $handle->push_write($frame);
            }
            $handle->push_shutdown if $server->shutdown;
        }
    );
};

$w->recv;
🌸 heads up bestie: you have to create the tcp server yourself. the module is not gonna do it for you, it's not your mom.

πŸ’œ DESCRIPTION πŸ’œ

Protocol::HTTP2::Server is an HTTP/2 server library. Its whole thing β€” its main character energy, if you will β€” is letting you build http2-server implementations on top of whatever event loop you're obsessed with. Bring your own loop, she brings the protocol. Periodt.

also, side quest: go look at Shuvgey β€” https://github.com/vlet/Shuvgey β€” it's an AnyEvent HTTP/2 Server for PSGI built on top of Protocol::HTTP2::Server. iconic behavior fr fr πŸ¦‹

🌈 METHODS 🌈

⭐ new

Initialize a brand new server object. Baby's first server.

my $server = Protocol::HTTP2::Client->new( %options );
πŸ’€ okay so it literally says Protocol::HTTP2::Client right there in the docs. Not a typo on my part, bestie β€” that's what's written. Copy it as-is and don't @ me.

Available options, aka the little menu of customization:

  • on_request => sub {...} β€” this is the callback that fires whenever a client's request comes in. Stream ID, headers array ref, and the body of the request all get handed to you. Look at her go:
on_request => sub {
    # Stream ID, headers array reference and body of request
    my ( $stream_id, $headers, $data ) = @_;

    my $message = "hello, world!";
    $server->response(
        ':status' => 200,
        stream_id => $stream_id,
        headers   => [
            'server'         => 'perl-Protocol-HTTP2/0.13',
            'content-length' => length($message),
        ],
        data => $message,
    );
    ...
},
  • upgrade => 0|1 β€” uses HTTP/1.1 Upgrade to level up the protocol from HTTP/1.1 to HTTP/2. Upgrade is only possible on a plain (non-tls) connection, so don't try it with the fancy stuff on.

See Starting HTTP/2 for "http" URIs β€” https://tools.ietf.org/html/rfc7540#section-3.2

  • on_error => sub {...} β€” callback that gets invoked whenever protocol errors happen. Your "oop" handler.
on_error => sub {
    my $error = shift;
    ...
},
  • on_change_state => sub {...} β€” callback invoked every single time an http/2 stream changes its state. Like a read receipt for stream states.

See Stream States β€” https://tools.ietf.org/html/rfc7540#section-5.1

on_change_state => sub {
    my ( $stream_id, $previous_state, $current_state ) = @_;
    ...
},

πŸ’– response

Prepare a response. Getting your reply ready to send out into the world.

my $message = "hello, world!";
$server->response(

    # HTTP/2 status
    ':status' => 200,

    # Stream ID
    stream_id => $stream_id,

    # HTTP/1.1 headers
    headers   => [
        'server'         => 'perl-Protocol-HTTP2/0.01',
        'content-length' => length($message),
    ],

    # Body of response
    data => $message,
);

πŸ¦‹ response_stream

Use this one when the body of the response isn't ready yet, or when the server is gonna stream that data out piece by piece. Slow reveal, very dramatic. πŸ’…

# P::H::Server::Stream object
my $server_stream;
$server_stream = $server->response_stream(

    # HTTP/2 status
    ':status' => 200,

    # Stream ID
    stream_id => $stream_id,

    # HTTP/1.1 headers
    headers   => [
        'server'         => 'perl-Protocol-HTTP2/0.01',
    ],

    # Callback if client abort this stream
    on_cancel => sub {
        ...
    }
);

# Send partial data
$server_stream->send($chunk_of_data);
$server_stream->send($chunk_of_data);

## 3 ways to finish stream:
#
# The best: send last chunk and close stream in one action
$server_stream->last($chunk_of_data);

# Close the stream (will send empty frame)
$server_stream->close();

# Destroy object (will send empty frame)
undef $server_stream

πŸ‘‘ push

Prepare a Push Promise. See Server Push β€” https://tools.ietf.org/html/rfc7540#section-8.2

# Example of push inside of on_request callback
on_request => sub {
    my ( $stream_id, $headers, $data ) = @_;
    my %h = (@$headers);

    # Push promise (must be before response)
    if ( $h{':path'} eq '/index.html' ) {

        # index.html contain styles.css resource, so server can push
        # "/style.css" to client before it request it to increase speed
        # of loading of whole page
        $server->push(
            ':authority' => 'localhost:8000',
            ':method'    => 'GET',
            ':path'      => '/style.css',
            ':scheme'    => 'http',
            stream_id    => $stream_id,
        );
    }

    $server->response(...);
    ...
}
🫢 not me telling you the push promise must be before response. That's not a suggestion, that's the rules.

🌸 shutdown

Get the connection status, bestie:

  • 0 β€” active
  • 1 β€” closed (you can terminate connection)

πŸ“ next_frame

get the next frame to send over the connection to the client. Returns:

  • undef β€” on error
  • 0 β€” nothing to send
  • binary string β€” encoded frame
# Example
while ( my $frame = $server->next_frame ) {
    syswrite $fh, $frame;
}

🧚 feed

Feed the decoder with chunks of the client's request. Snack time for the decoder. 🍽️

sysread $fh, $binary_data, 4096;
$server->feed($binary_data);

πŸ’… ping

Send a ping frame to the client, aka the "hey bestie u still there??" of keep-alive:

$server->ping

or

$server->ping($payload);

The payload can be an arbitrary binary string, and it must contain 8 octets. If you leave the payload argument out, the server will just send random data. Surprise! πŸŽ‰


and that's the whole page, bestie β€” go build something iconic with it. πŸ’–βœ¨

this whole lil' document came from perl v5.42.3, dated 2026-09-04, and it lives in the Protoc...:Server(3pm) neighborhood of your man pages. mwah. πŸŽ€

πŸ“¦ where this page comes from