File Coverage

blib/lib/Net/Server/PSGI.pm
Criterion Covered Total %
statement 76 96 79.1
branch 10 24 41.6
condition 4 18 22.2
subroutine 14 18 77.7
pod 7 12 58.3
total 111 168 66.0


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::PSGI - Extensible Perl HTTP PSGI base server
4             #
5             # Copyright (C) 2011-2022
6             #
7             # Paul Seamons
8             #
9             # This package may be distributed under the terms of either the
10             # GNU General Public License
11             # or the
12             # Perl Artistic License
13             #
14             ################################################################
15              
16             package Net::Server::PSGI;
17              
18 2     2   13372 use strict;
  2         6  
  2         84  
19 2     2   20 use base qw(Net::Server::HTTP);
  2         12  
  2         1310  
20 2     2   14 use Scalar::Util qw(blessed);
  2         4  
  2         1804  
21              
22 1     1 0 2 sub net_server_type { __PACKAGE__ }
23              
24             sub options {
25 2     2 0 5 my $self = shift;
26 2         41 my $ref = $self->SUPER::options(@_);
27 2         5 my $prop = $self->{'server'};
28 2         14 $ref->{$_} = \$prop->{$_} for qw(app);
29 2         5 return $ref;
30             }
31              
32             sub post_configure {
33 1     1 1 3 my $self = shift;
34 1         3 my $prop = $self->{'server'};
35              
36 1         20 $prop->{'log_handle'} = IO::Handle->new;
37 1         112 $prop->{'log_handle'}->fdopen(fileno(STDERR), "w");
38 1         76 $prop->{'no_client_stdout'} = 1;
39              
40 1         20 $self->SUPER::post_configure(@_);
41             }
42              
43       1     sub _tie_client_stdout {} # the client should not print directly
44              
45             sub process_request {
46 1     1 1 2 my $self = shift;
47              
48 1     0   39 local $SIG{'ALRM'} = sub { die "Server Timeout\n" };
  0         0  
49 1         3 my $ok = eval {
50 1         6 alarm($self->timeout_header);
51 1         16 $self->process_headers;
52              
53 1         5 alarm($self->timeout_idle);
54 1         14 my $env = { %ENV };
55 1         3 $env->{'psgi.version'} = [1, 0];
56 1 50 33     6 $env->{'psgi.url_scheme'} = ($ENV{'HTTPS'} && $ENV{'HTTPS'} eq 'on') ? 'https' : 'http';
57 1         2 $env->{'psgi.input'} = $self->{'server'}->{'client'};
58 1         3 $env->{'psgi.errors'} = $self->{'server'}->{'log_handle'};
59 1         3 $env->{'psgi.multithread'} = 1;
60 1         2 $env->{'psgi.multiprocess'} = 1;
61 1         2 $env->{'psgi.nonblocking'} = 1; # need to make this false if we aren't of a forking type server
62 1         2 $env->{'psgi.streaming'} = 1;
63 1         4 local %ENV;
64 1         5 $self->process_psgi_request($env);
65 1         12 alarm(0);
66 1         14 1;
67             };
68 1         5 alarm(0);
69              
70 1 50       20 if (! $ok) {
71 0   0     0 my $err = "$@" || "Something happened";
72 0         0 $self->send_500($err);
73 0         0 die $err;
74             }
75             }
76              
77             sub process_psgi_request {
78 1     1 1 3 my ($self, $env) = @_;
79 1         18 my $app = $self->find_psgi_handler($env);
80 1         5 my $resp = $app->($env);
81             return $resp->(sub {
82 0     0   0 my $resp = shift;
83 0         0 $self->print_psgi_headers($resp->[0], $resp->[1]);
84 0 0       0 return $self->{'server'}->{'client'} if @$resp == 2;
85 0         0 return $self->print_psgi_body($resp->[2]);
86 1 50       6 }) if ref($resp) eq 'CODE';
87 1         11 $self->print_psgi_headers($resp->[0], $resp->[1]);
88 1         14 $self->print_psgi_body($resp->[2]);
89             }
90              
91 1 50   1 1 6 sub find_psgi_handler { shift->app || \&psgi_echo_handler }
92              
93             sub app {
94 1     1 1 2 my $self = shift;
95 1 50       3 $self->{'server'}->{'app'} = shift if @_;
96 1         3 my $app = $self->{'server'}->{'app'};
97 1 50 33     15 if (!ref($app) && $app) {
98 0   0     0 $app = $self->{'server'}->{'app'} = eval { require CGI::Compile; CGI::Compile->compile($app) }
99             || die "Failed to compile app with CGI::Compile";
100             }
101 1         10 return $app;
102             }
103              
104             sub print_psgi_headers {
105 1     1 0 4 my ($self, $status, $headers) = @_;
106 1   50     3 $headers ||= [];
107             $self->send_status({
108             status => $status,
109 1         6 headers => [map {[@$headers[$_*2, $_*2+1]]} 0 .. $#$headers / 2],
  1         38  
110             });
111             }
112              
113             sub print_psgi_body {
114 1     1 0 4 my ($self, $body) = @_;
115 1         2 my $client = $self->{'server'}->{'client'};
116 1         3 my $request_info = $self->{'request_info'};
117 1 50 0     4 if (ref $body eq 'ARRAY') {
    0          
118 1         3 for my $chunk (@$body) {
119 1         3 $client->print($chunk);
120 1         53 $request_info->{'response_size'} += length $chunk;
121             }
122             } elsif (blessed($body) && $body->can('getline')) {
123 0         0 while (defined(my $chunk = $body->getline)) {
124 0         0 $client->print($chunk);
125 0         0 $request_info->{'response_size'} += length $chunk;
126             }
127             } else {
128 0         0 while (defined(my $chunk = <$body>)) {
129 0         0 $client->print($chunk);
130 0         0 $request_info->{'response_size'} += length $chunk;
131             }
132             }
133             }
134              
135             sub psgi_echo_handler {
136 1     1 0 2 my $env = shift;
137 1 50 33     6 if ($env->{'PATH_INFO'} && $env->{'PATH_INFO'} eq '/simple') {
138 0         0 return [200, [content_type => 'text/html'], ['Simple']];
139             }
140 1         2 my $txt = qq{
\n};
141 1 50       2 if (eval { require Data::Dumper }) {
  1         604  
142 1         6167 local $Data::Dumper::Sortkeys = 1;
143 1         4 my $form = {};
144 1 50       2 if (eval { require CGI::PSGI }) { my $q = CGI::PSGI->new($env); $form->{$_} = $q->param($_) for $q->param; }
  1         163  
  0         0  
  0         0  
145 1         28 $txt .= "
".Data::Dumper->Dump([$env, $form], ['env', 'form'])."
";
146             }
147 1         136 return [200, ['Content-type', 'text/html'], [$txt]];
148             }
149              
150 0     0 1   sub exec_cgi { die "Not implemented" }
151 0     0 1   sub exec_trusted_perl { die "Not implemented" }
152              
153             1;
154              
155             __END__