#!/usr/bin/env perl use 5.028; use Mojolicious::Lite -signatures; use Mojo::JSON qw/encode_json/; use Mojo::Util qw/sha1_sum/; # NOTICE: This script is designed to work in a single-threaded, # single-process server only! (morbo or Mojolicious::Command::daemon) get '/' => sub ($c) { $c->render(template => 'index') } => 'index'; my %runningprocs; post '/submit' => sub ($c) { # form variables my $foo = $c->param('foo'); my $bar = $c->param('bar'); # set up the event dispatcher my $ee = Mojo::EventEmitter->new; # hash collisions theoretically possible but very unlikely (could check `exists $runningprocs{$id}`) my $id = sha1_sum( time."\0".rand."\0".(0+$ee) ); $runningprocs{$id} = $ee; $c->render(json => { eventurl=>$c->url_for('status', id=>$id) }); # set up and run the subprocess my $subproc = Mojo::IOLoop->subprocess; $subproc->on(spawn => sub ($sp) { $ee->emit(status => { progress=>"Subprocess spawned in PID ".$sp->pid }) }); $subproc->on(progress => sub ($sp, @data) { $ee->emit(status => { progress=>\@data }) }); # give client a second to connect to event source Mojo::IOLoop->timer(1 => sub { $subproc->run( sub ($sp) { return long_running_subprocess($sp, $foo, $bar) }, sub ($sp, $err, @results) { if ($err) { $ee->emit(status => { error=>"$err", done=>"Error: $err" }) } else { $ee->emit(status => { done=>\@results }) } # don't clobber the event listener immediately (in case client took longer to re/connect) Mojo::IOLoop->timer(10 => sub { delete $runningprocs{$id} }); }); }); } => 'formsubmit'; get '/status/:id' => sub ($c) { my $id = $c->stash('id'); my $ee = $runningprocs{$id} or return $c->reply->not_found; $c->inactivity_timeout(300); $c->res->headers->content_type('text/event-stream'); $c->write; my $timerid = Mojo::IOLoop->recurring(10 => sub { $c->write(":\n\n") }); # comment as keepalive my $cb = $ee->on(status => sub ($ev, $data) { my $json = encode_json($data) =~ s/\n//gr; $c->write("event: status\ndata: $json\n\n"); }); $c->on(finish => sub ($c) { $ee->unsubscribe(status => $cb); Mojo::IOLoop->remove($timerid); }); } => 'status'; sub long_running_subprocess { my ($subproc, $foo, $bar) = @_; # this code is now running in the subprocess! $subproc->progress("Beginning work on Foo='$foo'"); sleep 5; # ...foo... $subproc->progress("Finished work on Foo"); if ( length $bar ) { $subproc->progress("Beginning work on Bar='$bar'"); sleep 5; # ...bar... $subproc->progress("Finished work on Bar"); } return "All done!"; } app->start; __DATA__ @@ index.html.ep % layout 'main', title => 'Hello, World!';
%= form_for formsubmit => ( method=>'post', id=>'myform' ) => begin
%= label_for foo => 'Foo' %= text_field foo => ( placeholder=>"Foo", required=>'required' )
%= label_for bar => 'Bar' %= text_field bar => ( placeholder=>"Bar" )
%= submit_button 'Process'
%= end
Output will display here.
@@ layouts/main.html.ep <%= title %> %= content