Hypersonic
view release on metacpan or search on metacpan
lib/Hypersonic/Protocol/HTTP1.pm view on Meta::CPAN
package Hypersonic::Protocol::HTTP1;
use strict;
use warnings;
# Hypersonic::Protocol::HTTP1 - JIT code generation for HTTP/1.1 protocol
#
# This module provides compile-time code generation methods for HTTP/1.1
# protocol handling. All methods return C code strings or use XS::JIT::Builder
# to generate code. There is NO runtime overhead - everything is JIT compiled.
#
# HTTP/1.1 specific features handled here:
# - Text-based request parsing (GET /path HTTP/1.1\r\n)
# - CRLF delimiters (\r\n\r\n header/body separator)
# - Connection: keep-alive/close header
# - Response format (HTTP/1.1 200 OK\r\nHeader: Value\r\n\r\nBody)
our $VERSION = '0.12';
# Protocol identifier - used for version negotiation
sub protocol_id { 'HTTP/1.1' }
# HTTP version string for responses
sub version_string { 'HTTP/1.1' }
# Generate a complete HTTP/1.1 response at compile time
# Returns the full response string with headers and body
sub build_response {
my ($class, %args) = @_;
my $status = $args{status} // 200;
my $status_text = $args{status_text} // _status_text($status);
my $headers = $args{headers} // {};
my $body = $args{body} // '';
my $keep_alive = $args{keep_alive} // 1;
my $security_headers = $args{security_headers} // '';
my $ct = $headers->{'Content-Type'}
// (($body =~ /^\s*[\[{]/) ? 'application/json' : 'text/plain');
my $response = "HTTP/1.1 $status $status_text\r\n"
. "Content-Type: $ct\r\n"
. "Content-Length: " . length($body) . "\r\n"
. "Connection: " . ($keep_alive ? 'keep-alive' : 'close') . "\r\n";
# Add security headers if provided
$response .= $security_headers if $security_headers;
# Add custom headers
for my $h (keys %$headers) {
next if $h eq 'Content-Type' || $h eq 'Content-Length' || $h eq 'Connection';
$response .= "$h: $headers->{$h}\r\n";
}
$response .= "\r\n" . $body;
return $response;
}
# Build 404 response at compile time
sub build_404_response {
my ($class, %args) = @_;
my $security_headers = $args{security_headers} // '';
return $class->build_response(
status => 404,
status_text => 'Not Found',
body => 'Not Found',
keep_alive => 0, # Close on 404
security_headers => $security_headers,
);
}
# Generate C code for parsing HTTP method from request buffer
# Uses XS::JIT::Builder API for clean code generation
sub gen_method_parser {
my ($class, $builder, $analysis) = @_;
my %methods_used = %{$analysis->{methods_used} // {}};
# Method lengths: GET=3, PUT=3, POST=4, HEAD=4, PATCH=5, DELETE=6, OPTIONS=7
my %method_lens = (
GET => 3, PUT => 3, POST => 4, HEAD => 4,
PATCH => 5, DELETE => 6, OPTIONS => 7
);
# Group methods by length
my %by_length;
for my $method (keys %methods_used) {
my $len = $method_lens{$method} // length($method);
push @{$by_length{$len}}, $method;
}
# If single method, generate super-optimized check
if ($analysis->{single_method}) {
my $method = $analysis->{single_method};
my $len = $method_lens{$method};
my $first_char = substr($method, 0, 1);
my $path_offset = $len + 1;
$builder->comment("OPTIMIZED: Single method ($method) - verify first char only")
->line('const char* method = recv_buf;')
->line("int method_len = $len;")
->line("const char* path = recv_buf + $path_offset;")
->blank
->comment("Quick validation: first char must be '$first_char'")
->if("recv_buf[0] != '$first_char'")
->line('HYPERSONIC_SEND(fd, RESP_404, RESP_404_LEN);')
->line('continue;')
->endif;
return $builder;
}
# Multiple methods - generate only the length checks we need
$builder->comment('HTTP/1.1: Parse method (space-delimited)')
->line('const char* method = recv_buf;')
->line('int method_len;')
->line('const char* path;')
->blank;
my $first = 1;
for my $len (sort { $a <=> $b } keys %by_length) {
my @methods_at_len = @{$by_length{$len}};
my $comment = join(', ', @methods_at_len);
my $path_offset = $len + 1;
if ($first) {
$builder->if("recv_buf[$len] == ' '");
lib/Hypersonic/Protocol/HTTP1.pm view on Meta::CPAN
}
# Add fallback for unknown methods
$builder->else
->comment('Fallback: scan for space')
->line('const char* sp = recv_buf;')
->while('*sp && *sp != \' \'')
->line('sp++;')
->endwhile
->line('method_len = sp - recv_buf;')
->line('path = sp + 1;')
->endif;
return $builder;
}
# Generate C code for parsing path from HTTP/1.1 request
# HTTP/1.1 format: METHOD /path?query HTTP/1.1\r\n
sub gen_path_parser {
my ($class, $builder) = @_;
$builder->comment('HTTP/1.1: Find end of path (space before HTTP/1.1)')
->line('const char* path_end = path;')
->line('int full_path_len;')
->line('const char* query_pos;')
->line('int path_len;')
->while('*path_end && *path_end != \' \'')
->line('path_end++;')
->endwhile
->line('full_path_len = path_end - path;')
->blank
->comment('Strip query string for route dispatch')
->line('query_pos = memchr(path, \'?\', full_path_len);')
->line('path_len = query_pos ? (query_pos - path) : full_path_len;');
return $builder;
}
# Generate C code for finding request body (after \r\n\r\n)
sub gen_body_parser {
my ($class, $builder, %opts) = @_;
if ($opts{has_body_access}) {
$builder->comment('HTTP/1.1: Find body after CRLF CRLF')
->line('const char* body_start = strstr(recv_buf, "\\r\\n\\r\\n");')
->line('const char* body = "";')
->line('int body_len = 0;')
->if('body_start')
->line('body = body_start + 4;') # Skip \r\n\r\n
->line('body_len = len - (body - recv_buf);')
->endif;
} else {
$builder->comment('OPTIMIZED: No body parsing needed')
->line('const char* body = "";')
->line('int body_len = 0;');
}
return $builder;
}
# Generate C code for keep-alive detection
sub gen_keepalive_check {
my ($class, $builder) = @_;
$builder->comment('HTTP/1.1: Check Connection header for keep-alive')
->line('int keep_alive = 1;') # HTTP/1.1 default is keep-alive
->if('len > 20')
->comment('Search for "Connection:" header (case-insensitive C or c)')
->line('const char* conn = strstr(recv_buf + 16, "onnection:");')
->if('conn && (conn[-1] == \'C\' || conn[-1] == \'c\')')
->if('strstr(conn, "close") || strstr(conn, "Close")')
->line('keep_alive = 0;')
->endif
->endif
->endif;
return $builder;
}
# Status code to text mapping (complete list)
sub _status_text {
my ($code) = @_;
my %text = (
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
204 => 'No Content',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
413 => 'Payload Too Large',
415 => 'Unsupported Media Type',
422 => 'Unprocessable Entity',
429 => 'Too Many Requests',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
);
return $text{$code} // 'Unknown';
}
# Get status text (class method for external use)
sub status_text {
my ($class, $code) = @_;
return _status_text($code);
}
# ============================================================
# Chunked Transfer Encoding (HTTP/1.1 streaming)
# ============================================================
# Generate C code for chunked response headers
sub gen_chunked_start {
my ($class, $builder) = @_;
$builder->comment('Send HTTP/1.1 headers with chunked encoding')
->line('static void send_chunked_headers(int fd, int status, const char* content_type) {')
->line(' char headers[2048];')
->line(' const char* status_str = "OK";')
->line(' switch(status) {')
->line(' case 200: status_str = "OK"; break;')
->line(' case 201: status_str = "Created"; break;')
->line(' case 202: status_str = "Accepted"; break;')
->line(' case 204: status_str = "No Content"; break;')
->line(' case 206: status_str = "Partial Content"; break;')
->line(' case 400: status_str = "Bad Request"; break;')
->line(' case 401: status_str = "Unauthorized"; break;')
->line(' case 403: status_str = "Forbidden"; break;')
->line(' case 404: status_str = "Not Found"; break;')
->line(' case 500: status_str = "Internal Server Error"; break;')
->line(' case 503: status_str = "Service Unavailable"; break;')
->line(' }')
->line(' int len = snprintf(headers, sizeof(headers),')
->line(' "HTTP/1.1 %d %s\\r\\n"')
->line(' "Content-Type: %s\\r\\n"')
->line(' "Transfer-Encoding: chunked\\r\\n"')
->line(' "Connection: keep-alive\\r\\n"')
->line(' "\\r\\n",')
->line(' status, status_str, content_type);')
->line(' send(fd, headers, len, 0);')
->line('}')
->blank;
return $builder;
}
# Generate C code for sending a chunk (hex length + data + CRLF)
sub gen_chunked_write {
my ($class, $builder) = @_;
$builder->comment('Send a single chunk - HTTP/1.1 chunked transfer encoding')
->line('static void send_chunk(int fd, const char* data, size_t len) {')
->line(' if (len == 0) return;')
->line(' ')
->line(' char size_line[32];')
->line(' int header_len = snprintf(size_line, sizeof(size_line), "%zx\\r\\n", len);')
->line(' ')
->line(' /* Use writev for efficiency (header + data + crlf in one syscall) */')
->line(' struct iovec iov[3];')
->line(' iov[0].iov_base = size_line;')
->line(' iov[0].iov_len = header_len;')
->line(' iov[1].iov_base = (void*)data;')
->line(' iov[1].iov_len = len;')
->line(' iov[2].iov_base = "\\r\\n";')
->line(' iov[2].iov_len = 2;')
->line(' ')
->line(' writev(fd, iov, 3);')
->line('}')
->blank;
return $builder;
}
# Generate C code for final chunk (0\r\n\r\n)
sub gen_chunked_end {
my ($class, $builder) = @_;
$builder->comment('Send final zero-length chunk to end stream')
->line('static void send_chunk_end(int fd) {')
->line(' send(fd, "0\\r\\n\\r\\n", 5, 0);')
->line('}')
->blank;
return $builder;
}
# Build a pre-formatted chunk for compile-time use
sub build_chunk {
my ($class, $data) = @_;
return '' unless defined $data && length($data);
my $len = length($data);
return sprintf("%x\r\n%s\r\n", $len, $data);
}
# Build final chunk
sub build_final_chunk {
return "0\r\n\r\n";
}
1;
__END__
=head1 NAME
Hypersonic::Protocol::HTTP1 - JIT code generation for HTTP/1.1 protocol
=head1 SYNOPSIS
use Hypersonic::Protocol::HTTP1;
# Build a complete response at compile time
my $response = Hypersonic::Protocol::HTTP1->build_response(
status => 200,
headers => { 'X-Custom' => 'value' },
body => '{"ok":true}',
);
# Generate C code for method parsing
Hypersonic::Protocol::HTTP1->gen_method_parser($builder, $analysis);
=head1 DESCRIPTION
This module provides JIT compile-time code generation for HTTP/1.1 protocol
handling. All methods either return pre-built strings or generate C code
using XS::JIT::Builder. There is zero runtime overhead.
=head2 HTTP/1.1 Specifics
=over 4
=item * Text-based request format: C<GET /path HTTP/1.1\r\n>
=item * CRLF delimiters: C<\r\n> between headers, C<\r\n\r\n> before body
=item * Keep-alive: Default in HTTP/1.1, detected via C<Connection: close>
=item * Response format: Status line + headers + body
=back
=head1 METHODS
=head2 build_response(%args)
Build a complete HTTP/1.1 response string at compile time.
=head2 gen_method_parser($builder, $analysis)
Generate C code for parsing HTTP method using XS::JIT::Builder.
=head2 gen_path_parser($builder)
Generate C code for parsing request path.
=head2 gen_body_parser($builder, %opts)
Generate C code for finding request body.
=head2 gen_keepalive_check($builder)
Generate C code for detecting Connection: close.
=head1 AUTHOR
Hypersonic Contributors
=cut
( run in 2.297 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )