File Coverage

blib/lib/HTTP/Message/PSGI.pm
Criterion Covered Total %
statement 85 112 75.8
branch 32 50 64.0
condition 13 19 68.4
subroutine 16 20 80.0
pod 2 4 50.0
total 148 205 72.2


line stmt bran cond sub pod time code
1             package HTTP::Message::PSGI;
2 71     71   1123742 use strict;
  71         204  
  71         3001  
3 71     71   439 use warnings;
  71         206  
  71         4508  
4 71     71   4352 use parent qw(Exporter);
  71         2741  
  71         533  
5             our @EXPORT = qw( req_to_psgi res_from_psgi );
6              
7 71     71   7187 use Carp ();
  71         183  
  71         2373  
8 71     71   6630 use HTTP::Status qw(status_message);
  71         66049  
  71         12466  
9 71     71   4888 use URI::Escape ();
  71         28671  
  71         4054  
10 71     71   13967 use Plack::Util;
  71         183  
  71         2454  
11 71     71   413 use Scalar::Util ();
  71         174  
  71         115427  
12              
13             my $TRUE = (1 == 1);
14             my $FALSE = !$TRUE;
15              
16             sub req_to_psgi {
17 347     347 1 1946134 my $req = shift;
18              
19 347 50 33     3940 unless (Scalar::Util::blessed($req) && $req->isa('HTTP::Request')) {
20 0         0 Carp::croak("Request is not HTTP::Request: $req");
21             }
22              
23             # from HTTP::Request::AsCGI
24 347         1730 my $host = $req->header('Host');
25 347         25464 my $uri = $req->uri->clone;
26 347 100       6298 $uri->scheme('http') unless $uri->scheme;
27 347 100       10875 $uri->host('localhost') unless $uri->host;
28 347 50       12998 $uri->port(80) unless $uri->port;
29 347 100 66     10727 $uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
30              
31 347         1402 my $input;
32 347         1480 my $content = $req->content;
33 347 50       4811 if (ref $content eq 'CODE') {
34 0 0       0 if (defined $req->content_length) {
35 0         0 $input = HTTP::Message::PSGI::ChunkedInput->new($content);
36             } else {
37 0         0 $req->header("Transfer-Encoding" => "chunked");
38 0         0 $input = HTTP::Message::PSGI::ChunkedInput->new($content, 1);
39             }
40             } else {
41 347         5080 open $input, "<", \$content;
42 347 100       1709 $req->content_length(length $content)
43             unless defined $req->content_length;
44             }
45              
46 347 50 100     31484 my $env = {
      100        
      50        
      100        
47             PATH_INFO => URI::Escape::uri_unescape($uri->path || '/'),
48             QUERY_STRING => $uri->query || '',
49             SCRIPT_NAME => '',
50             SERVER_NAME => $uri->host,
51             SERVER_PORT => $uri->port,
52             SERVER_PROTOCOL => $req->protocol || 'HTTP/1.1',
53             REMOTE_ADDR => '127.0.0.1',
54             REMOTE_HOST => 'localhost',
55             REMOTE_PORT => int( rand(64000) + 1000 ), # not in RFC 3875
56             REQUEST_URI => $uri->path_query || '/', # not in RFC 3875
57             REQUEST_METHOD => $req->method,
58             'psgi.version' => [ 1, 1 ],
59             'psgi.url_scheme' => $uri->scheme eq 'https' ? 'https' : 'http',
60             'psgi.input' => $input,
61             'psgi.errors' => *STDERR,
62             'psgi.multithread' => $FALSE,
63             'psgi.multiprocess' => $FALSE,
64             'psgi.run_once' => $TRUE,
65             'psgi.streaming' => $TRUE,
66             'psgi.nonblocking' => $FALSE,
67             @_,
68             };
69              
70 347         58916 for my $field ( $req->headers->header_field_names ) {
71 395         10871 my $key = uc("HTTP_$field");
72 395         897 $key =~ tr/-/_/;
73 395 100       2725 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
74              
75 395 50       1120 unless ( exists $env->{$key} ) {
76 395         1001 $env->{$key} = $req->headers->header($field);
77             }
78             }
79              
80 347 50       16405 if ($env->{SCRIPT_NAME}) {
81 0         0 $env->{PATH_INFO} =~ s/^\Q$env->{SCRIPT_NAME}\E/\//;
82 0         0 $env->{PATH_INFO} =~ s/^\/+/\//;
83             }
84              
85 347 100 100     2053 if (!defined($env->{HTTP_HOST}) && $req->uri->can('host')) {
86 337         4615 $env->{HTTP_HOST} = $req->uri->host;
87 337 100       13700 $env->{HTTP_HOST} .= ':' . $req->uri->port
88             if $req->uri->port ne $req->uri->default_port;
89             }
90              
91 347         14886 return $env;
92             }
93              
94             sub res_from_psgi {
95 329     329 1 697 my ($psgi_res) = @_;
96              
97 329         2234 require HTTP::Response;
98              
99 329         545 my $res;
100 329 100       1005 if (ref $psgi_res eq 'ARRAY') {
    100          
101 292         788 _res_from_psgi($psgi_res, \$res);
102             } elsif (ref $psgi_res eq 'CODE') {
103             $psgi_res->(sub {
104 33     33   148 _res_from_psgi($_[0], \$res);
105 35         282 });
106             } else {
107 2 100       438 Carp::croak("Bad response: ", defined $psgi_res ? $psgi_res : 'undef');
108             }
109              
110 325         1826 return $res;
111             }
112              
113             sub _res_from_psgi {
114 325     325   509 my ($status, $headers, $body) = @{+shift};
  325         985  
115 325         598 my $res_ref = shift;
116              
117             my $convert_resp = sub {
118 325     325   1579 my $res = HTTP::Response->new($status);
119 325         18584 $res->message(status_message($status));
120 325 100       9538 $res->headers->header(@$headers) if @$headers;
121              
122 325 100       23181 if (ref $body eq 'ARRAY') {
123 294         2290 $res->content(join '', grep defined, @$body);
124             } else {
125 31         188 local $/ = \4096;
126 31         59 my $content = '';
127 31         1211 while (defined(my $buf = $body->getline)) {
128 93         1825 $content .= $buf;
129             }
130 31         266 $body->close;
131 31         4110 $res->content($content);
132             }
133              
134 325         7625 ${ $res_ref } = $res;
  325         706  
135              
136 325         2915 return;
137 325         1869 };
138              
139 325 100       918 if (!defined $body) {
140 15         69 $body = [];
141             my $o = Plack::Util::inline_object
142 21     21   92 write => sub { push @$body, @_ },
143 15         89 close => $convert_resp;
144              
145 15         55 return $o;
146             }
147              
148 310         637 $convert_resp->();
149             }
150              
151             sub HTTP::Request::to_psgi {
152 291     291 0 773705 req_to_psgi(@_);
153             }
154              
155             sub HTTP::Response::from_psgi {
156 292     292 0 4070 my $class = shift;
157 292         769 res_from_psgi(@_);
158             }
159              
160             package
161             HTTP::Message::PSGI::ChunkedInput;
162              
163             sub new {
164 0     0     my($class, $content, $chunked) = @_;
165              
166 0           my $content_cb;
167 0 0         if ($chunked) {
168 0           my $done;
169             $content_cb = sub {
170 0     0     my $chunk = $content->();
171 0 0         return if $done;
172 0 0         unless (defined $chunk) {
173 0           $done = 1;
174 0           return "0\015\012\015\012";
175             }
176 0 0         return '' unless length $chunk;
177 0           return sprintf('%x', length $chunk) . "\015\012$chunk\015\012";
178 0           };
179             } else {
180 0           $content_cb = $content;
181             }
182              
183 0           bless { content => $content_cb }, $class;
184             }
185              
186             sub read {
187 0     0     my $self = shift;
188              
189 0           my $chunk = $self->{content}->();
190 0 0         return 0 unless defined $chunk;
191              
192 0           $_[0] = '';
193 0   0       substr($_[0], $_[2] || 0, length $chunk) = $chunk;
194              
195 0           return length $chunk;
196             }
197              
198       0     sub close { }
199              
200             package HTTP::Message::PSGI;
201              
202             1;
203              
204             __END__