File Coverage

blib/lib/HTTP/Message/PSGI.pm
Criterion Covered Total %
statement 86 113 76.1
branch 32 50 64.0
condition 12 16 75.0
subroutine 17 21 80.9
pod 2 4 50.0
total 149 204 73.0


line stmt bran cond sub pod time code
1             package HTTP::Message::PSGI;
2 71     71   951727 use strict;
  71         190  
  71         3372  
3 71     71   446 use warnings;
  71         174  
  71         4592  
4 71     71   3526 use parent qw(Exporter);
  71         2295  
  71         526  
5             our @EXPORT = qw( req_to_psgi res_from_psgi );
6              
7 71     71   8684 use Carp ();
  71         186  
  71         2327  
8 71     71   5163 use HTTP::Status qw(status_message);
  71         61679  
  71         13334  
9 71     71   4186 use URI::Escape ();
  71         25568  
  71         2149  
10 71     71   13912 use Plack::Util;
  71         204  
  71         2524  
11 71     71   32085 use Try::Tiny;
  71         148643  
  71         125663  
12              
13             my $TRUE = (1 == 1);
14             my $FALSE = !$TRUE;
15              
16             sub req_to_psgi {
17 347     347 1 1697412 my $req = shift;
18              
19 347 50   347   2550 unless (try { $req->isa('HTTP::Request') }) {
  347         13356  
20 0         0 Carp::croak("Request is not HTTP::Request: $req");
21             }
22              
23             # from HTTP::Request::AsCGI
24 347         7173 my $host = $req->header('Host');
25 347         25051 my $uri = $req->uri->clone;
26 347 100       6975 $uri->scheme('http') unless $uri->scheme;
27 347 100       11684 $uri->host('localhost') unless $uri->host;
28 347 50       13246 $uri->port(80) unless $uri->port;
29 347 100 66     11242 $uri->host_port($host) unless !$host || ( $host eq $uri->host_port );
30              
31 347         1162 my $input;
32 347         1554 my $content = $req->content;
33 347 50       5253 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         5107 open $input, "<", \$content;
42 347 100       1795 $req->content_length(length $content)
43             unless defined $req->content_length;
44             }
45              
46 347 50 100     32322 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         62127 for my $field ( $req->headers->header_field_names ) {
71 395         11413 my $key = uc("HTTP_$field");
72 395         859 $key =~ tr/-/_/;
73 395 100       3037 $key =~ s/^HTTP_// if $field =~ /^Content-(Length|Type)$/;
74              
75 395 50       1340 unless ( exists $env->{$key} ) {
76 395         1110 $env->{$key} = $req->headers->header($field);
77             }
78             }
79              
80 347 50       16840 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     2024 if (!defined($env->{HTTP_HOST}) && $req->uri->can('host')) {
86 337         4673 $env->{HTTP_HOST} = $req->uri->host;
87 337 100       13500 $env->{HTTP_HOST} .= ':' . $req->uri->port
88             if $req->uri->port ne $req->uri->default_port;
89             }
90              
91 347         16115 return $env;
92             }
93              
94             sub res_from_psgi {
95 329     329 1 765 my ($psgi_res) = @_;
96              
97 329         2388 require HTTP::Response;
98              
99 329         683 my $res;
100 329 100       1043 if (ref $psgi_res eq 'ARRAY') {
    100          
101 292         921 _res_from_psgi($psgi_res, \$res);
102             } elsif (ref $psgi_res eq 'CODE') {
103             $psgi_res->(sub {
104 33     33   145 _res_from_psgi($_[0], \$res);
105 35         192 });
106             } else {
107 2 100       313 Carp::croak("Bad response: ", defined $psgi_res ? $psgi_res : 'undef');
108             }
109              
110 325         2045 return $res;
111             }
112              
113             sub _res_from_psgi {
114 325     325   580 my ($status, $headers, $body) = @{+shift};
  325         948  
115 325         607 my $res_ref = shift;
116              
117             my $convert_resp = sub {
118 325     325   1660 my $res = HTTP::Response->new($status);
119 325         21223 $res->message(status_message($status));
120 325 100       5350 $res->headers->header(@$headers) if @$headers;
121              
122 325 100       26592 if (ref $body eq 'ARRAY') {
123 294         2237 $res->content(join '', grep defined, @$body);
124             } else {
125 31         230 local $/ = \4096;
126 31         53 my $content = '';
127 31         1314 while (defined(my $buf = $body->getline)) {
128 93         1426 $content .= $buf;
129             }
130 31         261 $body->close;
131 31         3107 $res->content($content);
132             }
133              
134 325         8541 ${ $res_ref } = $res;
  325         810  
135              
136 325         3024 return;
137 325         2003 };
138              
139 325 100       957 if (!defined $body) {
140 15         52 $body = [];
141             my $o = Plack::Util::inline_object
142 21     21   186 write => sub { push @$body, @_ },
143 15         125 close => $convert_resp;
144              
145 15         50 return $o;
146             }
147              
148 310         779 $convert_resp->();
149             }
150              
151             sub HTTP::Request::to_psgi {
152 291     291 0 679377 req_to_psgi(@_);
153             }
154              
155             sub HTTP::Response::from_psgi {
156 292     292 0 3279 my $class = shift;
157 292         1062 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__