@@ -4,6 +4,7 @@ use Mojo::Base 'Mojo::EventEmitter';
4
4
use Carp qw( croak) ;
5
5
use Compress::Raw::Zlib qw( WANT_GZIP Z_STREAM_END) ;
6
6
use Mojo::Headers;
7
+ use Mojo::SSE qw( build_event parse_event) ;
7
8
use Scalar::Util qw( looks_like_number) ;
8
9
9
10
has [qw( auto_decompress auto_relax relaxed skip_body) ];
@@ -62,6 +63,8 @@ sub is_multipart {undef}
62
63
63
64
sub is_parsing_body { (shift -> {state } // ' ' ) eq ' body' }
64
65
66
+ sub is_sse { (shift -> headers-> content_type // ' ' ) eq ' text/event-stream' }
67
+
65
68
sub leftovers { shift -> {buffer } }
66
69
67
70
sub parse {
@@ -73,11 +76,14 @@ sub parse {
73
76
74
77
# Chunked content
75
78
$self -> {real_size } //= 0;
76
- if ($self -> is_chunked && $self -> { state } ne ' headers ' ) {
79
+ if ($self -> is_chunked) {
77
80
$self -> _parse_chunked;
78
81
$self -> {state } = ' finished' if ($self -> {chunk_state } // ' ' ) eq ' finished' ;
79
82
}
80
83
84
+ # SSE
85
+ elsif ($self -> is_sse) { $self -> _parse_sse }
86
+
81
87
# Not chunked, pass through to second buffer
82
88
else {
83
89
$self -> {real_size } += length $self -> {pre_buffer };
@@ -158,6 +164,16 @@ sub write_chunk {
158
164
return $self ;
159
165
}
160
166
167
+ sub write_sse {
168
+ my ($self , $event , $cb ) = @_ ;
169
+
170
+ $self -> headers-> content_type(' text/event-stream' ) unless $self -> {sse };
171
+ $self -> {sse } = 1;
172
+
173
+ return $self -> write unless defined $event ;
174
+ return $self -> write (build_event($event ), $cb );
175
+ }
176
+
161
177
sub _build_chunk {
162
178
my ($self , $chunk ) = @_ ;
163
179
@@ -253,6 +269,20 @@ sub _parse_headers {
253
269
$self -> {header_size } = $self -> {raw_size } - length $leftovers ;
254
270
}
255
271
272
+ sub _parse_sse {
273
+ my $self = shift ;
274
+
275
+ # Connection established
276
+ $self -> emit(' sse' ) unless $self -> {sse };
277
+ $self -> {sse } = 1;
278
+
279
+ # Parse SSE
280
+ while (my $event = parse_event(\$self -> {pre_buffer })) { $self -> emit(sse => $event ) }
281
+
282
+ # Check buffer size
283
+ @$self {qw( state limit) } = (' finished' , 1) if length ($self -> {pre_buffer } // ' ' ) > $self -> max_buffer_size;
284
+ }
285
+
256
286
sub _parse_until_body {
257
287
my ($self , $chunk ) = @_ ;
258
288
@@ -319,6 +349,18 @@ Emitted when a new chunk of content arrives.
319
349
say "Streaming: $bytes";
320
350
});
321
351
352
+ =head2 sse
353
+
354
+ $content->on(sse => sub ($content, $event) {...});
355
+
356
+ Emitted when a new Server-Sent Event (SSE) connection has been established and for each new event that arrives. Note
357
+ that this event is B<EXPERIMENTAL > and may change without warning!
358
+
359
+ $content->on(sse => sub ($content, $event) {
360
+ if ($event) { say "Type: $event->{type}, Data: $event->{data}" }
361
+ else { say "SSE connection established" }
362
+ });
363
+
322
364
=head1 ATTRIBUTES
323
365
324
366
L<Mojo::Content> implements the following attributes.
@@ -480,6 +522,13 @@ False, this is not a L<Mojo::Content::MultiPart> object.
480
522
481
523
Check if body parsing started yet.
482
524
525
+ =head2 is_sse
526
+
527
+ my $bool = $content->is_sse;
528
+
529
+ Check if C<Content-Type > header indicates Server-Sent Events (SSE). Note that this method is B<EXPERIMENTAL > and may
530
+ change without warning!
531
+
483
532
=head2 leftovers
484
533
485
534
my $bytes = $content->leftovers;
@@ -541,6 +590,16 @@ dynamic content to be written later. You can write an empty chunk of data at any
541
590
});
542
591
});
543
592
593
+ =head2 write_sse
594
+
595
+ $content = $content->write_sse;
596
+ $content = $content->write_sse($event);
597
+ $content = $content->write_sse($event => sub {...});
598
+
599
+ Write Server-Sent Event (SSE) non-blocking, the optional drain callback will be executed once all data has been
600
+ written. Calling this method without an event will finalize the response headers and allow for events to be written
601
+ later. Note that this method is B<EXPERIMENTAL > and may change without warning!
602
+
544
603
=head1 SEE ALSO
545
604
546
605
L<Mojolicious> , L<Mojolicious::Guides> , L<https://mojolicious.org> .
0 commit comments