File Coverage

blib/lib/Pinto/Server/Responder/Action.pm
Criterion Covered Total %
statement 119 133 89.4
branch 20 26 76.9
condition 6 11 54.5
subroutine 24 26 92.3
pod 1 4 25.0
total 170 200 85.0


line stmt bran cond sub pod time code
1             # ABSTRACT: Responder for action requests
2              
3             package Pinto::Server::Responder::Action;
4              
5 12     12   7780 use Moose;
  12         24  
  12         114  
6              
7 12     12   78556 use Carp;
  12         24  
  12         836  
8 12     12   72 use JSON;
  12         46  
  12         106  
9 12     12   6628 use IO::Pipe;
  12         11776  
  12         156  
10 12     12   332 use IO::Select;
  12         26  
  12         452  
11 12     12   64 use Try::Tiny;
  12         24  
  12         534  
12 12     12   68 use File::Temp;
  12         24  
  12         808  
13 12     12   72 use File::Copy;
  12         24  
  12         484  
14 12     12   3512 use Proc::Fork;
  12         18944  
  12         82  
15 12     12   1986 use Path::Class;
  12         24  
  12         618  
16 12     12   3466 use Proc::Terminator;
  12         171206  
  12         1156  
17 12     12   886 use Plack::Response;
  12         1918  
  12         200  
18 12     12   390 use HTTP::Status qw(:constants);
  12         34  
  12         4622  
19              
20 12     12   94 use Pinto;
  12         28  
  12         152  
21 12     12   280 use Pinto::Result;
  12         24  
  12         102  
22 12     12   6204 use Pinto::Chrome::Net;
  12         48  
  12         188  
23 12     12   654 use Pinto::Constants qw(:protocol);
  12         34  
  12         1098  
24              
25             #-------------------------------------------------------------------------------
26              
27             our $VERSION = '0.14'; # VERSION
28              
29             #-------------------------------------------------------------------------------
30              
31             extends qw(Pinto::Server::Responder);
32              
33             #-------------------------------------------------------------------------------
34              
35             sub respond {
36 59     59 1 186 my ($self) = @_;
37              
38 59         274 my $error_response = $self->check_protocol_version;
39 59 100       757 return $error_response if $error_response;
40              
41             # path_info always has a leading slash, e.g. /action/list
42 56         1455 my ( undef, undef, $action_name ) = split '/', $self->request->path_info;
43              
44 56         635 my %params = %{ $self->request->parameters }; # Copying
  56         1481  
45 56 50       31027 my $chrome_args = $params{chrome} ? decode_json( $params{chrome} ) : {};
46 56 50       313 my $pinto_args = $params{pinto} ? decode_json( $params{pinto} ) : {};
47 56 100       817 my $action_args = $params{action} ? decode_json( $params{action} ) : {};
48              
49 56         1721 for my $upload_name ( $self->request->uploads->keys ) {
50 0         0 my $upload = $self->request->uploads->{$upload_name};
51 0         0 my $basename = $upload->filename;
52 0         0 my $localfile = file( $upload->path )->dir->file($basename);
53 0         0 File::Copy::move( $upload->path, $localfile ); #TODO: autodie
54 0         0 $action_args->{$upload_name} = $localfile;
55             }
56              
57 56         1379 my $response;
58 56         603 my $pipe = IO::Pipe->new;
59              
60             run_fork {
61 10         50796 child { $self->child_proc( $pipe, $chrome_args, $pinto_args, $action_name, $action_args ) }
62 46         217185 parent { my $child_pid = shift; $response = $self->parent_proc( $pipe, $child_pid ) }
  46         1343  
63 56     56   2318 error { croak "Failed to fork: $!" };
  0         0  
64 56         8859 };
65              
66 46         5018 return $response;
67             }
68              
69             #-------------------------------------------------------------------------------
70              
71             sub check_protocol_version {
72 59     59 0 179 my ($self) = @_;
73              
74             # NB: Format derived from GitHub: https://developer.github.com/v3/media
75 59         398 my $media_type_rx = qr{^ application / vnd [.] pinto [.] v(\d+) (?:[+] .+)? $}ix;
76              
77 59   100     1652 my $accept = $self->request->header('Accept') || '';
78 59 100       16778 my $version = $accept =~ $media_type_rx ? $1 : 0;
79              
80 59 100       692 return unless my $cmp = $version <=> $PINTO_PROTOCOL_VERSION;
81              
82 3         25 my $fmt = 'Your client is too %s for this server. You must upgrade %s.';
83 3 100       18 my ($age, $component) = $cmp > 0 ? qw(new pintod) : qw(old pinto);
84 3         20 my $msg = sprintf $fmt, $age, $component;
85              
86 3         19 return [ HTTP_UNSUPPORTED_MEDIA_TYPE, [], [$msg] ];
87             }
88              
89             #-------------------------------------------------------------------------------
90              
91             sub child_proc {
92 10     10 0 403 my ( $self, $pipe, $chrome_args, $pinto_args, $action_name, $action_args ) = @_;
93              
94 10         443 my $writer = $pipe->writer;
95 10         2384 $writer->autoflush;
96              
97             # I'm not sure why, but cleanup isn't happening when we get
98             # a TERM signal from the parent process. I suspect it
99             # has something to do with File::NFSLock messing with %SIG
100 10     0   2773 local $SIG{TERM} = sub { File::Temp::cleanup; die $@ };
  0         0  
  0         0  
101              
102             ## no critic qw(PackageVar)
103 10         255 local $Pinto::Globals::current_username = delete $pinto_args->{username};
104 10         190 local $Pinto::Globals::current_time_offset = delete $pinto_args->{time_offset};
105             ## use critic;
106              
107 10         174 $chrome_args->{stdout} = $writer;
108 10         156 $chrome_args->{stderr} = $writer;
109              
110 10         1637 my $chrome = Pinto::Chrome::Net->new($chrome_args);
111 10         945 my $pinto = Pinto->new( chrome => $chrome, root => $self->root );
112              
113             my $result =
114 10     10   1723 try { $pinto->run( ucfirst $action_name => %{$action_args} ) }
  10         386  
115 10     0   447 catch { print {$writer} $_; Pinto::Result->new->failed };
  0         0  
  0         0  
  0         0  
116              
117 10 100       605 print {$writer} $PINTO_PROTOCOL_STATUS_OK . "\n" if $result->was_successful;
  5         56  
118              
119 10 0       471 exit $result->was_successful ? 0 : 1;
120             }
121              
122             #-------------------------------------------------------------------------------
123              
124             sub parent_proc {
125 46     46 0 898 my ( $self, $pipe, $child_pid ) = @_;
126              
127 46         1169 my $reader = $pipe->reader;
128 46         11184 my $select = IO::Select->new($reader);
129 46         6803 $reader->blocking(0);
130              
131             my $response = sub {
132 46     46   3342 my $responder = shift;
133              
134 46         683 my $headers = ['Content-Type' => 'text/plain'];
135 46         495 my $writer = $responder->( [ HTTP_OK, $headers ] );
136 46         11578 my $socket = $self->request->env->{'psgix.io'};
137 46         1666 my $nullmsg = $PINTO_PROTOCOL_NULL_MESSAGE . "\n";
138              
139              
140 46         801 while (1) {
141              
142 313         1075 my $input;
143 313 100       2049 if ( $select->can_read(1) ) {
144 92         39975944 $input = <$reader>; # Will block until \n
145 92 100       524 last if not defined $input; # We reached eof
146             }
147              
148 267         221244293 my $ok = eval {
149 267         11465 local $SIG{ALRM} = sub { die "Write timed out" };
  0         0  
150 267         2098 alarm(3);
151              
152 267   66     7943 $writer->write( $input || $nullmsg );
153 267         15182 1; # Write succeeded
154             };
155              
156 267         1294 alarm(0);
157 267 50 33     2251 unless ( $ok && ( !$socket || getpeername($socket) ) ) {
      33        
158 0         0 proc_terminate( $child_pid, max_wait => 10 );
159 0         0 last;
160             }
161             }
162              
163 46 50       1334 $writer->close if not $socket; # Hangs otherwise!
164 46         2930858 waitpid $child_pid, 0;
165 46         1755 };
166              
167 46         631 return $response;
168             }
169              
170             #-------------------------------------------------------------------------------
171              
172             __PACKAGE__->meta->make_immutable;
173              
174             #-------------------------------------------------------------------------------
175              
176             1;
177              
178             __END__
179              
180             =pod
181              
182             =encoding UTF-8
183              
184             =for :stopwords Jeffrey Ryan Thalhammer
185              
186             =head1 NAME
187              
188             Pinto::Server::Responder::Action - Responder for action requests
189              
190             =head1 VERSION
191              
192             version 0.14
193              
194             =head1 AUTHOR
195              
196             Jeffrey Ryan Thalhammer <jeff@stratopan.com>
197              
198             =head1 COPYRIGHT AND LICENSE
199              
200             This software is copyright (c) 2015 by Jeffrey Ryan Thalhammer.
201              
202             This is free software; you can redistribute it and/or modify it under
203             the same terms as the Perl 5 programming language system itself.
204              
205             =cut