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 )