| 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__ |