Hypersonic

 view release on metacpan or  search on metacpan

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

package Hypersonic::Protocol::SSE;
use strict;
use warnings;

# Hypersonic::Protocol::SSE - JIT code generation for Server-Sent Events
#
# SSE is a text-based protocol for pushing events from server to client.
# This module generates C code for efficient event formatting.
# Transport uses HTTP/1.1 chunked encoding or HTTP/2 DATA frames.

our $VERSION = '0.16';

=head1 NAME

Hypersonic::Protocol::SSE - Server-Sent Events protocol support

=head1 SYNOPSIS

    use Hypersonic::Protocol::SSE;
    
    # Generate C code for SSE formatting
    Hypersonic::Protocol::SSE->gen_event_formatter($builder);

=head1 DESCRIPTION

Server-Sent Events (SSE) provide a standard for pushing events from a server
to a client over HTTP. This module provides compile-time C code generation
for efficient event formatting.

=head2 SSE Format (RFC 8895)

    event: message
    id: 123
    data: Hello World
    
    event: update
    data: line 1
    data: line 2
    
    : this is a comment/keepalive
    
    retry: 3000

=cut

# Content-Type for SSE
sub content_type { 'text/event-stream' }

# Generate C code for formatting SSE events
sub gen_event_formatter {
    my ($class, $builder) = @_;
    
    $builder->comment('SSE: Format an event into buffer')
      ->comment('Returns bytes written')
      ->line('static size_t format_sse_event(char* buf, size_t buf_size,')
      ->line('                                const char* event_type,')
      ->line('                                const char* data,')
      ->line('                                const char* id) {')
      ->line('    size_t pos = 0;')
      ->blank
      ->comment('Event type (optional)')
      ->if('event_type && event_type[0]')
        ->line('pos += snprintf(buf + pos, buf_size - pos, "event: %s\\n", event_type);')
      ->endif
      ->blank
      ->comment('ID (optional)')
      ->if('id && id[0]')
        ->line('pos += snprintf(buf + pos, buf_size - pos, "id: %s\\n", id);')
      ->endif
      ->blank
      ->comment('Data (required) - handle multiline')
      ->if('data')
        ->line('const char* line_start = data;')
        ->line('const char* p = data;')
        ->while('*p')
          ->if('*p == \'\\n\'')
            ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %.*s\\n",')
            ->line('               (int)(p - line_start), line_start);')
            ->line('line_start = p + 1;')
          ->endif
          ->line('p++;')
        ->endloop
        ->comment('Last line (or only line if no newlines)')
        ->if('line_start <= p && *line_start')
          ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %s\\n", line_start);')
        ->elsif('line_start == data')
          ->comment('Empty string - still need data line')
          ->line('pos += snprintf(buf + pos, buf_size - pos, "data: \\n");')
        ->endif
      ->endif
      ->blank
      ->comment('End of event (blank line)')
      ->if('pos < buf_size')
        ->line('buf[pos++] = \'\\n\';')
      ->endif
      ->blank
      ->line('return pos;')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate C code for keepalive comment
sub gen_keepalive {
    my ($class, $builder) = @_;
    
    $builder->comment('SSE: Format keepalive comment')
      ->line('static size_t format_sse_keepalive(char* buf, size_t buf_size) {')
      ->line('    return snprintf(buf, buf_size, ": keepalive\\n\\n");')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate C code for retry directive
sub gen_retry {
    my ($class, $builder) = @_;
    
    $builder->comment('SSE: Format retry directive')
      ->line('static size_t format_sse_retry(char* buf, size_t buf_size, int ms) {')
      ->line('    return snprintf(buf, buf_size, "retry: %d\\n\\n", ms);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate C code for custom comment
sub gen_comment {
    my ($class, $builder) = @_;
    
    $builder->comment('SSE: Format comment (can be used for keepalive or metadata)')
      ->line('static size_t format_sse_comment(char* buf, size_t buf_size, const char* text) {')
      ->line('    return snprintf(buf, buf_size, ": %s\\n\\n", text);')
      ->line('}')
      ->blank;
    
    return $builder;
}

# Generate all SSE C code
sub generate_c_code {
    my ($class, $builder, $opts) = @_;
    
    $class->gen_event_formatter($builder);
    $class->gen_keepalive($builder);
    $class->gen_retry($builder);
    $class->gen_comment($builder);
    
    return $builder;
}

# Perl-side event formatting (for compile-time or fallback)
sub format_event {
    my ($class, %opts) = @_;
    
    my $output = '';
    
    # Event type
    if (defined $opts{type} && $opts{type} ne '') {
        $output .= "event: $opts{type}\n";
    }
    
    # ID
    if (defined $opts{id} && $opts{id} ne '') {
        $output .= "id: $opts{id}\n";
    }
    
    # Data (handle multiline)
    my $data = $opts{data} // '';
    if ($data eq '') {
        # Empty string - still need one data line
        $output .= "data: \n";
    } else {
        for my $line (split /\n/, $data, -1) {
            $output .= "data: $line\n";
        }
    }
    
    # Blank line to end event
    $output .= "\n";
    
    return $output;
}

# Format keepalive
sub format_keepalive {
    return ": keepalive\n\n";
}

# Format retry directive
sub format_retry {
    my ($class, $ms) = @_;
    return "retry: $ms\n\n";
}

# Format comment
sub format_comment {
    my ($class, $text) = @_;
    return ": $text\n\n";
}

1;

__END__

=head1 SSE FORMAT

Each event consists of:

=over 4

=item * C<event: name> - Optional event type (default: "message")

=item * C<id: value> - Optional event ID for reconnection

=item * C<data: payload> - Required event data (can span multiple lines)

=item * Blank line - Terminates the event

=back

Special directives:

=over 4

=item * C<retry: ms> - Sets reconnection delay in milliseconds

=item * C<: comment> - Comment line (ignored by client, used for keepalive)

=back

=head1 AUTHOR

Hypersonic Contributors

=cut



( run in 0.612 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )