Plack-Handler-H2
view release on metacpan or search on metacpan
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)
--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->([
# 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 0.771 second using v1.01-cache-2.11-cpan-0b5f733616e )