File Coverage

blib/lib/Hypersonic/SSE.pm
Criterion Covered Total %
statement 84 84 100.0
branch n/a
condition 4 4 100.0
subroutine 27 27 100.0
pod 0 20 0.0
total 115 135 85.1


line stmt bran cond sub pod time code
1             package Hypersonic::SSE;
2 3     3   1012 use strict;
  3         4  
  3         126  
3 3     3   15 use warnings;
  3         5  
  3         227  
4 3     3   55 use 5.010;
  3         10  
5              
6             # Hypersonic::SSE - High-level Server-Sent Events API
7             #
8             # Wraps the streaming infrastructure to provide a clean SSE interface.
9             # Automatically handles headers, event formatting, and keepalives.
10             # Uses JIT-compiled XS for performance.
11              
12             our $VERSION = '0.15';
13              
14             use constant {
15 3         361 STATE_INIT => 0,
16             STATE_STARTED => 1,
17             STATE_FINISHED => 2,
18 3     3   15 };
  3         14  
19 3     3   24 use constant MAX_SSE_INSTANCES => 65536;
  3         7  
  3         153  
20 3     3   130 use constant DEFAULT_KEEPALIVE => 30;
  3         8  
  3         132  
21              
22 3     3   1143 use Hypersonic::Protocol::SSE;
  3         7  
  3         8804  
23              
24             =head1 NAME
25              
26             Hypersonic::SSE - Server-Sent Events streaming interface
27              
28             =head1 SYNOPSIS
29              
30             $app->get('/events' => sub {
31             my ($req, $stream) = @_;
32              
33             my $sse = Hypersonic::SSE->new($stream);
34              
35             $sse->event(
36             type => 'message',
37             data => 'Hello World!',
38             );
39              
40             $sse->event(
41             type => 'update',
42             data => '{"count": 42}',
43             id => '123',
44             );
45              
46             $sse->close();
47             }, { streaming => 1 });
48              
49             =head1 DESCRIPTION
50              
51             Hypersonic::SSE provides a high-level API for sending Server-Sent Events.
52             It wraps a Hypersonic::Stream object and handles SSE-specific formatting,
53             headers, and keepalives.
54              
55             =cut
56              
57             # ============================================================
58             # XS Code Generation - ALL instance methods generated in C
59             # ============================================================
60              
61             sub generate_c_code {
62 3     3 0 310 my ($class, $builder, $opts) = @_;
63 3   100     15 $opts //= {};
64 3   100     10 my $max = $opts->{max_sse_instances} // MAX_SSE_INSTANCES;
65              
66 3         23 $builder->line('#include ')
67             ->blank;
68              
69 3         10 $class->gen_sse_registry($builder, $max);
70 3         10 $class->gen_sse_reset($builder);
71 3         9 $class->gen_sse_format_event($builder);
72 3         8 $class->gen_sse_format_keepalive($builder);
73 3         7 $class->gen_sse_format_retry($builder);
74 3         6 $class->gen_sse_format_comment($builder);
75              
76             # XS instance methods
77 3         9 $class->gen_xs_new($builder);
78 3         9 $class->gen_xs_stream($builder);
79 3         10 $class->gen_xs_is_started($builder);
80 3         9 $class->gen_xs_event_count($builder);
81 3         7 $class->gen_xs_last_event_time($builder);
82 3         7 $class->gen_xs_needs_keepalive($builder);
83 3         17 $class->gen_xs_event($builder);
84 3         12 $class->gen_xs_data($builder);
85 3         11 $class->gen_xs_retry($builder);
86 3         13 $class->gen_xs_keepalive($builder);
87 3         11 $class->gen_xs_comment($builder);
88 3         11 $class->gen_xs_close($builder);
89              
90 3         12 return $builder;
91             }
92              
93             sub gen_sse_registry {
94 3     3 0 7 my ($class, $builder, $max) = @_;
95              
96 3         58 $builder->comment('SSE instance registry - stores SSE state')
97             ->line('#define SSE_MAX ' . $max)
98             ->line('#define SSE_STATE_INIT 0')
99             ->line('#define SSE_STATE_STARTED 1')
100             ->line('#define SSE_STATE_FINISHED 2')
101             ->blank
102             ->line('typedef struct {')
103             ->line(' SV* stream_sv;')
104             ->line(' int state;')
105             ->line(' int event_count;')
106             ->line(' time_t last_event_time;')
107             ->line(' int keepalive_interval;')
108             ->line('} SSEState;')
109             ->blank
110             ->line('static SSEState sse_registry[SSE_MAX];')
111             ->line('static int sse_next_id = 0;')
112             ->blank;
113             }
114              
115             sub gen_sse_reset {
116 3     3 0 5 my ($class, $builder) = @_;
117              
118 3         56 $builder->line('static void sse_reset(int id) {')
119             ->line(' if (sse_registry[id].stream_sv) {')
120             ->line(' SvREFCNT_dec(sse_registry[id].stream_sv);')
121             ->line(' }')
122             ->line(' memset(&sse_registry[id], 0, sizeof(SSEState));')
123             ->line(' sse_registry[id].last_event_time = time(NULL);')
124             ->line(' sse_registry[id].keepalive_interval = 30;')
125             ->line('}')
126             ->blank;
127             }
128              
129             sub gen_sse_format_event {
130 3     3 0 6 my ($class, $builder) = @_;
131              
132 3         161 $builder->comment('SSE: Format an event into buffer')
133             ->comment('Returns bytes written')
134             ->line('static size_t sse_format_event(char* buf, size_t buf_size,')
135             ->line(' const char* event_type,')
136             ->line(' const char* data,')
137             ->line(' const char* id) {')
138             ->line(' size_t pos = 0;')
139             ->blank
140             ->comment('Event type (optional)')
141             ->if('event_type && event_type[0]')
142             ->line('pos += snprintf(buf + pos, buf_size - pos, "event: %s\\n", event_type);')
143             ->endif
144             ->blank
145             ->comment('ID (optional)')
146             ->if('id && id[0]')
147             ->line('pos += snprintf(buf + pos, buf_size - pos, "id: %s\\n", id);')
148             ->endif
149             ->blank
150             ->comment('Data (required) - handle multiline')
151             ->if('data')
152             ->line('const char* line_start = data;')
153             ->line('const char* p = data;')
154             ->while('*p')
155             ->if('*p == \'\\n\'')
156             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %.*s\\n",')
157             ->line(' (int)(p - line_start), line_start);')
158             ->line('line_start = p + 1;')
159             ->endif
160             ->line('p++;')
161             ->endloop
162             ->comment('Last line (or only line if no newlines)')
163             ->if('line_start <= p && *line_start')
164             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: %s\\n", line_start);')
165             ->elsif('line_start == data')
166             ->comment('Empty string - still need data line')
167             ->line('pos += snprintf(buf + pos, buf_size - pos, "data: \\n");')
168             ->endif
169             ->endif
170             ->blank
171             ->comment('End of event (blank line)')
172             ->if('pos < buf_size')
173             ->line('buf[pos++] = \'\\n\';')
174             ->endif
175             ->blank
176             ->line('return pos;')
177             ->line('}')
178             ->blank;
179              
180 3         7 return $builder;
181             }
182              
183             sub gen_sse_format_keepalive {
184 3     3 0 7 my ($class, $builder) = @_;
185              
186 3         28 $builder->comment('SSE: Format keepalive comment')
187             ->line('static size_t sse_format_keepalive(char* buf, size_t buf_size) {')
188             ->line(' return snprintf(buf, buf_size, ": keepalive\\n\\n");')
189             ->line('}')
190             ->blank;
191              
192 3         5 return $builder;
193             }
194              
195             sub gen_sse_format_retry {
196 3     3 0 4 my ($class, $builder) = @_;
197              
198 3         18 $builder->comment('SSE: Format retry directive')
199             ->line('static size_t sse_format_retry(char* buf, size_t buf_size, int ms) {')
200             ->line(' return snprintf(buf, buf_size, "retry: %d\\n\\n", ms);')
201             ->line('}')
202             ->blank;
203              
204 3         4 return $builder;
205             }
206              
207             sub gen_sse_format_comment {
208 3     3 0 6 my ($class, $builder) = @_;
209              
210 3         24 $builder->comment('SSE: Format comment')
211             ->line('static size_t sse_format_comment(char* buf, size_t buf_size, const char* text) {')
212             ->line(' return snprintf(buf, buf_size, ": %s\\n\\n", text);')
213             ->line('}')
214             ->blank;
215              
216 3         14 return $builder;
217             }
218              
219             # XS: new($stream, %opts) - returns blessed scalar
220             sub gen_xs_new {
221 3     3 0 4 my ($class, $builder) = @_;
222              
223 3         108 $builder->xs_function('xs_sse_new')
224             ->xs_preamble
225             ->line('int id = sse_next_id;')
226             ->line('sse_next_id = (sse_next_id + 1) % SSE_MAX;')
227             ->blank
228             ->line('sse_reset(id);')
229             ->blank
230             ->comment('First arg after class is the stream object')
231             ->if('items >= 2')
232             ->line('sse_registry[id].stream_sv = newSVsv(ST(1));')
233             ->else
234             ->line('croak("Hypersonic::SSE->new requires a stream argument");')
235             ->endif
236             ->blank
237             ->comment('Parse optional hash args: keepalive => N')
238             ->for('int i = 2', 'i < items', 'i += 2')
239             ->if('i + 1 < items')
240             ->line('STRLEN klen;')
241             ->line('const char* key = SvPV(ST(i), klen);')
242             ->if('klen == 9 && strncmp(key, "keepalive", 9) == 0')
243             ->line('sse_registry[id].keepalive_interval = SvIV(ST(i + 1));')
244             ->endif
245             ->endif
246             ->endfor
247             ->blank
248             ->line('SV* id_sv = newSViv(id);')
249             ->line('SV* ref = newRV_noinc(id_sv);')
250             ->line('sv_bless(ref, gv_stashpv("Hypersonic::SSE", GV_ADD));')
251             ->line('ST(0) = sv_2mortal(ref);')
252             ->line('XSRETURN(1);')
253             ->xs_end
254             ->blank;
255             }
256              
257             sub gen_xs_stream {
258 3     3 0 5 my ($class, $builder) = @_;
259              
260 3         98 $builder->xs_function('xs_sse_stream')
261             ->xs_preamble
262             ->check_items(1, 1, '$sse->stream')
263             ->line('int id = SvIV(SvRV(ST(0)));')
264             ->if('id < 0 || id >= SSE_MAX')
265             ->line('XSRETURN_UNDEF;')
266             ->endif
267             ->if('sse_registry[id].stream_sv')
268             ->line('ST(0) = sv_2mortal(newSVsv(sse_registry[id].stream_sv));')
269             ->line('XSRETURN(1);')
270             ->endif
271             ->line('XSRETURN_UNDEF;')
272             ->xs_end
273             ->blank;
274             }
275              
276             sub gen_xs_is_started {
277 3     3 0 6 my ($class, $builder) = @_;
278              
279 3         53 $builder->xs_function('xs_sse_is_started')
280             ->xs_preamble
281             ->check_items(1, 1, '$sse->is_started')
282             ->line('int id = SvIV(SvRV(ST(0)));')
283             ->if('id < 0 || id >= SSE_MAX')
284             ->line('XSRETURN_NO;')
285             ->endif
286             ->if('sse_registry[id].state >= SSE_STATE_STARTED')
287             ->line('XSRETURN_YES;')
288             ->else
289             ->line('XSRETURN_NO;')
290             ->endif
291             ->xs_end
292             ->blank;
293             }
294              
295             sub gen_xs_event_count {
296 3     3 0 5 my ($class, $builder) = @_;
297              
298 3         34 $builder->xs_function('xs_sse_event_count')
299             ->xs_preamble
300             ->check_items(1, 1, '$sse->event_count')
301             ->line('int id = SvIV(SvRV(ST(0)));')
302             ->if('id < 0 || id >= SSE_MAX')
303             ->line('XSRETURN_IV(0);')
304             ->endif
305             ->line('XSRETURN_IV(sse_registry[id].event_count);')
306             ->xs_end
307             ->blank;
308             }
309              
310             sub gen_xs_last_event_time {
311 3     3 0 5 my ($class, $builder) = @_;
312              
313 3         32 $builder->xs_function('xs_sse_last_event_time')
314             ->xs_preamble
315             ->check_items(1, 1, '$sse->last_event_time')
316             ->line('int id = SvIV(SvRV(ST(0)));')
317             ->if('id < 0 || id >= SSE_MAX')
318             ->line('XSRETURN_IV(0);')
319             ->endif
320             ->line('XSRETURN_IV((IV)sse_registry[id].last_event_time);')
321             ->xs_end
322             ->blank;
323             }
324              
325             sub gen_xs_needs_keepalive {
326 3     3 0 6 my ($class, $builder) = @_;
327              
328 3         77 $builder->xs_function('xs_sse_needs_keepalive')
329             ->xs_preamble
330             ->check_items(1, 1, '$sse->needs_keepalive')
331             ->line('int id = SvIV(SvRV(ST(0)));')
332             ->if('id < 0 || id >= SSE_MAX')
333             ->line('XSRETURN_NO;')
334             ->endif
335             ->blank
336             ->comment('Check if stream is finished')
337             ->line('SSEState* s = &sse_registry[id];')
338             ->if('s->state >= SSE_STATE_FINISHED')
339             ->line('XSRETURN_NO;')
340             ->endif
341             ->blank
342             ->line('time_t now = time(NULL);')
343             ->line('time_t elapsed = now - s->last_event_time;')
344             ->if('elapsed >= s->keepalive_interval')
345             ->line('XSRETURN_YES;')
346             ->else
347             ->line('XSRETURN_NO;')
348             ->endif
349             ->xs_end
350             ->blank;
351             }
352              
353             sub gen_xs_event {
354 3     3 0 5 my ($class, $builder) = @_;
355              
356 3         403 $builder->xs_function('xs_sse_event')
357             ->xs_preamble
358             ->line('int id = SvIV(SvRV(ST(0)));')
359             ->if('id < 0 || id >= SSE_MAX')
360             ->line('ST(0) = ST(0);')
361             ->line('XSRETURN(1);')
362             ->endif
363             ->blank
364             ->line('SSEState* s = &sse_registry[id];')
365             ->blank
366             ->comment('Check if stream is finished by calling is_finished method')
367             ->if('s->stream_sv')
368             ->line('dSP;')
369             ->line('ENTER;')
370             ->line('SAVETMPS;')
371             ->line('PUSHMARK(SP);')
372             ->line('XPUSHs(s->stream_sv);')
373             ->line('PUTBACK;')
374             ->line('int count = call_method("is_finished", G_SCALAR);')
375             ->line('SPAGAIN;')
376             ->line('int is_finished = 0;')
377             ->if('count > 0')
378             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
379             ->line('SV* _ret_sv = POPs;')
380             ->line('is_finished = SvTRUE(_ret_sv);')
381             ->endif
382             ->line('PUTBACK;')
383             ->line('FREETMPS;')
384             ->line('LEAVE;')
385             ->if('is_finished')
386             ->line('ST(0) = ST(0);')
387             ->line('XSRETURN(1);')
388             ->endif
389             ->endif
390             ->blank
391             ->comment('Start SSE if not started - call headers method on stream')
392             ->if('s->state == SSE_STATE_INIT && s->stream_sv')
393             ->line('dSP;')
394             ->line('ENTER;')
395             ->line('SAVETMPS;')
396             ->line('PUSHMARK(SP);')
397             ->line('XPUSHs(s->stream_sv);')
398             ->line('XPUSHs(sv_2mortal(newSViv(200)));')
399             ->comment('Create headers hash')
400             ->line('HV* hv = newHV();')
401             ->line('(void)hv_store(hv, "Content-Type", 12, newSVpv("text/event-stream", 0), 0);')
402             ->line('(void)hv_store(hv, "Cache-Control", 13, newSVpv("no-cache", 0), 0);')
403             ->line('(void)hv_store(hv, "Connection", 10, newSVpv("keep-alive", 0), 0);')
404             ->line('(void)hv_store(hv, "X-Accel-Buffering", 17, newSVpv("no", 0), 0);')
405             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV*)hv)));')
406             ->line('PUTBACK;')
407             ->line('call_method("headers", G_DISCARD);')
408             ->line('FREETMPS;')
409             ->line('LEAVE;')
410             ->line('s->state = SSE_STATE_STARTED;')
411             ->endif
412             ->blank
413             ->comment('Parse event options from hash args')
414             ->line('const char* event_type = NULL;')
415             ->line('const char* data = "";')
416             ->line('const char* event_id = NULL;')
417             ->blank
418             ->for('int i = 1', 'i < items', 'i += 2')
419             ->if('i + 1 < items')
420             ->line('STRLEN klen;')
421             ->line('const char* key = SvPV(ST(i), klen);')
422             ->if('klen == 4 && strncmp(key, "type", 4) == 0')
423             ->line('event_type = SvPV_nolen(ST(i + 1));')
424             ->endif
425             ->if('klen == 4 && strncmp(key, "data", 4) == 0')
426             ->line('data = SvPV_nolen(ST(i + 1));')
427             ->endif
428             ->if('klen == 2 && strncmp(key, "id", 2) == 0')
429             ->line('event_id = SvPV_nolen(ST(i + 1));')
430             ->endif
431             ->endif
432             ->endfor
433             ->blank
434             ->comment('Format event')
435             ->line('char buf[8192];')
436             ->line('size_t len = sse_format_event(buf, sizeof(buf), event_type, data, event_id);')
437             ->blank
438             ->comment('Write to stream')
439             ->if('s->stream_sv && len > 0')
440             ->line('dSP;')
441             ->line('ENTER;')
442             ->line('SAVETMPS;')
443             ->line('PUSHMARK(SP);')
444             ->line('XPUSHs(s->stream_sv);')
445             ->line('XPUSHs(sv_2mortal(newSVpvn(buf, len)));')
446             ->line('PUTBACK;')
447             ->line('call_method("write", G_DISCARD);')
448             ->line('FREETMPS;')
449             ->line('LEAVE;')
450             ->endif
451             ->blank
452             ->line('s->event_count++;')
453             ->line('s->last_event_time = time(NULL);')
454             ->blank
455             ->line('ST(0) = ST(0);')
456             ->line('XSRETURN(1);')
457             ->xs_end
458             ->blank;
459             }
460              
461             sub gen_xs_data {
462 3     3 0 5 my ($class, $builder) = @_;
463              
464 3         336 $builder->xs_function('xs_sse_data')
465             ->xs_preamble
466             ->if('items != 2')
467             ->line('croak("Usage: $sse->data(payload)");')
468             ->endif
469             ->line('int id = SvIV(SvRV(ST(0)));')
470             ->if('id < 0 || id >= SSE_MAX')
471             ->line('ST(0) = ST(0);')
472             ->line('XSRETURN(1);')
473             ->endif
474             ->blank
475             ->line('SSEState* s = &sse_registry[id];')
476             ->blank
477             ->comment('Check if stream is finished')
478             ->if('s->stream_sv')
479             ->line('dSP;')
480             ->line('ENTER;')
481             ->line('SAVETMPS;')
482             ->line('PUSHMARK(SP);')
483             ->line('XPUSHs(s->stream_sv);')
484             ->line('PUTBACK;')
485             ->line('int count = call_method("is_finished", G_SCALAR);')
486             ->line('SPAGAIN;')
487             ->line('int is_finished = 0;')
488             ->if('count > 0')
489             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
490             ->line('SV* _ret_sv = POPs;')
491             ->line('is_finished = SvTRUE(_ret_sv);')
492             ->endif
493             ->line('PUTBACK;')
494             ->line('FREETMPS;')
495             ->line('LEAVE;')
496             ->if('is_finished')
497             ->line('ST(0) = ST(0);')
498             ->line('XSRETURN(1);')
499             ->endif
500             ->endif
501             ->blank
502             ->comment('Start SSE if not started')
503             ->if('s->state == SSE_STATE_INIT && s->stream_sv')
504             ->line('dSP;')
505             ->line('ENTER;')
506             ->line('SAVETMPS;')
507             ->line('PUSHMARK(SP);')
508             ->line('XPUSHs(s->stream_sv);')
509             ->line('XPUSHs(sv_2mortal(newSViv(200)));')
510             ->line('HV* hv = newHV();')
511             ->line('(void)hv_store(hv, "Content-Type", 12, newSVpv("text/event-stream", 0), 0);')
512             ->line('(void)hv_store(hv, "Cache-Control", 13, newSVpv("no-cache", 0), 0);')
513             ->line('(void)hv_store(hv, "Connection", 10, newSVpv("keep-alive", 0), 0);')
514             ->line('(void)hv_store(hv, "X-Accel-Buffering", 17, newSVpv("no", 0), 0);')
515             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV*)hv)));')
516             ->line('PUTBACK;')
517             ->line('call_method("headers", G_DISCARD);')
518             ->line('FREETMPS;')
519             ->line('LEAVE;')
520             ->line('s->state = SSE_STATE_STARTED;')
521             ->endif
522             ->blank
523             ->comment('Format data-only event')
524             ->line('const char* data = SvPV_nolen(ST(1));')
525             ->line('char buf[8192];')
526             ->line('size_t len = sse_format_event(buf, sizeof(buf), NULL, data, NULL);')
527             ->blank
528             ->comment('Write to stream')
529             ->if('s->stream_sv && len > 0')
530             ->line('dSP;')
531             ->line('ENTER;')
532             ->line('SAVETMPS;')
533             ->line('PUSHMARK(SP);')
534             ->line('XPUSHs(s->stream_sv);')
535             ->line('XPUSHs(sv_2mortal(newSVpvn(buf, len)));')
536             ->line('PUTBACK;')
537             ->line('call_method("write", G_DISCARD);')
538             ->line('FREETMPS;')
539             ->line('LEAVE;')
540             ->endif
541             ->blank
542             ->line('s->event_count++;')
543             ->line('s->last_event_time = time(NULL);')
544             ->blank
545             ->line('ST(0) = ST(0);')
546             ->line('XSRETURN(1);')
547             ->xs_end
548             ->blank;
549             }
550              
551             sub gen_xs_retry {
552 3     3 0 6 my ($class, $builder) = @_;
553              
554 3         500 $builder->xs_function('xs_sse_retry')
555             ->xs_preamble
556             ->if('items != 2')
557             ->line('croak("Usage: $sse->retry(milliseconds)");')
558             ->endif
559             ->line('int id = SvIV(SvRV(ST(0)));')
560             ->if('id < 0 || id >= SSE_MAX')
561             ->line('ST(0) = ST(0);')
562             ->line('XSRETURN(1);')
563             ->endif
564             ->blank
565             ->line('SSEState* s = &sse_registry[id];')
566             ->blank
567             ->comment('Check if stream is finished')
568             ->if('s->stream_sv')
569             ->line('dSP;')
570             ->line('ENTER;')
571             ->line('SAVETMPS;')
572             ->line('PUSHMARK(SP);')
573             ->line('XPUSHs(s->stream_sv);')
574             ->line('PUTBACK;')
575             ->line('int count = call_method("is_finished", G_SCALAR);')
576             ->line('SPAGAIN;')
577             ->line('int is_finished = 0;')
578             ->if('count > 0')
579             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
580             ->line('SV* _ret_sv = POPs;')
581             ->line('is_finished = SvTRUE(_ret_sv);')
582             ->endif
583             ->line('PUTBACK;')
584             ->line('FREETMPS;')
585             ->line('LEAVE;')
586             ->if('is_finished')
587             ->line('ST(0) = ST(0);')
588             ->line('XSRETURN(1);')
589             ->endif
590             ->endif
591             ->blank
592             ->comment('Start SSE if not started')
593             ->if('s->state == SSE_STATE_INIT && s->stream_sv')
594             ->line('dSP;')
595             ->line('ENTER;')
596             ->line('SAVETMPS;')
597             ->line('PUSHMARK(SP);')
598             ->line('XPUSHs(s->stream_sv);')
599             ->line('XPUSHs(sv_2mortal(newSViv(200)));')
600             ->line('HV* hv = newHV();')
601             ->line('(void)hv_store(hv, "Content-Type", 12, newSVpv("text/event-stream", 0), 0);')
602             ->line('(void)hv_store(hv, "Cache-Control", 13, newSVpv("no-cache", 0), 0);')
603             ->line('(void)hv_store(hv, "Connection", 10, newSVpv("keep-alive", 0), 0);')
604             ->line('(void)hv_store(hv, "X-Accel-Buffering", 17, newSVpv("no", 0), 0);')
605             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV*)hv)));')
606             ->line('PUTBACK;')
607             ->line('call_method("headers", G_DISCARD);')
608             ->line('FREETMPS;')
609             ->line('LEAVE;')
610             ->line('s->state = SSE_STATE_STARTED;')
611             ->endif
612             ->blank
613             ->comment('Format retry directive')
614             ->line('int ms = SvIV(ST(1));')
615             ->line('char buf[64];')
616             ->line('size_t len = sse_format_retry(buf, sizeof(buf), ms);')
617             ->blank
618             ->comment('Write to stream')
619             ->if('s->stream_sv && len > 0')
620             ->line('dSP;')
621             ->line('ENTER;')
622             ->line('SAVETMPS;')
623             ->line('PUSHMARK(SP);')
624             ->line('XPUSHs(s->stream_sv);')
625             ->line('XPUSHs(sv_2mortal(newSVpvn(buf, len)));')
626             ->line('PUTBACK;')
627             ->line('call_method("write", G_DISCARD);')
628             ->line('FREETMPS;')
629             ->line('LEAVE;')
630             ->endif
631             ->blank
632             ->line('ST(0) = ST(0);')
633             ->line('XSRETURN(1);')
634             ->xs_end
635             ->blank;
636             }
637              
638             sub gen_xs_keepalive {
639 3     3 0 5 my ($class, $builder) = @_;
640              
641 3         357 $builder->xs_function('xs_sse_keepalive')
642             ->xs_preamble
643             ->check_items(1, 1, '$sse->keepalive')
644             ->line('int id = SvIV(SvRV(ST(0)));')
645             ->if('id < 0 || id >= SSE_MAX')
646             ->line('ST(0) = ST(0);')
647             ->line('XSRETURN(1);')
648             ->endif
649             ->blank
650             ->line('SSEState* s = &sse_registry[id];')
651             ->blank
652             ->comment('Check if stream is finished')
653             ->if('s->stream_sv')
654             ->line('dSP;')
655             ->line('ENTER;')
656             ->line('SAVETMPS;')
657             ->line('PUSHMARK(SP);')
658             ->line('XPUSHs(s->stream_sv);')
659             ->line('PUTBACK;')
660             ->line('int count = call_method("is_finished", G_SCALAR);')
661             ->line('SPAGAIN;')
662             ->line('int is_finished = 0;')
663             ->if('count > 0')
664             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
665             ->line('SV* _ret_sv = POPs;')
666             ->line('is_finished = SvTRUE(_ret_sv);')
667             ->endif
668             ->line('PUTBACK;')
669             ->line('FREETMPS;')
670             ->line('LEAVE;')
671             ->if('is_finished')
672             ->line('ST(0) = ST(0);')
673             ->line('XSRETURN(1);')
674             ->endif
675             ->endif
676             ->blank
677             ->comment('Start SSE if not started')
678             ->if('s->state == SSE_STATE_INIT && s->stream_sv')
679             ->line('dSP;')
680             ->line('ENTER;')
681             ->line('SAVETMPS;')
682             ->line('PUSHMARK(SP);')
683             ->line('XPUSHs(s->stream_sv);')
684             ->line('XPUSHs(sv_2mortal(newSViv(200)));')
685             ->line('HV* hv = newHV();')
686             ->line('(void)hv_store(hv, "Content-Type", 12, newSVpv("text/event-stream", 0), 0);')
687             ->line('(void)hv_store(hv, "Cache-Control", 13, newSVpv("no-cache", 0), 0);')
688             ->line('(void)hv_store(hv, "Connection", 10, newSVpv("keep-alive", 0), 0);')
689             ->line('(void)hv_store(hv, "X-Accel-Buffering", 17, newSVpv("no", 0), 0);')
690             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV*)hv)));')
691             ->line('PUTBACK;')
692             ->line('call_method("headers", G_DISCARD);')
693             ->line('FREETMPS;')
694             ->line('LEAVE;')
695             ->line('s->state = SSE_STATE_STARTED;')
696             ->endif
697             ->blank
698             ->comment('Format keepalive')
699             ->line('char buf[32];')
700             ->line('size_t len = sse_format_keepalive(buf, sizeof(buf));')
701             ->blank
702             ->comment('Write to stream')
703             ->if('s->stream_sv && len > 0')
704             ->line('dSP;')
705             ->line('ENTER;')
706             ->line('SAVETMPS;')
707             ->line('PUSHMARK(SP);')
708             ->line('XPUSHs(s->stream_sv);')
709             ->line('XPUSHs(sv_2mortal(newSVpvn(buf, len)));')
710             ->line('PUTBACK;')
711             ->line('call_method("write", G_DISCARD);')
712             ->line('FREETMPS;')
713             ->line('LEAVE;')
714             ->endif
715             ->blank
716             ->line('s->last_event_time = time(NULL);')
717             ->blank
718             ->line('ST(0) = ST(0);')
719             ->line('XSRETURN(1);')
720             ->xs_end
721             ->blank;
722             }
723              
724             sub gen_xs_comment {
725 3     3 0 7 my ($class, $builder) = @_;
726              
727 3         262 $builder->xs_function('xs_sse_comment')
728             ->xs_preamble
729             ->if('items != 2')
730             ->line('croak("Usage: $sse->comment(text)");')
731             ->endif
732             ->line('int id = SvIV(SvRV(ST(0)));')
733             ->if('id < 0 || id >= SSE_MAX')
734             ->line('ST(0) = ST(0);')
735             ->line('XSRETURN(1);')
736             ->endif
737             ->blank
738             ->line('SSEState* s = &sse_registry[id];')
739             ->blank
740             ->comment('Check if stream is finished')
741             ->if('s->stream_sv')
742             ->line('dSP;')
743             ->line('ENTER;')
744             ->line('SAVETMPS;')
745             ->line('PUSHMARK(SP);')
746             ->line('XPUSHs(s->stream_sv);')
747             ->line('PUTBACK;')
748             ->line('int count = call_method("is_finished", G_SCALAR);')
749             ->line('SPAGAIN;')
750             ->line('int is_finished = 0;')
751             ->if('count > 0')
752             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
753             ->line('SV* _ret_sv = POPs;')
754             ->line('is_finished = SvTRUE(_ret_sv);')
755             ->endif
756             ->line('PUTBACK;')
757             ->line('FREETMPS;')
758             ->line('LEAVE;')
759             ->if('is_finished')
760             ->line('ST(0) = ST(0);')
761             ->line('XSRETURN(1);')
762             ->endif
763             ->endif
764             ->blank
765             ->comment('Start SSE if not started')
766             ->if('s->state == SSE_STATE_INIT && s->stream_sv')
767             ->line('dSP;')
768             ->line('ENTER;')
769             ->line('SAVETMPS;')
770             ->line('PUSHMARK(SP);')
771             ->line('XPUSHs(s->stream_sv);')
772             ->line('XPUSHs(sv_2mortal(newSViv(200)));')
773             ->line('HV* hv = newHV();')
774             ->line('(void)hv_store(hv, "Content-Type", 12, newSVpv("text/event-stream", 0), 0);')
775             ->line('(void)hv_store(hv, "Cache-Control", 13, newSVpv("no-cache", 0), 0);')
776             ->line('(void)hv_store(hv, "Connection", 10, newSVpv("keep-alive", 0), 0);')
777             ->line('(void)hv_store(hv, "X-Accel-Buffering", 17, newSVpv("no", 0), 0);')
778             ->line('XPUSHs(sv_2mortal(newRV_noinc((SV*)hv)));')
779             ->line('PUTBACK;')
780             ->line('call_method("headers", G_DISCARD);')
781             ->line('FREETMPS;')
782             ->line('LEAVE;')
783             ->line('s->state = SSE_STATE_STARTED;')
784             ->endif
785             ->blank
786             ->comment('Format comment')
787             ->line('const char* text = SvPV_nolen(ST(1));')
788             ->line('char buf[4096];')
789             ->line('size_t len = sse_format_comment(buf, sizeof(buf), text);')
790             ->blank
791             ->comment('Write to stream')
792             ->if('s->stream_sv && len > 0')
793             ->line('dSP;')
794             ->line('ENTER;')
795             ->line('SAVETMPS;')
796             ->line('PUSHMARK(SP);')
797             ->line('XPUSHs(s->stream_sv);')
798             ->line('XPUSHs(sv_2mortal(newSVpvn(buf, len)));')
799             ->line('PUTBACK;')
800             ->line('call_method("write", G_DISCARD);')
801             ->line('FREETMPS;')
802             ->line('LEAVE;')
803             ->endif
804             ->blank
805             ->line('ST(0) = ST(0);')
806             ->line('XSRETURN(1);')
807             ->xs_end
808             ->blank;
809             }
810              
811             sub gen_xs_close {
812 3     3 0 5 my ($class, $builder) = @_;
813              
814 3         246 $builder->xs_function('xs_sse_close')
815             ->xs_preamble
816             ->check_items(1, 1, '$sse->close')
817             ->line('int id = SvIV(SvRV(ST(0)));')
818             ->if('id < 0 || id >= SSE_MAX')
819             ->line('ST(0) = ST(0);')
820             ->line('XSRETURN(1);')
821             ->endif
822             ->blank
823             ->line('SSEState* s = &sse_registry[id];')
824             ->blank
825             ->comment('Check if stream is finished')
826             ->if('s->stream_sv')
827             ->line('dSP;')
828             ->line('ENTER;')
829             ->line('SAVETMPS;')
830             ->line('PUSHMARK(SP);')
831             ->line('XPUSHs(s->stream_sv);')
832             ->line('PUTBACK;')
833             ->line('int count = call_method("is_finished", G_SCALAR);')
834             ->line('SPAGAIN;')
835             ->line('int is_finished = 0;')
836             ->if('count > 0')
837             ->comment('SvTRUE is a multi-evaluation macro on older perls; pop into a temp first')
838             ->line('SV* _ret_sv = POPs;')
839             ->line('is_finished = SvTRUE(_ret_sv);')
840             ->endif
841             ->line('PUTBACK;')
842             ->line('FREETMPS;')
843             ->line('LEAVE;')
844             ->if('is_finished')
845             ->line('ST(0) = ST(0);')
846             ->line('XSRETURN(1);')
847             ->endif
848             ->endif
849             ->blank
850             ->comment('Call end on stream')
851             ->if('s->stream_sv')
852             ->line('dSP;')
853             ->line('ENTER;')
854             ->line('SAVETMPS;')
855             ->line('PUSHMARK(SP);')
856             ->line('XPUSHs(s->stream_sv);')
857             ->line('PUTBACK;')
858             ->line('call_method("end", G_DISCARD);')
859             ->line('FREETMPS;')
860             ->line('LEAVE;')
861             ->endif
862             ->blank
863             ->line('s->state = SSE_STATE_FINISHED;')
864             ->blank
865             ->line('ST(0) = ST(0);')
866             ->line('XSRETURN(1);')
867             ->xs_end
868             ->blank;
869             }
870              
871             sub get_xs_functions {
872             return {
873 3     3 0 1037285 'Hypersonic::SSE::new' => { source => 'xs_sse_new', is_xs_native => 1 },
874             'Hypersonic::SSE::stream' => { source => 'xs_sse_stream', is_xs_native => 1 },
875             'Hypersonic::SSE::is_started' => { source => 'xs_sse_is_started', is_xs_native => 1 },
876             'Hypersonic::SSE::event_count' => { source => 'xs_sse_event_count', is_xs_native => 1 },
877             'Hypersonic::SSE::last_event_time' => { source => 'xs_sse_last_event_time', is_xs_native => 1 },
878             'Hypersonic::SSE::needs_keepalive' => { source => 'xs_sse_needs_keepalive', is_xs_native => 1 },
879             'Hypersonic::SSE::event' => { source => 'xs_sse_event', is_xs_native => 1 },
880             'Hypersonic::SSE::data' => { source => 'xs_sse_data', is_xs_native => 1 },
881             'Hypersonic::SSE::retry' => { source => 'xs_sse_retry', is_xs_native => 1 },
882             'Hypersonic::SSE::keepalive' => { source => 'xs_sse_keepalive', is_xs_native => 1 },
883             'Hypersonic::SSE::comment' => { source => 'xs_sse_comment', is_xs_native => 1 },
884             'Hypersonic::SSE::close' => { source => 'xs_sse_close', is_xs_native => 1 },
885             };
886             }
887              
888             1;
889              
890             __END__