File Coverage

blib/lib/YATT/Lite/WebMVC0/SiteApp/FCGI.pm
Criterion Covered Total %
statement 15 92 16.3
branch 0 44 0.0
condition 0 20 0.0
subroutine 5 14 35.7
pod 0 1 0.0
total 20 171 11.7


line stmt bran cond sub pod time code
1             package YATT::Lite::WebMVC0::SiteApp::FCGI;
2             # -*- coding: utf-8 -*-
3 1     1   2611 use strict;
  1         3  
  1         29  
4 1     1   5 use warnings qw(FATAL all NONFATAL misc);
  1         3  
  1         30  
5              
6 1     1   60 use FCGI;
  1         1226  
  1         24  
7              
8 1     1   6 use YATT::Lite::WebMVC0::SiteApp; # To make lint happy, this is required.
  1         3  
  1         7  
9 1     1   6 use YATT::Lite::WebMVC0::SiteApp::CGI;
  1         3  
  1         875  
10              
11             package YATT::Lite::WebMVC0::SiteApp;
12              
13              
14             ########################################
15             #
16             # FastCGI support with auto_exit, based on PSGI mode.
17             #
18             # Many parts are stolen from Plack::Handler::FCGI.
19             #
20             ########################################
21              
22             sub _prepare_config_for_fcgi {
23 0     0     (my MY $self, my ($opts)) = @_;
24              
25 0           $self->{cf_is_psgi} = 1;
26              
27             # In suexec fcgi, $0 will not be absolute path.
28 0   0       $self->{cf_progname} //= do {
29 0 0 0       if (-r (my $fn = delete $opts->{progname} || $0)) {
30 0           $self->rel2abs($fn);
31             } else {
32 0           croak "progname is empty!";
33             }
34             };
35              
36 0 0         if ((my $dn = $self->{cf_progname}) =~ s{/html/cgi-bin/[^/]+$}{}) {
37 0   0       $self->{cf_app_root} //= $dn;
38 0   0       $self->{cf_doc_root} //= "$dn/html";
39 0           push @{$self->{tmpldirs}}, $self->{cf_doc_root}
40 0 0 0       unless $self->{tmpldirs} and @{$self->{tmpldirs}};
  0            
41             #print STDERR "Now:", terse_dump($self->{cf_app_root}, $self->{cf_doc_root}
42             # , $self->{tmpldirs}), "\n";
43             }
44             }
45              
46             # callas_fcgi() and runas_fcgi() is designed for single process app.
47             # If you want psgi.multiprocess, use Plack's own FCGI instead.
48              
49             #
50             # Check timestamp for $self->{cf_progname} and exit when modified.
51             # (Outer processmanager is responsible to restart).
52             #
53             sub _callas_fcgi {
54 0     0     (my MY $self, my $app, my $fhset, my Env $init_env, my ($args, %opts)) = @_;
55             # $fhset is either stdout or [\*STDIN, \*STDOUT, \*STDERR].
56             # $init_env is just discarded.
57             # $args = \@ARGV
58             # %opts is fcgi specific options.
59              
60 0           $self->_prepare_config_for_fcgi(\%opts);
61              
62 0           my $dir = dirname($self->{cf_progname});
63 0           my $age = -M $self->{cf_progname};
64              
65             my ($stdin, $stdout, $stderr) = ref $fhset eq 'ARRAY' ? @$fhset
66             : (\*STDIN, $fhset
67 0 0 0       , ((delete $opts{isolate_stderr}) // 1) ? \*STDERR : $fhset);
    0          
68              
69 0           my $sock = do {
70 0 0         if (my $sockfile = delete $opts{listen}) {
71 0 0         unless (-e (my $d = dirname($sockfile))) {
72 0           require File::Path;
73 0 0         File::Path::make_path($d)
74             or die "Can't mkdir $d: $!";
75             }
76 0 0         FCGI::OpenSocket($sockfile, 100)
77             or die "Can't open FCGI socket '$sockfile': $!";
78             } else {
79 0           0;
80             }
81             };
82              
83 0           my %env;
84             my $request = FCGI::Request
85             ($stdin, $stdout, $stderr
86 0 0         , \%env, $sock, $opts{nointr} ? 0 :&FCGI::FAIL_ACCEPT_ON_INTR);
87              
88 0 0         if (keys %opts) {
89 0           croak "Unknown options: ".join(", ", sort keys %opts);
90             }
91              
92 0     0     local $self->{cf_at_done} = sub {die \"DONE"};
  0            
93 0           local $SIG{PIPE} = 'IGNORE';
94 0           while ($request->Accept >= 0) {
95 0           my Env $env = $self->psgi_fcgi_newenv(\%env, $stdin, $stderr);
96              
97 0 0         if (-e "$dir/.htdebug_env") {
98 0           $self->printenv($stdout, $env);
99 0           next;
100             }
101              
102 0           my $res;
103 0 0   0     if (my $err = catch { $res = $app->($env) }) {
  0            
104             # XXX: Should I do error specific things?
105 0           $res = $err;
106             }
107              
108 0 0         unless (defined $res) {
    0          
    0          
    0          
109 0           die "Empty response";
110             }
111 0           elsif (ref $res eq 'ARRAY') {
112 0           $self->fcgi_handle_response($res);
113             }
114 0           elsif (ref $res eq 'CODE') {
115             $res->(sub {
116 0     0     $self->fcgi_handle_response($_[0]);
117 0           });
118             }
119 0 0         elsif (not ref $res or UNIVERSAL::can($res, 'message')) {
120 0 0         print $stderr $res if $$stderr ne $stdout;
121 0 0         if ($self->is_debug_allowed($env)) {
122 0           $self->cgi_process_error($res, undef, $stdout, $env);
123             } else {
124 0           $self->cgi_process_error("Application error", undef, $stdout, $env);
125             }
126             }
127             else {
128 0           die "Bad response $res";
129             }
130              
131 0           $request->Finish;
132              
133             # Exit if bootscript is modified.
134 0 0 0       last if -e $self->{cf_progname} and -M $self->{cf_progname} < $age;
135             }
136             }
137              
138             sub _runas_fcgi {
139 0     0     (my MY $self, my $fhset, my Env $init_env, my ($args, %opts)) = @_;
140              
141             # This will be called again in _callas_fcgi, but it will not harm.
142 0           $self->_prepare_config_for_fcgi(\%opts);
143              
144 0           $self->prepare_app;
145              
146 0           $self->_callas_fcgi($self->to_app, $fhset, $init_env, $args, %opts);
147             }
148              
149             *psgi_fcgi_newenv = *psgi_cgi_newenv;
150             *psgi_fcgi_newenv = *psgi_cgi_newenv;
151              
152             sub fcgi_handle_response {
153 0     0 0   my ($self, $res) = @_;
154              
155 0           require HTTP::Status;
156              
157 0           *STDOUT->autoflush(1);
158 0           binmode STDOUT;
159              
160 0           my $hdrs;
161 0           my $message = HTTP::Status::status_message($res->[0]);
162 0           $hdrs = "Status: $res->[0] $message\015\012";
163              
164 0           my $headers = $res->[1];
165 0           while (my ($k, $v) = splice @$headers, 0, 2) {
166 0           $hdrs .= "$k: $v\015\012";
167             }
168 0           $hdrs .= "\015\012";
169              
170 0           print STDOUT $hdrs;
171              
172 0     0     my $cb = sub { print STDOUT $_[0] };
  0            
173 0           my $body = $res->[2];
174 0 0         if (defined $body) {
175 0           Plack::Util::foreach($body, $cb);
176             }
177             else {
178             return Plack::Util::inline_object
179             write => $cb,
180 0     0     close => sub { };
181             }
182             }
183              
184             &YATT::Lite::Breakpoint::break_load_dispatcher_fcgi;
185              
186             1;