File Coverage

blib/lib/App/screenorama.pm
Criterion Covered Total %
statement 18 56 32.1
branch 2 10 20.0
condition 0 5 0.0
subroutine 4 14 28.5
pod 1 1 100.0
total 25 86 29.0


line stmt bran cond sub pod time code
1             package App::screenorama;
2              
3             =head1 NAME
4              
5             App::screenorama - Application output to websocket stream
6              
7             =head1 VERSION
8              
9             0.02
10              
11             =head1 DESCRIPTION
12              
13             This program allow you to pipe STDOUT and STDERR from a program over a
14             websocket.
15              
16             =begin html
17              
18             screenshot
19              
20             =end html
21              
22             =head2 Protocol
23              
24             The data transmitted over the websocket connection is JSON in each frame:
25              
26             =over 4
27              
28             =item * Startup
29              
30             {"program":$str,"program_args":...}
31              
32             Same as L and L.
33              
34             =item * Output
35              
36             {"output":$str}
37              
38             Comes after each time the program emit data. NOTE: There's no guaranty
39             that it will be emitted on newline.
40              
41             =item * Exit
42              
43             {"exit_value":$int,"signal":$int}
44              
45             The exit value of the application. The websocket will be closed after
46             you see this.
47              
48             =item * Error
49              
50             {"error":$str}
51              
52             If something goes wrong with the application or other operating
53             system errors.
54              
55             =back
56              
57             =head1 SYNOPSIS
58              
59             =head2 Server
60              
61             # let others connect to the running program
62             $ screenorama --listen http://*:5000 --single -- 'while sleep 1; do echo "hey!"; done'
63              
64             # pipe the output on incoming request
65             $ screenorama -- ls -l
66              
67             =head2 Client
68              
69             Connect a browser to L or L to
70             see the output.
71              
72             =cut
73              
74 2     2   1710 use Mojo::Base 'Mojolicious';
  2         4  
  2         24  
75 2     2   338189 use Mojo::IOLoop::ReadWriteFork;
  2         29422  
  2         33  
76              
77             our $VERSION = '0.02';
78              
79             =head1 ATTRIBUTES
80              
81             =head2 conduit
82              
83             $str = $self->conduit;
84             $self = $self->conduit($str);
85              
86             Defaults to "pty". Can also be set to "pipe".
87              
88             =head2 program
89              
90             $str = $self->program;
91             $self = $self->program($str);
92              
93             Path, name of to the application to run or the whole command.
94              
95             =head2 program_args
96              
97             $array_ref = $self->program_args;
98             $self = $self->program_args([qw( --option )]);
99              
100             Arguments to L.
101              
102             =head2 single
103              
104             $bool = $self->single;
105             $self = $self->single($bool);
106              
107             Set this to true if you want all incoming requests to connect to
108             one stream. False means start L on each request.
109              
110             Default: false.
111              
112             =head2 stdin
113              
114             Allow standard input from websocket to L.
115              
116             =cut
117              
118             has conduit => 'pty';
119             has program => '';
120             has program_args => sub { +[] };
121             has single => 0;
122             has stdin => 0;
123             has _fork => sub {
124             my $self = shift;
125             my $fork = Mojo::IOLoop::ReadWriteFork->new;
126             my $plugins = $self->plugins;
127              
128             $fork->on(close => sub {
129             $plugins->emit(output => { exit_value => $_[1], signal => $_[2] });
130             $plugins->has_subscribers('output') or Mojo::IOLoop->stop;
131             });
132             $fork->on(error => sub {
133             $plugins->emit(output => { error => $_[1] });
134             $plugins->has_subscribers('output') or Mojo::IOLoop->stop;
135             });
136             $fork->on(read => sub {
137             $plugins->emit(output => { output => $_[1] });
138             });
139             $fork->start(
140             conduit => $self->conduit,
141             program => $self->program,
142             program_args => $self->program_args,
143             );
144              
145             $fork;
146             };
147              
148             =head1 METHODS
149              
150             =head2 startup
151              
152             Used to start the web server.
153              
154             =cut
155              
156             sub startup {
157 3     3 1 66991 my $self = shift;
158 3         80 my $r = $self->routes;
159              
160 3         91 $self->renderer->classes([__PACKAGE__]);
161              
162             $r->get('/' => sub {
163 2     2   95134 my $c = shift;
164 2         52 my $url = $c->req->url->to_abs;
165 2         1415 my $stream_base = $url->base;
166              
167 2 50       50 $stream_base->scheme($url->scheme =~ /https/ ? 'wss' : 'ws');
168 2         576 $c->render(
169 2         1373 cmd => join(' ', $self->app->program, @{ $self->app->program_args }),
170             stream_base => $stream_base,
171             template => 'index',
172             );
173 3         170 });
174              
175 3 50       2108 if($self->single) {
176 0         0 $self->_fork;
177 0         0 $r->websocket('/stream' => \&_single_stream);
178             }
179             else {
180 3         52 $r->websocket('/stream' => \&_stream_on_request);
181             }
182             }
183              
184             sub _single_stream {
185 0     0     my $c = shift;
186 0           my $plugins = $c->app->plugins;
187 0           my $fork = $c->app->_fork;
188 0           my $cb;
189              
190             $cb = sub {
191 0     0     $c->send({ json => $_[1] });
192 0 0 0       defined $_[1]->{error} or defined $_[1]->{exit_value} or return;
193 0           $plugins->unsubscribe(output => $cb);
194 0           $c->finish;
195 0           };
196              
197 0           Mojo::IOLoop->stream($c->tx->connection)->timeout(60);
198              
199 0           $c->send({ json => { program => $c->app->program, program_args => $c->app->program_args } });
200 0 0   0     $c->on(json => sub { $fork->write(chr $_[1]->{key}); }) if $c->app->stdin;
  0            
201 0     0     $c->on(finish => sub { $plugins->unsubscribe(output => $cb); });
  0            
202 0           $plugins->on(output => $cb);
203             }
204              
205             sub _stream_on_request {
206 0     0     my $c = shift;
207 0           my $fork = Mojo::IOLoop::ReadWriteFork->new;
208 0           my $app = $c->app;
209              
210 0           Scalar::Util::weaken($c);
211 0           Mojo::IOLoop->stream($c->tx->connection)->timeout(60);
212 0 0   0     $c->on(json => sub { $fork->write(chr $_[1]->{key}); }) if $c->app->stdin;
  0            
213 0   0 0     $c->on(finish => sub { $fork->kill($ENV{SCREENORAMA_KILL_SIGNAL} || 15); });
  0            
214 0           $c->stash(fork => $fork);
215 0           $c->send({ json => { program => $app->program, program_args => $app->program_args } });
216              
217             $fork->on(close => sub {
218 0     0     $c->send({ json => { exit_value => $_[1], signal => $_[2] } });
219 0           $c->stash(fork => undef)->finish;
220 0           });
221             $fork->on(error => sub {
222 0     0     $c->send({ json => { error => $_[1] } });
223 0           $c->stash(fork => undef)->finish;
224 0           });
225             $fork->on(read => sub {
226 0     0     $c->send({ json => { output => $_[1] } });
227 0           });
228              
229 0           $fork->start(program => $app->program, program_args => $app->program_args, conduit => $app->conduit);
230             }
231              
232             =head1 AUTHOR
233              
234             Jan Henning Thorsen - C
235              
236             =cut
237              
238             1;
239              
240             __DATA__