Added prepare_body_chunk method for upload progress support
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Engine.pm
CommitLineData
fc7ec1d9 1package Catalyst::Engine;
2
3use strict;
fbcc39ad 4use base 'Class::Accessor::Fast';
6dc87a0f 5use CGI::Cookie;
fc7ec1d9 6use Data::Dumper;
7use HTML::Entities;
fbcc39ad 8use HTTP::Body;
fc7ec1d9 9use HTTP::Headers;
fbcc39ad 10
11# input position and length
12__PACKAGE__->mk_accessors( qw/read_position read_length/ );
13
14# Stringify to class
15use overload '""' => sub { return ref shift }, fallback => 1;
fc7ec1d9 16
4bd82c41 17# Amount of data to read from input on each pass
18our $CHUNKSIZE = 4096;
19
fc7ec1d9 20=head1 NAME
21
22Catalyst::Engine - The Catalyst Engine
23
24=head1 SYNOPSIS
25
26See L<Catalyst>.
27
28=head1 DESCRIPTION
29
23f9d934 30=head1 METHODS
fc7ec1d9 31
23f9d934 32=over 4
33
fbcc39ad 34=item $self->finalize_output
cd3bb248 35
36<obsolete>, see finalize_body
37
fbcc39ad 38=item $self->finalize_body($c)
06e1b616 39
fbcc39ad 40Finalize body. Prints the response output.
06e1b616 41
42=cut
43
fbcc39ad 44sub finalize_body {
45 my ( $self, $c ) = @_;
46
47 $self->write( $c, $c->response->output );
48}
6dc87a0f 49
fbcc39ad 50=item $self->finalize_cookies($c)
6dc87a0f 51
52=cut
53
54sub finalize_cookies {
fbcc39ad 55 my ( $self, $c ) = @_;
6dc87a0f 56
fbcc39ad 57 my @cookies;
6dc87a0f 58 while ( my ( $name, $cookie ) = each %{ $c->response->cookies } ) {
fbcc39ad 59
6dc87a0f 60 my $cookie = CGI::Cookie->new(
61 -name => $name,
62 -value => $cookie->{value},
63 -expires => $cookie->{expires},
64 -domain => $cookie->{domain},
65 -path => $cookie->{path},
66 -secure => $cookie->{secure} || 0
67 );
68
fbcc39ad 69 push @cookies, $cookie->as_string;
6dc87a0f 70 }
6dc87a0f 71
fbcc39ad 72 if (@cookies) {
73 $c->res->headers->push_header( 'Set-Cookie' => join ',', @cookies );
74 }
75}
969647fd 76
fbcc39ad 77=item $self->finalize_error($c)
969647fd 78
79=cut
80
81sub finalize_error {
fbcc39ad 82 my ( $self, $c ) = @_;
969647fd 83
84 $c->res->headers->content_type('text/html');
85 my $name = $c->config->{name} || 'Catalyst Application';
86
87 my ( $title, $error, $infos );
88 if ( $c->debug ) {
62d9b030 89
90 # For pretty dumps
91 local $Data::Dumper::Terse = 1;
92 $error = join '',
93 map { '<code class="error">' . encode_entities($_) . '</code>' }
94 @{ $c->error };
969647fd 95 $error ||= 'No output';
96 $title = $name = "$name on Catalyst $Catalyst::VERSION";
fbcc39ad 97
98 # Don't show context in the dump
99 delete $c->req->{_context};
100 delete $c->res->{_context};
101
102 # Don't show body parser in the dump
103 delete $c->req->{_body};
104
105 # Don't show response header state in dump
106 delete $c->res->{_finalized_headers};
107
969647fd 108 my $req = encode_entities Dumper $c->req;
109 my $res = encode_entities Dumper $c->res;
110 my $stash = encode_entities Dumper $c->stash;
111 $infos = <<"";
112<br/>
113<b><u>Request</u></b><br/>
114<pre>$req</pre>
115<b><u>Response</u></b><br/>
116<pre>$res</pre>
117<b><u>Stash</u></b><br/>
118<pre>$stash</pre>
119
120 }
121 else {
122 $title = $name;
123 $error = '';
124 $infos = <<"";
125<pre>
126(en) Please come back later
127(de) Bitte versuchen sie es spaeter nocheinmal
128(nl) Gelieve te komen later terug
129(no) Vennligst prov igjen senere
130(fr) Veuillez revenir plus tard
131(es) Vuelto por favor mas adelante
132(pt) Voltado por favor mais tarde
fbcc39ad 133(it) Ritornato prego piĆ¹ successivamente
969647fd 134</pre>
135
136 $name = '';
137 }
e060fe05 138 $c->res->body( <<"" );
969647fd 139<html>
140<head>
141 <title>$title</title>
142 <style type="text/css">
143 body {
144 font-family: "Bitstream Vera Sans", "Trebuchet MS", Verdana,
145 Tahoma, Arial, helvetica, sans-serif;
146 color: #ddd;
147 background-color: #eee;
148 margin: 0px;
149 padding: 0px;
150 }
151 div.box {
152 background-color: #ccc;
153 border: 1px solid #aaa;
154 padding: 4px;
155 margin: 10px;
156 -moz-border-radius: 10px;
157 }
158 div.error {
159 background-color: #977;
160 border: 1px solid #755;
161 padding: 8px;
162 margin: 4px;
163 margin-bottom: 10px;
164 -moz-border-radius: 10px;
165 }
166 div.infos {
167 background-color: #797;
168 border: 1px solid #575;
169 padding: 8px;
170 margin: 4px;
171 margin-bottom: 10px;
172 -moz-border-radius: 10px;
173 }
174 div.name {
175 background-color: #779;
176 border: 1px solid #557;
177 padding: 8px;
178 margin: 4px;
179 -moz-border-radius: 10px;
180 }
7f8e0078 181 code.error {
182 display: block;
183 margin: 1em 0;
184 overflow: auto;
185 white-space: pre;
186 }
969647fd 187 </style>
188</head>
189<body>
190 <div class="box">
191 <div class="error">$error</div>
192 <div class="infos">$infos</div>
193 <div class="name">$name</div>
194 </div>
195</body>
196</html>
197
198}
199
fbcc39ad 200=item $self->finalize_headers($c)
fc7ec1d9 201
202=cut
203
204sub finalize_headers { }
205
fbcc39ad 206=item $self->finalize_read($c)
fc7ec1d9 207
208=cut
209
fbcc39ad 210sub finalize_read {
211 my ( $self, $c ) = @_;
212
213 undef $self->{_prepared_read};
fc7ec1d9 214}
215
fbcc39ad 216=item $self->finalize_uploads($c)
fc7ec1d9 217
218=cut
219
fbcc39ad 220sub finalize_uploads {
221 my ( $self, $c ) = @_;
99fe1710 222
fbcc39ad 223 if ( keys %{ $c->request->uploads } ) {
224 for my $key ( keys %{ $c->request->uploads } ) {
225 my $upload = $c->request->uploads->{$key};
226 unlink map { $_->tempname }
227 grep { -e $_->tempname }
228 ref $upload eq 'ARRAY' ? @{$upload} : ($upload);
c85ff642 229 }
c85ff642 230 }
fc7ec1d9 231}
232
fbcc39ad 233=item $self->prepare_body($c)
fc7ec1d9 234
235=cut
236
fbcc39ad 237sub prepare_body {
238 my ( $self, $c ) = @_;
99fe1710 239
fbcc39ad 240 $self->read_length( $c->request->header('Content-Length') || 0 );
241 my $type = $c->request->header('Content-Type');
99fe1710 242
fbcc39ad 243 unless ( $c->request->{_body} ) {
244 $c->request->{_body} = HTTP::Body->new( $type, $self->read_length );
fc7ec1d9 245 }
fbcc39ad 246
247 if ( $self->read_length > 0 ) {
248 while ( my $buffer = $self->read( $c ) ) {
4bd82c41 249 $c->prepare_body_chunk( $buffer );
fbcc39ad 250 }
fc7ec1d9 251 }
252}
253
4bd82c41 254=item $self->prepare_body_chunk($c)
255
256=cut
257
258sub prepare_body_chunk {
259 my ( $self, $c, $chunk ) = @_;
260
261 $c->request->{_body}->add( $chunk );
262}
263
fbcc39ad 264=item $self->prepare_body_parameters($c)
06e1b616 265
266=cut
267
fbcc39ad 268sub prepare_body_parameters {
269 my ( $self, $c ) = @_;
270 $c->request->body_parameters( $c->request->{_body}->param );
271}
0556eb49 272
fbcc39ad 273=item $self->prepare_connection($c)
0556eb49 274
275=cut
276
277sub prepare_connection { }
278
fbcc39ad 279=item $self->prepare_cookies($c)
fc7ec1d9 280
281=cut
282
6dc87a0f 283sub prepare_cookies {
fbcc39ad 284 my ( $self, $c ) = @_;
6dc87a0f 285
286 if ( my $header = $c->request->header('Cookie') ) {
287 $c->req->cookies( { CGI::Cookie->parse($header) } );
288 }
289}
fc7ec1d9 290
fbcc39ad 291=item $self->prepare_headers($c)
fc7ec1d9 292
293=cut
294
295sub prepare_headers { }
296
fbcc39ad 297=item $self->prepare_parameters($c)
fc7ec1d9 298
299=cut
300
fbcc39ad 301sub prepare_parameters {
302 my ( $self, $c ) = @_;
fc7ec1d9 303
fbcc39ad 304 # We copy, no references
305 while ( my ( $name, $param ) = each %{ $c->request->query_parameters } ) {
306 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
307 $c->request->parameters->{$name} = $param;
308 }
fc7ec1d9 309
fbcc39ad 310 # Merge query and body parameters
311 while ( my ( $name, $param ) = each %{ $c->request->body_parameters } ) {
312 $param = ref $param eq 'ARRAY' ? [ @{$param} ] : $param;
313 if ( my $old_param = $c->request->parameters->{$name} ) {
314 if ( ref $old_param eq 'ARRAY' ) {
315 push @{ $c->request->parameters->{$name} },
316 ref $param eq 'ARRAY' ? @$param : $param;
317 }
318 else { $c->request->parameters->{$name} = [ $old_param, $param ] }
319 }
320 else { $c->request->parameters->{$name} = $param }
321 }
322}
323
324=item $self->prepare_path($c)
fc7ec1d9 325
326=cut
327
328sub prepare_path { }
329
fbcc39ad 330=item $self->prepare_request($c)
fc7ec1d9 331
fbcc39ad 332=item $self->prepare_query_parameters($c)
fc7ec1d9 333
334=cut
335
fbcc39ad 336sub prepare_query_parameters { }
337
338=item $self->prepare_read($c)
339
340=cut
fc7ec1d9 341
fbcc39ad 342sub prepare_read {
343 my ( $self, $c ) = @_;
344
345 # Reset the read position
346 $self->read_position( 0 );
347}
fc7ec1d9 348
fbcc39ad 349=item $self->prepare_request(@arguments)
fc7ec1d9 350
351=cut
352
fbcc39ad 353sub prepare_request { }
fc7ec1d9 354
fbcc39ad 355=item $self->prepare_uploads($c)
c9afa5fc 356
fbcc39ad 357=cut
358
359sub prepare_uploads {
360 my ( $self, $c ) = @_;
361 my $uploads = $c->request->{_body}->upload;
362 for my $name ( keys %$uploads ) {
363 my $files = $uploads->{$name};
364 $files = ref $files eq 'ARRAY' ? $files : [$files];
365 my @uploads;
366 for my $upload (@$files) {
367 my $u = Catalyst::Request::Upload->new;
368 $u->headers( HTTP::Headers->new( %{ $upload->{headers} } ) );
369 $u->type( $u->headers->content_type );
370 $u->tempname( $upload->{tempname} );
371 $u->size( $upload->{size} );
372 $u->filename( $upload->{filename} );
373 push @uploads, $u;
374 }
375 $c->request->uploads->{$name} = @uploads > 1 ? \@uploads : $uploads[0];
376 }
377}
378
379=item $self->prepare_write($c)
c9afa5fc 380
381=cut
382
fbcc39ad 383sub prepare_write { }
384
385=item $self->read($c, [$maxlength])
386
387=cut
388
389sub read {
390 my ( $self, $c, $maxlength ) = @_;
391
392 unless ( $self->{_prepared_read} ) {
393 $self->prepare_read( $c );
394 $self->{_prepared_read} = 1;
395 }
396
397 my $remaining = $self->read_length - $self->read_position;
4bd82c41 398 $maxlength ||= $CHUNKSIZE;
fbcc39ad 399
400 # Are we done reading?
401 if ( $remaining <= 0 ) {
402 $self->finalize_read( $c );
403 return;
404 }
c9afa5fc 405
fbcc39ad 406 my $readlen = ( $remaining > $maxlength ) ? $maxlength : $remaining;
407 my $rc = $self->read_chunk( $c, my $buffer, $readlen );
408 if ( defined $rc ) {
409 $self->read_position( $self->read_position + $rc );
410 return $buffer;
411 }
412 else {
413 Catalyst::Exception->throw(
414 message => "Unknown error reading input: $!"
415 );
416 }
417}
fc7ec1d9 418
fbcc39ad 419=item $self->read_chunk($c, $buffer, $length)
23f9d934 420
fbcc39ad 421Each engine inplements read_chunk as its preferred way of reading a chunk
422of data.
fc7ec1d9 423
fbcc39ad 424=cut
61b1e958 425
fbcc39ad 426sub read_chunk { }
61b1e958 427
fbcc39ad 428=item $self->read_length
ca39d576 429
fbcc39ad 430The length of input data to be read. This is obtained from the Content-Length
431header.
fc7ec1d9 432
fbcc39ad 433=item $self->read_position
fc7ec1d9 434
fbcc39ad 435The amount of input data that has already been read.
63b763c5 436
fbcc39ad 437=item $self->run($c)
63b763c5 438
fbcc39ad 439=cut
fc7ec1d9 440
fbcc39ad 441sub run { }
fc7ec1d9 442
fbcc39ad 443=item $self->write($c, $buffer)
fc7ec1d9 444
445=cut
446
fbcc39ad 447sub write {
448 my ( $self, $c, $buffer ) = @_;
449
450 unless ( $self->{_prepared_write} ) {
451 $self->prepare_write( $c );
452 $self->{_prepared_write} = 1;
fc7ec1d9 453 }
fbcc39ad 454
455 my $handle = $c->response->handle;
456
457 print $handle $buffer;
fc7ec1d9 458}
459
23f9d934 460=back
461
fbcc39ad 462=head1 AUTHORS
463
464Sebastian Riedel, <sri@cpan.org>
fc7ec1d9 465
fbcc39ad 466Andy Grundman, <andy@hybridized.org>
fc7ec1d9 467
468=head1 COPYRIGHT
469
470This program is free software, you can redistribute it and/or modify it under
471the same terms as Perl itself.
472
473=cut
474
4751;