EventSource example

TL;DR

An alternative example for the EventSource web service.

In previous post Fixing an example in Mojolicious I bragged about how fixing a small issue in an example makes me feel part of the community. By the way, I think you should participate in the community and brag about it too!

I was looking at that example because I was interested into the EventSource API for a small project involving a game, rolling some dice on the web and being able to play at a distance with physical stuff (apart from the dice, of course). Hopefully more on this in the future.

I modified the example in the Mojolicious Cookbook guide to fit my example, here’s the code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
#!/usr/bin/env perl
use 5.024;
use Mojolicious::Lite -signatures;

package SequenceEmitter {
   use Mojo::Base 'Mojo::EventEmitter', '-signatures';
   use experimental qw< postderef >;
   no warnings qw< experimental::postderef >;

   has sequence => sub { [__rand()] };
   sub __rand { return scalar int rand 100 }

   sub update ($self) {
      my $v = __rand;
      push $self->sequence->@*, $v;
      $self->emit(push => $v);
   }

   sub onboard_controller ($self, $c) {
      my $method = sub ($e, $v) { $c->write("event:push\ndata: $v\n\n") };
      $method->($self, $self->sequence->[-1]); # send latest available
      my $cb = $self->on(push => $method); # subscribe
      $c->on(finish => sub ($c) { $self->unsubscribe(push => $cb) });
   }
}

# Template 'index' with browser-side code
get '/box/:id' => sub ($c) {
   return $c->render(template => 'index', id => scalar $c->param('id'));
};

get '/tickle/:id' => sub ($c) {
   emitter_for($c->param('id'))->update;
   return $c->render(text => "OK\n");
};

get '/events/:id' => sub ($c) {
   # Increase inactivity timeout for connection a bit
   $c->inactivity_timeout(300);

   # Change content type and finalize response headers
   $c->res->headers->content_type('text/event-stream');
   $c->write;

   emitter_for($c->param('id'))->onboard_controller($c);
};

app->start;

sub emitter_for ($id) {
   state %emitter_for;
   return $emitter_for{$id} //= SequenceEmitter->new;
}
__DATA__
 
@@ index.html.ep
<!DOCTYPE html>
<html>
  <head><title>LiveLog</title></head>
  <body>
    <script>
      var events = new EventSource('<%= url_for '/events' %>/<%= $id %>');
 
      events.addEventListener('push', function (event) {
        document.body.innerHTML += event.data + '<br/>';
      }, false);
    </script>
  </body>
</html>

I guess you know how to start it, so I won’t bother you here.

After the program is started, you can try it on the local host like this:

  • decide a name for your box, for example foo
  • point a couple of browser windows to http://127.0.0.1:3000/box/foo - you should see that a number between 0 and 99 (included) is shown in both (the same number);
  • use another browser window, or a command line curl command, to trigger the generation of a new value using url http://127.0.0.1/tickle/foo - you should see it appear in both windows
  • open another browser window to http://127.0.0.1:3000/box/foo - you should just see the last number that was generated in it

If you use bar instead of foo you will look into a different box… with a different sequence of numbers.

I know, I know… I should have used a POST and not a GET… but it’s a proof of concept!


Comments? Octodon, , GitHub, Reddit, or drop me a line!