File Coverage

blib/lib/Perlbal/Plugin/PSGI.pm
Criterion Covered Total %
statement 48 146 32.8
branch 0 24 0.0
condition 0 6 0.0
subroutine 16 33 48.4
pod 0 6 0.0
total 64 215 29.7


line stmt bran cond sub pod time code
1             package Perlbal::Plugin::PSGI;
2 1     1   30899 use strict;
  1         2  
  1         38  
3 1     1   5 use warnings;
  1         1  
  1         28  
4 1     1   24 use 5.008_001;
  1         7  
  1         67  
5             our $VERSION = '0.03';
6              
7 1     1   23072 use Perlbal;
  1         364279  
  1         36  
8 1     1   936 use Plack::Util;
  1         4620  
  1         594  
9              
10             sub register {
11 0     0 0   my ($class, $svc) = @_;
12 0     0     $svc->register_hook('PSGI', 'start_http_request', sub { handle_request($svc, $_[0]); });
  0            
13             }
14              
15             sub handle_psgi_app_command {
16 0     0 0   my $mc = shift->parse(qr/^psgi_app\s*=\s*(\S+)$/, "usage: PSGI_APP=");
17 0           my ($app_path) = $mc->args;
18              
19 0           my $handler = Plack::Util::load_psgi $app_path;
20 0           my $svcname;
21 0 0 0       unless ($svcname ||= $mc->{ctx}{last_created}) {
22 0           return $mc->err("No service name in context from CREATE SERVICE or USE ");
23             }
24              
25 0           my $svc = Perlbal->service($svcname);
26 0 0         return $mc->err("Non-existent service '$svcname'") unless $svc;
27              
28 0           my $cfg = $svc->{extra_config}->{_psgi_app} = $handler;
29              
30 0           return 1;
31             }
32              
33             sub unregister {
34 0     0 0   my ($class, $svc) = @_;
35 0           $svc->unregister_hooks('PSGI');
36 0           return 1;
37             }
38              
39             sub load {
40 0     0 0   Perlbal::register_global_hook('manage_command.psgi_app', \&handle_psgi_app_command);
41 0     0     Perlbal::Service::add_role('psgi_server', sub { Perlbal::Plugin::PSGI::Client->new(@_) });
  0            
42 0           return 1;
43             }
44              
45             sub unload {
46 0     0 0   Perlbal::unregister_global_hook('manage_command.psgi_app');
47 0           Perlbal::Service::remove_role('psgi_server');
48 0           return 1;
49             }
50              
51             our $HR_RECURSION = 0;
52              
53             sub handle_request {
54 0     0 0   my $svc = shift;
55 0           my $pb = shift;
56              
57 0 0         return 0 if $HR_RECURSION;
58 0           local $HR_RECURSION = 1;
59              
60 0           my $app = $svc->{extra_config}->{_psgi_app};
61 0 0         unless (defined $app) {
62 0           return $pb->send_response(500, "No PSGI app is configured for this service");
63             }
64              
65 0           Perlbal::Plugin::PSGI::Client->new_from_base($pb);
66              
67 0           return 1;
68             }
69              
70             package Perlbal::Plugin::PSGI::Client;
71              
72 1     1   8 use strict;
  1         2  
  1         31  
73 1     1   6 use warnings;
  1         2  
  1         46  
74 1     1   7 use base "Perlbal::ClientProxy";
  1         2  
  1         398  
75 1     1   15 use fields;
  1         1  
  1         9  
76              
77             sub request_backend {
78 0     0     my Perlbal::Plugin::PSGI::Client $self = shift;
79 0           my $backend = Perlbal::Plugin::PSGI::Backend->new;
80 0           $backend->assign_client($self);
81             }
82              
83             package Perlbal::Plugin::PSGI::Backend;
84              
85 1     1   93 use strict;
  1         2  
  1         47  
86 1     1   5 use warnings;
  1         2  
  1         32  
87              
88 1     1   5 use Perlbal::ClientHTTPBase;
  1         2  
  1         31  
89 1     1   5 use Perlbal::Service;
  1         2  
  1         27  
90              
91 1     1   13 use Plack::Util;
  1         2  
  1         29  
92 1     1   874 use Plack::HTTPParser qw(parse_http_request);
  1         18960  
  1         76  
93 1     1   1879 use HTTP::Status;
  1         5738  
  1         1628  
94              
95             sub new {
96 0     0     my $class = shift;
97 0   0       my $self = bless {}, (ref $class || $class);
98 0           $self->{input} = [];
99 0           $self->{remaining} = 0;
100 0           return $self;
101             }
102              
103 0     0     sub close {
104             # Do we need to do any cleanup?
105             }
106              
107 0     0     sub forget_client {
108             # Do we need to do any cleanup?
109             }
110              
111             sub write {
112 0     0     my $self = shift;
113 0           my $bufref = shift;
114 0           my $input = $self->{input};
115 0           push @$input, $bufref;
116 0           $self->{remaining} -= length($$bufref);
117 0 0         return if $self->{remaining};
118 0           $self->run_request;
119             }
120              
121             sub assign_client {
122 0     0     my $self = shift;
123 0           my Perlbal::ClientHTTPBase $pb = shift;
124 0           my Perlbal::Service $svc = $pb->{service};
125 0           $self->{client} = $pb;
126 0           $pb->backend($self);
127              
128 0 0         my $hdr = $pb->{req_headers} or return 0;
129 0 0         my ($server_name, $server_port) = split /:/, ($pb->{selector_svc} ? $pb->{selector_svc}->{listen} : $svc->{listen});
130              
131             my $env = $self->{env} = {
132             'psgi.version' => [ 1, 0 ],
133 0     0     'psgi.errors' => Plack::Util::inline_object(print => sub { Perlbal::log('error', @_) }),
  0            
134             'psgi.url_scheme' => 'http',
135             'psgi.nonblocking' => Plack::Util::TRUE,
136             'psgi.run_once' => Plack::Util::FALSE,
137             'psgi.multithread' => Plack::Util::FALSE,
138             'psgi.multiprocess' => Plack::Util::FALSE,
139             'psgi.streaming' => Plack::Util::TRUE,
140             REMOTE_ADDR => $pb->{peer_ip},
141             SERVER_NAME => $server_name,
142             SERVER_PORT => $server_port,
143             };
144              
145 0           parse_http_request($pb->{headers_string}, $env);
146              
147 0 0         if ($env->{CONTENT_LENGTH}) {
148 0           $self->{remaining} = $env->{CONTENT_LENGTH};
149             } else {
150 0           $self->run_request;
151             }
152             }
153              
154             sub run_request {
155 0     0     my $self = shift;
156              
157 0           my Perlbal::ClientHTTPBase $pb = $self->{client};
158 0           my Perlbal::Service $svc = $pb->{service};
159 0           my $app = $svc->{extra_config}->{_psgi_app};
160 0           my $env = $self->{env};
161 0           my $buf_ref = \join('', map { $$_ } @{$self->{input}});
  0            
  0            
162 0           open my $input, "<", $buf_ref;
163 0           $env->{'psgi.input'} = $input;
164              
165             my $responder = sub {
166 0     0     my $res = shift;
167              
168 0           my $hd = $pb->{res_headers} = Perlbal::HTTPHeaders->new_response($res->[0]);
169 0           my %seen;
170 0           while (my($k, $v) = splice @{$res->[1]}, 0, 2) {
  0            
171 0 0         if ($seen{lc($k)}++) {
172 0           my $newvalue = $hd->header($k) . "\015\012$k: $v";
173 0           $hd->header($k, $newvalue);
174             } else {
175 0           $hd->header($k, $v);
176             }
177             }
178              
179 0           $pb->setup_keepalive($hd);
180              
181 0           $pb->state('xfer_resp');
182 0           $pb->tcp_cork(1); # cork writes to self
183 0           $pb->write($hd->to_string_ref);
184              
185 0 0         if (!defined $res->[2]) {
    0          
186             return Plack::Util::inline_object
187 0           write => sub { $pb->write(@_) },
188 0           close => sub { $pb->write(sub { $pb->http_response_sent}) };
  0            
  0            
189             } elsif (Plack::Util::is_real_fh($res->[2])) {
190 0           $pb->reproxy_fh($res->[2], -s $res->[2]);
191             } else {
192 0           Plack::Util::foreach($res->[2], sub { $pb->write(@_) });
  0            
193 0           $pb->write(sub { $pb->http_response_sent });
  0            
194             }
195 0           };
196              
197 0           my $res = Plack::Util::run_app $app, $env;
198 0 0         ref $res eq 'CODE' ? $res->($responder) : $responder->($res);
199             }
200              
201             1;
202              
203             =head1 NAME
204              
205             Perlbal::Plugin::PSGI - PSGI web server on Perlbal
206              
207             =head1 SYNOPSIS
208              
209             LOAD PSGI
210             CREATE SERVICE psgi
211             SET role = psgi_server
212             SET listen = 127.0.0.1:80
213             PSGI_APP = /path/to/app.psgi
214             ENABLE psgi
215              
216             =head1 DESCRIPTION
217              
218             This is a Perlbal plugin to allow any PSGI application run natively
219             inside Perlbal process.
220              
221             =head1 COPYRIGHT
222              
223             Copyright 2009- Tatsuhiko Miyagawa
224              
225             =head1 AUTHOR
226              
227             Tatsuhiko Miyagawa
228              
229             Jonathan Steinert
230              
231             Based on Perlbal::Plugin::Cgilike written by Martin Atkins.
232              
233             =head1 LICENSE
234              
235             This module is licensed under the same terms as Perl itself.
236              
237             =cut