File Coverage

blib/lib/PAGI/App/WrapCGI.pm
Criterion Covered Total %
statement 46 59 77.9
branch 9 22 40.9
condition 12 24 50.0
subroutine 6 6 100.0
pod 0 2 0.0
total 73 113 64.6


line stmt bran cond sub pod time code
1             package PAGI::App::WrapCGI;
2             $PAGI::App::WrapCGI::VERSION = '0.002000';
3 3     3   539877 use strict;
  3         6  
  3         105  
4 3     3   15 use warnings;
  3         3  
  3         174  
5 3     3   15 use Future::AsyncAwait;
  3         3  
  3         15  
6              
7             =head1 NAME
8              
9             PAGI::App::WrapCGI - Execute CGI scripts as PAGI apps
10              
11             =head1 SYNOPSIS
12              
13             use PAGI::App::WrapCGI;
14              
15             my $app = PAGI::App::WrapCGI->new(
16             script => '/path/to/script.cgi',
17             )->to_app;
18              
19             =cut
20              
21             sub new {
22 2     2 0 856390 my ($class, %args) = @_;
23              
24             return bless {
25             script => $args{script},
26 2   50     234 timeout => $args{timeout} // 30,
27             }, $class;
28             }
29              
30             sub to_app {
31 2     2 0 42 my ($self) = @_;
32              
33 2         66 my $script = $self->{script};
34 2         8 my $timeout = $self->{timeout};
35              
36 2     2   278 return async sub {
37 2         10 my ($scope, $receive, $send) = @_;
38 2 50       26 die "Unsupported scope type: $scope->{type}" if $scope->{type} ne 'http';
39              
40             # Build CGI environment
41             my %env = (
42             REQUEST_METHOD => $scope->{method},
43             SCRIPT_NAME => $scope->{root_path} // '',
44             PATH_INFO => $scope->{path},
45             QUERY_STRING => $scope->{query_string} // '',
46             SERVER_PROTOCOL => 'HTTP/' . ($scope->{http_version} // '1.1'),
47             SERVER_NAME => $scope->{server}[0] // 'localhost',
48             SERVER_PORT => $scope->{server}[1] // 80,
49             REMOTE_ADDR => $scope->{client}[0] // '',
50 2   50     412 REMOTE_PORT => $scope->{client}[1] // 0,
      50        
      50        
      50        
      50        
      50        
      50        
51             GATEWAY_INTERFACE => 'CGI/1.1',
52             );
53              
54             # Add headers
55 2   50     10 for my $h (@{$scope->{headers} // []}) {
  2         22  
56 0         0 my ($name, $value) = @$h;
57 0         0 my $key = uc($name);
58 0         0 $key =~ s/-/_/g;
59 0 0       0 if ($key eq 'CONTENT_TYPE') {
    0          
60 0         0 $env{CONTENT_TYPE} = $value;
61             } elsif ($key eq 'CONTENT_LENGTH') {
62 0         0 $env{CONTENT_LENGTH} = $value;
63             } else {
64 0         0 $env{"HTTP_$key"} = $value;
65             }
66             }
67              
68             # Collect body
69 2         26 my $body = '';
70 2         8 while (1) {
71 2         14 my $event = await $receive->();
72 2 50       386 last if $event->{type} ne 'http.request';
73 2   50     20 $body .= $event->{body} // '';
74 2 50       22 last unless $event->{more};
75             }
76              
77             # Execute CGI
78 2         210 local %ENV = %env;
79 2         7690 my $pid = open my $fh, '-|';
80              
81 2 50       184 unless (defined $pid) {
82 0         0 await $send->({
83             type => 'http.response.start',
84             status => 500,
85             headers => [['content-type', 'text/plain']],
86             });
87 0         0 await $send->({ type => 'http.response.body', body => 'Internal Server Error', more => 0 });
88 0         0 return;
89             }
90              
91 2 100       128 if ($pid == 0) {
92             # Child - run CGI
93 1 50       63 if (length $body) {
94 0         0 open my $stdin, '<', \$body;
95 0         0 *STDIN = $stdin;
96             }
97 1 0       0 exec($script) or exit(1);
98             }
99              
100             # Parent - read output
101 1         71 local $/;
102 1         259778 my $output = <$fh>;
103 1         123 close $fh;
104              
105             # Parse CGI output
106 1   50     119 my ($headers, $resp_body) = split /\r?\n\r?\n/, $output // '', 2;
107 1         25 my @resp_headers;
108 1         11 my $status = 200;
109              
110 1   50     27 for my $line (split /\r?\n/, $headers // '') {
111 1         30 my ($name, $value) = split /:\s*/, $line, 2;
112 1 50       13 next unless $name;
113 1 50       52 if (lc($name) eq 'status') {
114 0         0 ($status) = $value =~ /^(\d+)/;
115             } else {
116 1         32 push @resp_headers, [lc($name), $value];
117             }
118             }
119              
120             await $send->({
121             type => 'http.response.start',
122             status => $status,
123             headers => \@resp_headers,
124 1         58 });
125 1         309 await $send->({ type => 'http.response.body', body => $resp_body // '', more => 0 });
126 2         192 };
127             }
128              
129             1;
130              
131             __END__