Hypersonic

 view release on metacpan or  search on metacpan

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

      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate HTTP/2 response sender
sub gen_response_sender {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2: Send static response')
      ->line('static void h2_send_static_response(nghttp2_session* session,')
      ->line('                                     int32_t stream_id,')
      ->line('                                     const char* http1_resp, int resp_len) {')
      ->line('    /* Parse HTTP/1.1 response to extract status and body */')
      ->line('    /* Format: HTTP/1.1 200 OK\\r\\nContent-Type: ...\\r\\n\\r\\nbody */')
      ->line('    ')
      ->line('    /* Find status code */')
      ->line('    int status = 200;')
      ->line('    if (resp_len > 12 && memcmp(http1_resp, "HTTP/1.1 ", 9) == 0) {')
      ->line('        status = (http1_resp[9] - \'0\') * 100 + ')
      ->line('                 (http1_resp[10] - \'0\') * 10 + ')
      ->line('                 (http1_resp[11] - \'0\');')
      ->line('    }')
      ->line('    ')
      ->line('    /* Find body (after \\r\\n\\r\\n) */')
      ->line('    const char* body = strstr(http1_resp, "\\r\\n\\r\\n");')
      ->line('    int body_len = 0;')
      ->line('    if (body) {')
      ->line('        body += 4;')
      ->line('        body_len = resp_len - (body - http1_resp);')
      ->line('    } else {')
      ->line('        body = "";')
      ->line('    }')
      ->line('    ')
      ->line('    /* Find Content-Type */')
      ->line('    const char* ct = "text/plain";')
      ->line('    const char* ct_hdr = strstr(http1_resp, "Content-Type: ");')
      ->line('    static char ct_buf[64];')
      ->line('    if (ct_hdr) {')
      ->line('        ct_hdr += 14;')
      ->line('        const char* ct_end = strstr(ct_hdr, "\\r\\n");')
      ->line('        if (ct_end && ct_end - ct_hdr < 63) {')
      ->line('            memcpy(ct_buf, ct_hdr, ct_end - ct_hdr);')
      ->line('            ct_buf[ct_end - ct_hdr] = \'\\0\';')
      ->line('            ct = ct_buf;')
      ->line('        }')
      ->line('    }')
      ->line('    ')
      ->line('    /* Build content-length string */')
      ->line('    char cl_buf[16];')
      ->line('    snprintf(cl_buf, sizeof(cl_buf), "%d", body_len);')
      ->line('    ')
      ->line('    /* Build status string */')
      ->line('    char status_buf[4];')
      ->line('    snprintf(status_buf, sizeof(status_buf), "%d", status);')
      ->line('    ')
      ->line('    /* Submit HTTP/2 response headers */')
      ->line('    nghttp2_nv hdrs[] = {')
      ->line('        { (uint8_t*)":status", (uint8_t*)status_buf, 7, strlen(status_buf), NGHTTP2_NV_FLAG_NONE },')
      ->line('        { (uint8_t*)"content-type", (uint8_t*)ct, 12, strlen(ct), NGHTTP2_NV_FLAG_NONE },')
      ->line('        { (uint8_t*)"content-length", (uint8_t*)cl_buf, 14, strlen(cl_buf), NGHTTP2_NV_FLAG_NONE },')
      ->line('    };')
      ->line('    ')
      ->line('    /* Create data provider for body */')
      ->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
      ->comment('HTTP/2: Data source read callback for response body')
      ->line('static ssize_t h2_data_source_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('    const char* body = (const char*)source->ptr;')
      ->line('    size_t body_len = strlen(body);')
      ->line('    ')
      ->line('    if (body_len == 0) {')
      ->line('        *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
      ->line('        return 0;')
      ->line('    }')
      ->line('    ')
      ->line('    size_t copy_len = (length < body_len) ? length : body_len;')
      ->line('    memcpy(buf, body, copy_len);')
      ->line('    ')
      ->line('    /* Update source pointer for next read */')
      ->line('    source->ptr = (void*)(body + copy_len);')
      ->line('    ')
      ->line('    if (copy_len == body_len) {')
      ->line('        *data_flags |= NGHTTP2_DATA_FLAG_EOF;')
      ->line('    }')
      ->line('    ')
      ->line('    return copy_len;')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate 404 response for HTTP/2
sub gen_404_response {
    my ($class, $builder) = @_;
    
    $builder->comment('HTTP/2: Send 404 response')
      ->line('static void h2_send_404(nghttp2_session* session, int32_t stream_id) {')
      ->line('    static const char* body = "Not Found";')
      ->line('    ')
      ->line('    nghttp2_nv hdrs[] = {')
      ->line('        { (uint8_t*)":status", (uint8_t*)"404", 7, 3, NGHTTP2_NV_FLAG_NONE },')
      ->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);')



( run in 2.169 seconds using v1.01-cache-2.11-cpan-524268b4103 )