Plack-Handler-H2

 view release on metacpan or  search on metacpan

MANIFEST  view on Meta::CPAN

LICENSE
Makefile.PL
MANIFEST			This list of files
MANIFEST.SKIP
README.md
t/00-load.t
t/01-writer.t
t/02-handler.t
t/03-integration.t
t/04-responder.t
t/05-streaming.t
META.yml                                 Module YAML meta-data (added by MakeMaker)
META.json                                Module JSON meta-data (added by MakeMaker)

README.md  view on Meta::CPAN

    --ssl-key-file=/path/to/server.key \
    --port=8443 \
    app.psgi

# Development mode (auto-generates self-signed certificate)
plackup -s H2 --port=8443 app.psgi
```

### Streaming Response Example

Create a streaming PSGI application (`streaming.psgi`):

```perl
my $app = sub {
    my $env = shift;
    
    return sub {
        my $responder = shift;
        
        # Send headers and get writer
        my $writer = $responder->([

README.md  view on Meta::CPAN

        
        # Close the stream
        $writer->close();
    };
};
```

Run with plackup:

```bash
plackup -s H2 --port=8443 streaming.psgi
```

### Configuration Options

When using plackup, you can pass options via command-line flags:

- **--ssl-cert-file** (optional): Path to SSL certificate file (PEM format)
  - If not provided, generates self-signed certificate automatically
- **--ssl-key-file** (optional): Path to SSL private key file (PEM format)
  - Required if --ssl-cert-file is provided

lib/Plack/Handler/H2.pm  view on Meta::CPAN

stream multiplexing, server push capabilities, and flow control.

=item * B<TLS/SSL Required>

Secure connections with OpenSSL, including ALPN (Application-Layer Protocol 
Negotiation). Supports OpenSSL 1.1.1+ and 3.0+. Automatically generates 
self-signed certificates for development.

=item * B<Streaming Responses>

Full support for PSGI streaming and delayed responses, including chunked 
transfer without Content-Length, progressive rendering, and server-sent 
events compatibility.

=item * B<Asynchronous I/O>

Event-driven architecture using libevent2 with non-blocking request handling, 
concurrent stream processing, and efficient memory management for large request 
bodies.

=item * B<High Performance>

lib/Plack/Handler/H2.pm  view on Meta::CPAN

B<Parameters:>

=over 4

=item * C<$app> - A PSGI application code reference

=back

=head1 STREAMING RESPONSES

Plack::Handler::H2 fully supports PSGI streaming responses using the delayed 
response pattern. This is useful for:

=over 4

=item * Large responses that don't fit in memory

=item * Server-sent events

=item * Progressive rendering

lib/Plack/Handler/H2.pm  view on Meta::CPAN


=head1 ARCHITECTURE

The module consists of three main layers:

=over 4

=item 1. B<H2.pm> - High-level Perl interface

PSGI handler implementation, configuration management, self-signed certificate 
generation, and streaming response coordination.

=item 2. B<H2.xs> - XS bindings layer

Efficient Perl-to-C++ interface, type conversion and marshalling, and function 
wrappers for core operations.

=item 3. B<plack_handler_h2.cc/h> - Core C++ implementation

nghttp2 integration for HTTP/2 protocol, libevent event loop for async I/O, 
OpenSSL for TLS/SSL and ALPN, request/response handling, stream multiplexing, 

lib/Plack/Handler/H2/Writer.pm  view on Meta::CPAN

1;

__END__

=head1 NAME

Plack::Handler::H2::Writer - Streaming response writer for HTTP/2

=head1 SYNOPSIS

    # In your PSGI application using delayed/streaming responses
    my $app = sub {
        my $env = shift;
        
        return sub {
            my $responder = shift;
            
            # Send headers first
            my $writer = $responder->([
                200,
                ['Content-Type' => 'text/plain']

lib/Plack/Handler/H2/Writer.pm  view on Meta::CPAN

            $writer->write("Second chunk\n");
            $writer->write("Third chunk\n");
            
            # Close the stream
            $writer->close();
        };
    };

=head1 DESCRIPTION

C<Plack::Handler::H2::Writer> provides a streaming interface for sending HTTP/2 
response bodies in chunks. This is used internally by L<Plack::Handler::H2> to 
implement PSGI's streaming response protocol.

When your PSGI application returns a code reference (delayed response), it 
receives a responder callback. Calling this responder with a status and headers 
returns a writer object that allows you to send the response body in multiple 
chunks, which is particularly useful for:

=over 4

=item * Large responses that don't fit in memory

lib/Plack/Handler/H2/Writer.pm  view on Meta::CPAN

After calling C<close()>, you should not call C<write()> again on the same 
writer object.

B<Example:>

    $writer->write("Final data");
    $writer->close();

=head1 HTTP/2 SPECIFICS

This writer integrates with HTTP/2's streaming model:

=over 4

=item * Each C<write()> call sends data with the HTTP/2 DATA frame

=item * The C<close()> method sends an END_STREAM flag

=item * Content-Length headers are automatically omitted for streaming responses

=item * Backpressure is handled by the HTTP/2 flow control mechanism

=back

=head1 SEE ALSO

L<Plack::Handler::H2>, L<PSGI>, L<Plack>

=head1 AUTHOR

lib/Plack/Handler/plack_handler_h2.cc  view on Meta::CPAN

    warn("Session pointer is null");
    return nullptr;
  }

  return reinterpret_cast<H2Session *>(session_ptr);
}

static inline int32_t send_headers_from_av(H2Session *session,
                                           int32_t stream_id, SV *status_sv,
                                           SV *headers_sv,
                                           bool streaming = false) {
  int status = SvIV(status_sv);
  AV *headers_av =
      (headers_sv && SvROK(headers_sv)) ? (AV *)SvRV(headers_sv) : nullptr;

  auto data_it = std::find_if(
      session->data.begin(), session->data.end(),
      [stream_id](H2Data *d) { return d->stream_id == stream_id; });
  if (data_it == session->data.end()) {
    croak("Stream data not found for stream_id %d", stream_id);
  }

lib/Plack/Handler/plack_handler_h2.cc  view on Meta::CPAN


      if (name_sv && value_sv) {
        size_t name_len, value_len;
        const char *name = SvPV(*name_sv, name_len);
        const char *value = SvPV(*value_sv, value_len);

        std::string lowercase_name(name, name_len);
        std::transform(lowercase_name.begin(), lowercase_name.end(),
                       lowercase_name.begin(), ::tolower);

        // Skip content-length for streaming responses (when with_end_stream is
        // false) HTTP/2 doesn't need content-length for streaming
        if (streaming && lowercase_name == "content-length") {
          continue;
        }

        header_storage.push_back(lowercase_name);
        header_storage.push_back(std::string(value, value_len));

        nghttp2_nv header = {
            (uint8_t *)header_storage[header_storage.size() - 2].c_str(),
            (uint8_t *)header_storage[header_storage.size() - 1].c_str(),
            lowercase_name.length(), value_len, NGHTTP2_NV_FLAG_NONE};

lib/Plack/Handler/plack_handler_h2.cc  view on Meta::CPAN


  AV *version = newAV();
  av_store(version, 0, newSViv(1));
  av_store(version, 1, newSViv(1));
  hv_stores(env, "psgi.version", newRV_noinc((SV *)version));
  hv_stores(env, "psgi.url_scheme", newSVpvs("https"));
  hv_stores(env, "psgi.errors", newRV_inc((SV *)PL_stderrgv));
  hv_stores(env, "psgi.multithread", &PL_sv_no);
  hv_stores(env, "psgi.multiprocess", &PL_sv_yes);
  hv_stores(env, "psgi.run_once", &PL_sv_no);
  hv_stores(env, "psgi.streaming", &PL_sv_yes);
  hv_stores(env, "psgi.nonblocking", &PL_sv_no);
  hv_stores(env, "psgix.h2.stream_id", newSViv(data->stream_id));

  SV *input_sv = nullptr;
  if (data->body_fd != -1) {
    lseek(data->body_fd, 0, SEEK_SET); // Rewind to start
    PerlIO *pio = PerlIO_fdopen(data->body_fd, "r");
    if (pio) {
      GV *gv = newGVgen("Plack::Handler::H2");
      IO *io = GvIOn(gv);

t/04-responder.t  view on Meta::CPAN

    use warnings 'redefine';
    
    my $responder = Plack::Handler::H2::_responder($env, $session);
    
    my $response = [
        200,
        ['Content-Type' => 'text/plain']
    ];
    
    my $result = $responder->($response);
    isa_ok($result, 'Plack::Handler::H2::Writer', 'Responder returns Writer for streaming response');
    
    $result->write('chunk1');
    is(scalar(@$writer_calls), 1, 'Writer callback called once');
    is($writer_calls->[0]->{end_stream}, 0, 'write() sets end_stream=0');
    is($writer_calls->[0]->{data}, 'chunk1', 'write() passes correct data');
    
    $result->write('chunk2');
    is(scalar(@$writer_calls), 2, 'Writer callback called twice');
    is($writer_calls->[1]->{data}, 'chunk2', 'Second write() passes correct data');
    



( run in 1.280 second using v1.01-cache-2.11-cpan-f4a522933cf )