Hypersonic

 view release on metacpan or  search on metacpan

lib/Hypersonic/Protocol/HTTP2.pm  view on Meta::CPAN

      ->line('        { (uint8_t*)"content-type", (uint8_t*)"text/plain", 12, 10, NGHTTP2_NV_FLAG_NONE },')
      ->line('        { (uint8_t*)"content-length", (uint8_t*)"9", 14, 1, NGHTTP2_NV_FLAG_NONE },')
      ->line('    };')
      ->line('    ')
      ->line('    nghttp2_data_provider data_prd;')
      ->line('    data_prd.source.ptr = (void*)body;')
      ->line('    data_prd.read_callback = h2_data_source_read_cb;')
      ->line('    ')
      ->line('    nghttp2_submit_response(session, stream_id, hdrs, 3, &data_prd);')
      ->line('    nghttp2_session_send(session);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate HTTP/2 input processing
sub gen_input_processor {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2: Process incoming data from socket')
      ->line('static int h2_process_input(H2Connection* conn, const uint8_t* data, size_t len) {')
      ->line('    ssize_t rv = nghttp2_session_mem_recv(conn->session, data, len);')
      ->line('    if (rv < 0) {')
      ->line('        return -1;  /* Protocol error */')
      ->line('    }')
      ->line('    ')
      ->line('    /* Send any pending frames */')
      ->line('    rv = nghttp2_session_send(conn->session);')
      ->line('    if (rv != 0) {')
      ->line('        return -1;')
      ->line('    }')
      ->line('    ')
      ->line('    return 0;')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate HTTP/2 connection detection (for h2c upgrade or ALPN)
sub gen_connection_preface_check {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2: Check for connection preface (h2c)')
      ->line('static const char H2_PREFACE[] = "PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n";')
      ->line('#define H2_PREFACE_LEN 24')
      ->blank
      ->line('static int is_h2_preface(const char* data, size_t len) {')
      ->line('    return len >= H2_PREFACE_LEN && memcmp(data, H2_PREFACE, H2_PREFACE_LEN) == 0;')
      ->line('}')
      ->blank;
    
    return $builder;
}

# ============================================================
# HTTP/2 Streaming Support (Phase 3)
# ============================================================

# Generate streaming headers without END_STREAM
sub gen_stream_headers {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2 Streaming: Send HEADERS without END_STREAM (allows more DATA)')
      ->line('static int h2_stream_headers(nghttp2_session* session, int32_t stream_id,')
      ->line('                              int status, const char* content_type) {')
      ->line('    char status_str[4];')
      ->line('    snprintf(status_str, sizeof(status_str), "%d", status);')
      ->line('    ')
      ->line('    nghttp2_nv hdrs[] = {')
      ->line('        { (uint8_t*)":status", (uint8_t*)status_str, 7, strlen(status_str), NGHTTP2_NV_FLAG_NONE },')
      ->line('        { (uint8_t*)"content-type", (uint8_t*)content_type, 12, strlen(content_type), NGHTTP2_NV_FLAG_NONE },')
      ->line('    };')
      ->line('    ')
      ->line('    /* Submit headers WITHOUT END_STREAM - more DATA frames to come */')
      ->line('    int rv = nghttp2_submit_headers(session, NGHTTP2_FLAG_END_HEADERS,')
      ->line('                                     stream_id, NULL, hdrs, 2, NULL);')
      ->line('    if (rv < 0) return rv;')
      ->line('    ')
      ->line('    return nghttp2_session_send(session);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate streaming data chunk sender
sub gen_stream_data {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2 Streaming: Chunk provider for streaming DATA frames')
      ->line('typedef struct {')
      ->line('    const uint8_t* data;')
      ->line('    size_t length;')
      ->line('    size_t pos;')
      ->line('} H2ChunkProvider;')
      ->blank
      ->line('static ssize_t h2_chunk_read_cb(nghttp2_session* session,')
      ->line('                                 int32_t stream_id,')
      ->line('                                 uint8_t* buf, size_t length,')
      ->line('                                 uint32_t* data_flags,')
      ->line('                                 nghttp2_data_source* source,')
      ->line('                                 void* user_data) {')
      ->line('    (void)session; (void)stream_id; (void)user_data;')
      ->line('    H2ChunkProvider* provider = (H2ChunkProvider*)source->ptr;')
      ->line('    size_t remaining = provider->length - provider->pos;')
      ->line('    size_t to_copy = remaining < length ? remaining : length;')
      ->line('    ')
      ->line('    memcpy(buf, provider->data + provider->pos, to_copy);')
      ->line('    provider->pos += to_copy;')
      ->line('    ')
      ->line('    if (provider->pos >= provider->length) {')
      ->line('        /* This DATA frame is complete (but not end of stream) */')
      ->line('        *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
      ->line('    }')
      ->line('    ')
      ->line('    return (ssize_t)to_copy;')
      ->line('}')
      ->blank
      ->comment('HTTP/2 Streaming: Send a single DATA frame (not final)')
      ->line('static int h2_stream_data(nghttp2_session* session, int32_t stream_id,')
      ->line('                           const uint8_t* data, size_t len) {')
      ->line('    /* Allocate provider on stack - nghttp2 copies data synchronously */')
      ->line('    H2ChunkProvider provider = { data, len, 0 };')
      ->line('    ')
      ->line('    nghttp2_data_provider data_prd;')
      ->line('    data_prd.source.ptr = &provider;')
      ->line('    data_prd.read_callback = h2_chunk_read_cb;')
      ->line('    ')
      ->line('    /* Submit DATA without END_STREAM */')
      ->line('    int rv = nghttp2_submit_data(session, NGHTTP2_FLAG_NONE,')
      ->line('                                  stream_id, &data_prd);')
      ->line('    if (rv < 0) return rv;')
      ->line('    ')
      ->line('    return nghttp2_session_send(session);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate stream end (empty DATA with END_STREAM)
sub gen_stream_end {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2 Streaming: Send empty DATA with END_STREAM flag')
      ->line('static int h2_stream_end(nghttp2_session* session, int32_t stream_id) {')
      ->line('    /* Submit empty data with END_STREAM flag */')
      ->line('    nghttp2_data_provider data_prd;')
      ->line('    data_prd.source.ptr = NULL;')
      ->line('    data_prd.read_callback = NULL;')
      ->line('    ')
      ->line('    int rv = nghttp2_submit_data(session, NGHTTP2_FLAG_END_STREAM,')
      ->line('                                  stream_id, NULL);')
      ->line('    if (rv < 0) return rv;')
      ->line('    ')
      ->line('    return nghttp2_session_send(session);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate flow control helpers
sub gen_flow_control {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2 Streaming: Check flow control window')
      ->line('static int h2_can_send(nghttp2_session* session, int32_t stream_id, size_t len) {')
      ->line('    /* Check connection-level window */')
      ->line('    int32_t conn_window = nghttp2_session_get_remote_window_size(session);')
      ->line('    if (conn_window < (int32_t)len) return 0;')
      ->line('    ')
      ->line('    /* Check stream-level window */')
      ->line('    int32_t stream_window = nghttp2_session_get_stream_remote_window_size(')
      ->line('        session, stream_id);')
      ->line('    if (stream_window < (int32_t)len) return 0;')
      ->line('    ')
      ->line('    return 1;')
      ->line('}')
      ->blank
      ->comment('HTTP/2 Streaming: Get available window size')
      ->line('static int32_t h2_window_size(nghttp2_session* session, int32_t stream_id) {')
      ->line('    int32_t conn_window = nghttp2_session_get_remote_window_size(session);')
      ->line('    int32_t stream_window = nghttp2_session_get_stream_remote_window_size(')
      ->line('        session, stream_id);')
      ->line('    return conn_window < stream_window ? conn_window : stream_window;')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate XS wrappers for HTTP/2 streaming from Perl
sub gen_stream_xs_wrappers {
    my ($class, $builder) = @_;
    
    $builder->comment('XS wrappers for HTTP/2 streaming from Perl');
    
    # h2_stream_start(session_ptr, stream_id, status, content_type)
    $builder->xs_function('hypersonic_h2_stream_start')
      ->xs_preamble
      ->check_items(4, 4, 'session_ptr, stream_id, status, content_type')
      ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
      ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
      ->line('int status = (int)SvIV(ST(2));')
      ->line('STRLEN ct_len;')
      ->line('const char* content_type = SvPV(ST(3), ct_len);')
      ->line('int rv = h2_stream_headers(session, stream_id, status, content_type);')
      ->line('XSRETURN_IV(rv);')
      ->xs_end
      ->blank;
    
    # h2_stream_write(session_ptr, stream_id, data)
    $builder->xs_function('hypersonic_h2_stream_write')
      ->xs_preamble
      ->check_items(3, 3, 'session_ptr, stream_id, data')
      ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
      ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
      ->line('STRLEN data_len;')
      ->line('const char* data = SvPV(ST(2), data_len);')
      ->line('int rv = h2_stream_data(session, stream_id, (const uint8_t*)data, data_len);')
      ->line('XSRETURN_IV(rv);')
      ->xs_end
      ->blank;
    
    # h2_stream_end(session_ptr, stream_id)
    $builder->xs_function('hypersonic_h2_stream_end')
      ->xs_preamble
      ->check_items(2, 2, 'session_ptr, stream_id')
      ->line('nghttp2_session* session = (nghttp2_session*)SvUV(ST(0));')
      ->line('int32_t stream_id = (int32_t)SvIV(ST(1));')
      ->line('int rv = h2_stream_end(session, stream_id);')
      ->line('XSRETURN_IV(rv);')
      ->xs_end
      ->blank;
    
    return $builder;
}

# Generate all HTTP/2 streaming code
sub generate_streaming {
    my ($class, $builder, $opts) = @_;
    
    $class->gen_stream_headers($builder);
    $class->gen_stream_data($builder);
    $class->gen_stream_end($builder);
    $class->gen_flow_control($builder);
    $class->gen_stream_xs_wrappers($builder);
    
    return $builder;
}

1;

__END__

=head1 NAME

Hypersonic::Protocol::HTTP2 - JIT code generation for HTTP/2 protocol

=head1 SYNOPSIS

    use Hypersonic::Protocol::HTTP2;
    
    # Check if nghttp2 is available
    if (Hypersonic::Protocol::HTTP2->check_nghttp2()) {
        my $cflags = Hypersonic::Protocol::HTTP2->get_extra_cflags();
        my $ldflags = Hypersonic::Protocol::HTTP2->get_extra_ldflags();
    }
    
    # Generate HTTP/2 C code
    Hypersonic::Protocol::HTTP2->gen_includes($builder);
    Hypersonic::Protocol::HTTP2->gen_callbacks($builder);

=head1 DESCRIPTION

This module provides JIT compile-time code generation for HTTP/2 protocol
support using the nghttp2 library. All methods generate C code using
XS::JIT::Builder - there is zero runtime overhead.

=head2 HTTP/2 Features

=over 4

=item * Binary framing via nghttp2

=item * HPACK header compression

=item * Stream multiplexing

=item * Server push (future)

=back

=head1 AUTHOR

Hypersonic Contributors

=cut



( run in 0.700 second using v1.01-cache-2.11-cpan-140bd7fdf52 )