fixed CGI::Simple warning
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Test.pm
1 package Catalyst::Test;
2
3 use strict;
4 use UNIVERSAL::require;
5 use IO::File;
6 use HTTP::Request;
7 use HTTP::Response;
8 use Socket;
9 use URI;
10
11 require Catalyst;
12
13 my $class;
14 $ENV{CATALYST_ENGINE} = 'CGI';
15 $ENV{CATALYST_TEST}   = 1;
16
17 =head1 NAME
18
19 Catalyst::Test - Test Catalyst applications
20
21 =head1 SYNOPSIS
22
23     # Helper
24     script/cgi-server.pl
25     script/server.pl
26     script/test.pl
27
28     # Tests
29     use Catalyst::Test 'TestApp';
30     request('index.html');
31     get('index.html');
32
33     # Request
34     perl -MCatalyst::Test=MyApp -e1 index.html
35
36     # Server
37     perl -MCatalyst::Test=MyApp -e1 3000
38
39 =head1 DESCRIPTION
40
41 Test Catalyst applications.
42
43 =head2 METHODS
44
45 =head3 get
46
47 Returns the content.
48
49     my $content = get('foo/bar?test=1');
50
51 =head3 request
52
53 Returns a C<HTTP::Response> object.
54
55     my $res =request('foo/bar?test=1');
56
57 =cut
58
59 {
60     no warnings;
61     CHECK {
62         if ( ( caller(0) )[1] eq '-e' ) {
63             if ( $ARGV[0] =~ /^\d+$/ ) { server( $ARGV[0] ) }
64             else { print request( $ARGV[0] || 'http://localhost' )->content }
65         }
66     }
67 }
68
69 sub import {
70     my $self = shift;
71     if ( $class = shift ) {
72         $class->require;
73         unless ( $INC{'Test/Builder.pm'} ) {
74             die qq/Couldn't load "$class", "$@"/ if $@;
75         }
76         my $caller = caller(0);
77         no strict 'refs';
78         *{"$caller\::request"} = \&request;
79         *{"$caller\::get"} = sub { request(@_)->content };
80     }
81 }
82
83 sub request {
84     my $request = shift;
85     unless ( ref $request ) {
86         $request = URI->new( $request, 'http' );
87     }
88     unless ( ref $request eq 'HTTP::Request' ) {
89         $request = HTTP::Request->new( 'GET', $request );
90     }
91     local ( *STDIN, *STDOUT );
92     my %clean  = %ENV;
93     my $output = '';
94     $ENV{CONTENT_TYPE}   ||= $request->header('Content-Type')   || '';
95     $ENV{CONTENT_LENGTH} ||= $request->header('Content-Length') || '';
96     $ENV{GATEWAY_INTERFACE} ||= 'CGI/1.1';
97     $ENV{HTTP_USER_AGENT}   ||= 'Catalyst';
98     $ENV{HTTP_HOST}         ||= $request->uri->host || 'localhost';
99     $ENV{QUERY_STRING}      ||= $request->uri->query || '';
100     $ENV{REQUEST_METHOD}    ||= $request->method;
101     $ENV{SCRIPT_NAME}       ||= $request->uri->path || '/';
102     $ENV{SERVER_NAME}       ||= $request->uri->host || 'localhost';
103     $ENV{SERVER_PORT}       ||= $request->uri->port;
104     $ENV{SERVER_PROTOCOL}   ||= 'HTTP/1.1';
105
106     for my $field ( $request->header_field_names ) {
107         if ( $field =~ /^Content-(Length|Type)$/ ) {
108             next;
109         }
110         $field =~ s/-/_/g;
111         $ENV{ 'HTTP_' . uc($field) } = $request->header($field);
112     }
113     if ( $request->content_length ) {
114         my $body = IO::File->new_tmpfile;
115         $body->print( $request->content ) or die $!;
116         $body->seek( 0, SEEK_SET ) or die $!;
117         open( STDIN, "<&=", $body->fileno )
118           or die("Failed to dup \$body: $!");
119     }
120     open( STDOUT, '>', \$output );
121     $class->handler;
122     %ENV = %clean;
123     return HTTP::Response->parse($output);
124 }
125
126 =head3 server
127
128 Starts a testserver.
129
130     Catalyst::Test::server(3000);
131
132 =cut
133
134 sub server {
135     my ( $port, $script ) = @_;
136
137     # Listen
138     my $tcp = getprotobyname('tcp');
139     socket( HTTPDaemon, PF_INET, SOCK_STREAM, $tcp ) or die $!;
140     setsockopt( HTTPDaemon, SOL_SOCKET, SO_REUSEADDR, pack( "l", 1 ) )
141       or warn $!;
142     bind( HTTPDaemon, sockaddr_in( $port, INADDR_ANY ) ) or die $!;
143     listen( HTTPDaemon, SOMAXCONN ) or die $!;
144
145     print "You can connect to your server at http://localhost:$port\n";
146
147     # Process
148     my %clean = %ENV;
149     for ( ; accept( Remote, HTTPDaemon ) ; close Remote ) {
150         *STDIN  = *Remote;
151         *STDOUT = *Remote;
152         my $remote_sockaddr = getpeername(STDIN);
153         my ( undef, $iaddr ) = sockaddr_in($remote_sockaddr);
154         my $peername = gethostbyaddr( $iaddr, AF_INET ) || "localhost";
155         my $peeraddr = inet_ntoa($iaddr) || "127.0.0.1";
156         my $local_sockaddr = getsockname(STDIN);
157         my ( undef, $localiaddr ) = sockaddr_in($local_sockaddr);
158         my $localname = gethostbyaddr( $localiaddr, AF_INET ) || 'localhost';
159         my $localaddr = inet_ntoa($localiaddr) || '127.0.0.1';
160         my $chunk;
161
162         while ( sysread( STDIN, my $buff, 1 ) ) {
163             last if $buff eq "\n";
164             $chunk .= $buff;
165         }
166         my ( $method, $request_uri, $proto, undef ) = split /\s+/, $chunk;
167         my ( $file, undef, $query_string ) =
168           ( $request_uri =~ /([^?]*)(\?(.*))?/ );
169         last if ( $method !~ /^(GET|POST|HEAD)$/ );
170         %ENV = %clean;
171
172         $chunk = '';
173         while ( sysread( STDIN, my $buff, 1 ) ) {
174             if ( $buff eq "\n" ) {
175                 $chunk =~ s/[\r\l\n\s]+$//;
176                 if ( $chunk =~ /^([\w\-]+): (.+)/i ) {
177                     my $tag = uc($1);
178                     $tag =~ s/^COOKIES$/COOKIE/;
179                     my $val = $2;
180                     $tag =~ s/-/_/g;
181                     $tag = "HTTP_" . $tag
182                       unless ( grep /^$tag$/, qw(CONTENT_LENGTH CONTENT_TYPE) );
183                     if ( $ENV{$tag} ) { $ENV{$tag} .= "; $val" }
184                     else { $ENV{$tag} = $val }
185                 }
186                 last if $chunk =~ /^$/;
187                 $chunk = '';
188             }
189             else { $chunk .= $buff }
190         }
191         $ENV{SERVER_PROTOCOL} = $proto;
192         $ENV{SERVER_PORT}     = $port;
193         $ENV{SERVER_NAME}     = $localname;
194         $ENV{SERVER_URL}      = "http://$localname:$port/";
195         $ENV{PATH_INFO}       = $file;
196         $ENV{REQUEST_URI}     = $request_uri;
197         $ENV{REQUEST_METHOD}  = $method;
198         $ENV{REMOTE_ADDR}     = $peeraddr;
199         $ENV{REMOTE_HOST}     = $peername;
200         $ENV{QUERY_STRING}    = $query_string || '';
201         $ENV{CONTENT_TYPE}    ||= 'multipart/form-data';
202         $ENV{SERVER_SOFTWARE} ||= "Catalyst/$Catalyst::VERSION";
203         $script ? print STDOUT `$script` : $class->run;
204     }
205 }
206
207 =head1 SEE ALSO
208
209 L<Catalyst>.
210
211 =head1 AUTHOR
212
213 Sebastian Riedel, C<sri@cpan.org>
214
215 =head1 COPYRIGHT
216
217 This program is free software, you can redistribute it and/or modify it under
218 the same terms as Perl itself.
219
220 =cut
221
222 1;