File Coverage

blib/lib/Hypersonic/Protocol/SSE.pm
Criterion Covered Total %
statement 43 43 100.0
branch 6 6 100.0
condition 5 8 62.5
subroutine 12 12 100.0
pod 0 10 0.0
total 66 79 83.5


line stmt bran cond sub pod time code
1             package Hypersonic::Protocol::SSE;
2 3     3   219745 use strict;
  3         5  
  3         107  
3 3     3   9 use warnings;
  3         5  
  3         2422  
4              
5             # Hypersonic::Protocol::SSE - JIT code generation for Server-Sent Events
6             #
7             # SSE is a text-based protocol for pushing events from server to client.
8             # This module generates C code for efficient event formatting.
9             # Transport uses HTTP/1.1 chunked encoding or HTTP/2 DATA frames.
10              
11             our $VERSION = '0.15';
12              
13             =head1 NAME
14              
15             Hypersonic::Protocol::SSE - Server-Sent Events protocol support
16              
17             =head1 SYNOPSIS
18              
19             use Hypersonic::Protocol::SSE;
20            
21             # Generate C code for SSE formatting
22             Hypersonic::Protocol::SSE->gen_event_formatter($builder);
23              
24             =head1 DESCRIPTION
25              
26             Server-Sent Events (SSE) provide a standard for pushing events from a server
27             to a client over HTTP. This module provides compile-time C code generation
28             for efficient event formatting.
29              
30             =head2 SSE Format (RFC 8895)
31              
32             event: message
33             id: 123
34             data: Hello World
35            
36             event: update
37             data: line 1
38             data: line 2
39            
40             : this is a comment/keepalive
41            
42             retry: 3000
43              
44             =cut
45              
46             # Content-Type for SSE
47 1     1 0 26514 sub content_type { 'text/event-stream' }
48              
49             # Generate C code for formatting SSE events
50             sub gen_event_formatter {
51 2     2 0 4168 my ($class, $builder) = @_;
52            
53 2         205 $builder->comment('SSE: Format an event into buffer')
54             ->comment('Returns bytes written')
55             ->line('static size_t format_sse_event(char* buf, size_t buf_size,')
56             ->line(' const char* event_type,')
57             ->line(' const char* data,')
58             ->line(' const char* id) {')
59             ->line(' size_t pos = 0;')
60             ->blank
61             ->comment('Event type (optional)')
62             ->if('event_type && event_type[0]')
63             ->line('pos += snprintf(buf + pos, buf_size - pos, "event: %s\\n", event_type);')
64             ->endif
65             ->blank
66             ->comment('ID (optional)')
67             ->if('id && id[0]')
68             ->line('pos += snprintf(buf + pos, buf_size - pos, "id: %s\\n", id);')
69             ->endif
70             ->blank
71             ->comment('Data (required) - handle multiline')
72             ->if('data')
73             ->line('const char* line_start = data;')
74             ->line('const char* p = data;')
75             ->while('*p')
76             ->if('*p == \'\\n\'')
77             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %.*s\\n",')
78             ->line(' (int)(p - line_start), line_start);')
79             ->line('line_start = p + 1;')
80             ->endif
81             ->line('p++;')
82             ->endloop
83             ->comment('Last line (or only line if no newlines)')
84             ->if('line_start <= p && *line_start')
85             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %s\\n", line_start);')
86             ->elsif('line_start == data')
87             ->comment('Empty string - still need data line')
88             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: \\n");')
89             ->endif
90             ->endif
91             ->blank
92             ->comment('End of event (blank line)')
93             ->if('pos < buf_size')
94             ->line('buf[pos++] = \'\\n\';')
95             ->endif
96             ->blank
97             ->line('return pos;')
98             ->line('}')
99             ->blank;
100            
101 2         6 return $builder;
102             }
103              
104             # Generate C code for keepalive comment
105             sub gen_keepalive {
106 2     2 0 7335 my ($class, $builder) = @_;
107            
108 2         37 $builder->comment('SSE: Format keepalive comment')
109             ->line('static size_t format_sse_keepalive(char* buf, size_t buf_size) {')
110             ->line(' return snprintf(buf, buf_size, ": keepalive\\n\\n");')
111             ->line('}')
112             ->blank;
113            
114 2         5 return $builder;
115             }
116              
117             # Generate C code for retry directive
118             sub gen_retry {
119 2     2 0 4767 my ($class, $builder) = @_;
120            
121 2         24 $builder->comment('SSE: Format retry directive')
122             ->line('static size_t format_sse_retry(char* buf, size_t buf_size, int ms) {')
123             ->line(' return snprintf(buf, buf_size, "retry: %d\\n\\n", ms);')
124             ->line('}')
125             ->blank;
126            
127 2         4 return $builder;
128             }
129              
130             # Generate C code for custom comment
131             sub gen_comment {
132 1     1 0 5 my ($class, $builder) = @_;
133            
134 1         9 $builder->comment('SSE: Format comment (can be used for keepalive or metadata)')
135             ->line('static size_t format_sse_comment(char* buf, size_t buf_size, const char* text) {')
136             ->line(' return snprintf(buf, buf_size, ": %s\\n\\n", text);')
137             ->line('}')
138             ->blank;
139            
140 1         2 return $builder;
141             }
142              
143             # Generate all SSE C code
144             sub generate_c_code {
145 1     1 0 4791 my ($class, $builder, $opts) = @_;
146            
147 1         7 $class->gen_event_formatter($builder);
148 1         6 $class->gen_keepalive($builder);
149 1         4 $class->gen_retry($builder);
150 1         5 $class->gen_comment($builder);
151            
152 1         4 return $builder;
153             }
154              
155             # Perl-side event formatting (for compile-time or fallback)
156             sub format_event {
157 5     5 0 29737 my ($class, %opts) = @_;
158            
159 5         14 my $output = '';
160            
161             # Event type
162 5 100 66     41 if (defined $opts{type} && $opts{type} ne '') {
163 2         7 $output .= "event: $opts{type}\n";
164             }
165            
166             # ID
167 5 100 66     28 if (defined $opts{id} && $opts{id} ne '') {
168 1         4 $output .= "id: $opts{id}\n";
169             }
170            
171             # Data (handle multiline)
172 5   50     23 my $data = $opts{data} // '';
173 5 100       21 if ($data eq '') {
174             # Empty string - still need one data line
175 1         4 $output .= "data: \n";
176             } else {
177 4         24 for my $line (split /\n/, $data, -1) {
178 6         15 $output .= "data: $line\n";
179             }
180             }
181            
182             # Blank line to end event
183 5         16 $output .= "\n";
184            
185 5         17 return $output;
186             }
187              
188             # Format keepalive
189             sub format_keepalive {
190 1     1 0 5644 return ": keepalive\n\n";
191             }
192              
193             # Format retry directive
194             sub format_retry {
195 1     1 0 787 my ($class, $ms) = @_;
196 1         4 return "retry: $ms\n\n";
197             }
198              
199             # Format comment
200             sub format_comment {
201 1     1 0 723 my ($class, $text) = @_;
202 1         5 return ": $text\n\n";
203             }
204              
205             1;
206              
207             __END__