add files
|
1 |
package Mojolicious::Controller; |
2 |
use Mojo::Base -base; |
|
3 | ||
4 |
# No imports, for security reasons! |
|
5 |
use Carp (); |
|
6 |
use Mojo::ByteStream; |
|
7 |
use Mojo::Exception; |
|
8 |
use Mojo::Transaction::HTTP; |
|
9 |
use Mojo::URL; |
|
10 |
use Mojo::Util; |
|
11 |
use Mojolicious; |
|
12 |
use Mojolicious::Routes::Match; |
|
13 |
use Scalar::Util (); |
|
14 |
use Time::HiRes (); |
|
15 | ||
16 |
has app => sub { Mojolicious->new }; |
|
17 |
has match => |
|
18 |
sub { Mojolicious::Routes::Match->new(root => shift->app->routes) }; |
|
19 |
has tx => sub { Mojo::Transaction::HTTP->new }; |
|
20 | ||
21 |
# Reserved stash values |
|
22 |
my %RESERVED = map { $_ => 1 } ( |
|
23 |
qw(action app cb controller data extends format handler json layout), |
|
24 |
qw(namespace partial path status template text) |
|
25 |
); |
|
26 | ||
27 |
sub AUTOLOAD { |
|
28 |
my $self = shift; |
|
29 | ||
30 |
my ($package, $method) = our $AUTOLOAD =~ /^([\w:]+)::(\w+)$/; |
|
31 |
Carp::croak "Undefined subroutine &${package}::$method called" |
|
32 |
unless Scalar::Util::blessed $self && $self->isa(__PACKAGE__); |
|
33 | ||
34 |
# Call helper with current controller |
|
35 |
Carp::croak qq{Can't locate object method "$method" via package "$package"} |
|
36 |
unless my $helper = $self->app->renderer->helpers->{$method}; |
|
37 |
return $self->$helper(@_); |
|
38 |
} |
|
39 | ||
40 |
sub DESTROY { } |
|
41 | ||
42 |
sub continue { $_[0]->app->routes->continue($_[0]) } |
|
43 | ||
44 |
sub cookie { |
|
45 |
my ($self, $name) = (shift, shift); |
|
46 | ||
47 |
# Response cookie |
|
48 |
if (@_) { |
|
49 | ||
50 |
# Cookie too big |
|
51 |
my $cookie = {name => $name, value => shift, %{shift || {}}}; |
|
52 |
$self->app->log->error(qq{Cookie "$name" is bigger than 4096 bytes.}) |
|
53 |
if length $cookie->{value} > 4096; |
|
54 | ||
55 |
$self->res->cookies($cookie); |
|
56 |
return $self; |
|
57 |
} |
|
58 | ||
59 |
# Request cookies |
|
60 |
return map { $_->value } $self->req->cookie($name) if wantarray; |
|
61 |
return undef unless my $cookie = $self->req->cookie($name); |
|
62 |
return $cookie->value; |
|
63 |
} |
|
64 | ||
65 |
sub finish { |
|
66 |
my $self = shift; |
|
67 | ||
68 |
# WebSocket |
|
69 |
my $tx = $self->tx; |
|
70 |
$tx->finish(@_) and return $self if $tx->is_websocket; |
|
71 | ||
72 |
# Chunked stream |
|
73 |
if ($tx->res->content->is_chunked) { |
|
74 |
$self->write_chunk(@_) if @_; |
|
75 |
return $self->write_chunk(''); |
|
76 |
} |
|
77 | ||
78 |
# Normal stream |
|
79 |
$self->write(@_) if @_; |
|
80 |
return $self->write(''); |
|
81 |
} |
|
82 | ||
83 |
sub flash { |
|
84 |
my $self = shift; |
|
85 | ||
86 |
# Check old flash |
|
87 |
my $session = $self->session; |
|
88 |
return $session->{flash} ? $session->{flash}{$_[0]} : undef |
|
89 |
if @_ == 1 && !ref $_[0]; |
|
90 | ||
91 |
# Initialize new flash and merge values |
|
92 |
my $flash = $session->{new_flash} ||= {}; |
|
93 |
%$flash = (%$flash, %{@_ > 1 ? {@_} : $_[0]}); |
|
94 | ||
95 |
return $self; |
|
96 |
} |
|
97 | ||
98 |
sub on { |
|
99 |
my ($self, $name, $cb) = @_; |
|
100 |
my $tx = $self->tx; |
|
101 |
$self->rendered(101) if $tx->is_websocket; |
|
102 |
return $tx->on($name => sub { shift and $self->$cb(@_) }); |
|
103 |
} |
|
104 | ||
105 |
sub param { |
|
106 |
my ($self, $name) = (shift, shift); |
|
107 | ||
108 |
# Multiple names |
|
109 |
return map { scalar $self->param($_) } @$name if ref $name eq 'ARRAY'; |
|
110 | ||
111 |
# List names |
|
112 |
my $captures = $self->stash->{'mojo.captures'} ||= {}; |
|
113 |
my $req = $self->req; |
|
114 |
unless (defined $name) { |
|
115 |
my %seen; |
|
116 |
my @keys = grep { !$seen{$_}++ } $req->param; |
|
117 |
push @keys, grep { !$seen{$_}++ } map { $_->name } @{$req->uploads}; |
|
118 |
push @keys, grep { !$RESERVED{$_} && !$seen{$_}++ } keys %$captures; |
|
119 |
return sort @keys; |
|
120 |
} |
|
121 | ||
122 |
# Override values |
|
123 |
if (@_) { |
|
124 |
$captures->{$name} = @_ > 1 ? [@_] : $_[0]; |
|
125 |
return $self; |
|
126 |
} |
|
127 | ||
128 |
# Captured unreserved values |
|
129 |
if (!$RESERVED{$name} && defined(my $value = $captures->{$name})) { |
|
130 |
return ref $value eq 'ARRAY' ? wantarray ? @$value : $$value[0] : $value; |
|
131 |
} |
|
132 | ||
133 |
# Uploads |
|
134 |
return $req->upload($name) if $req->upload($name); |
|
135 | ||
136 |
# Param values |
|
137 |
return $req->param($name); |
|
138 |
} |
|
139 | ||
140 |
sub redirect_to { |
|
141 |
my $self = shift; |
|
142 | ||
143 |
# Don't override 3xx status |
|
144 |
my $res = $self->res; |
|
145 |
$res->headers->location($self->url_for(@_)->to_abs); |
|
146 |
return $self->rendered($res->is_status_class(300) ? () : 302); |
|
147 |
} |
|
148 | ||
149 |
sub render { |
|
150 |
my $self = shift; |
|
151 | ||
152 |
# Template may be first argument |
|
153 |
my ($template, $args) = (@_ % 2 ? shift : undef, {@_}); |
|
154 |
$args->{template} = $template if $template; |
|
155 |
my $maybe = delete $args->{'mojo.maybe'}; |
|
156 | ||
157 |
# Render |
|
158 |
my $app = $self->app; |
|
159 |
my ($output, $format) = $app->renderer->render($self, $args); |
|
160 |
return defined $output ? Mojo::ByteStream->new($output) : undef |
|
161 |
if $args->{partial}; |
|
162 | ||
163 |
# Maybe |
|
164 |
return $maybe ? undef : !$self->render_not_found unless defined $output; |
|
165 | ||
166 |
# Prepare response |
|
167 |
$app->plugins->emit_hook(after_render => $self, \$output, $format); |
|
168 |
my $headers = $self->res->body($output)->headers; |
|
169 |
$headers->content_type($app->types->type($format) || 'text/plain') |
|
170 |
unless $headers->content_type; |
|
171 |
return !!$self->rendered($self->stash->{status}); |
|
172 |
} |
|
173 | ||
174 |
sub render_exception { |
|
175 |
my ($self, $e) = @_; |
|
176 | ||
177 |
my $app = $self->app; |
|
178 |
$app->log->error($e = Mojo::Exception->new($e)); |
|
179 | ||
180 |
# Filtered stash snapshot |
|
181 |
my $stash = $self->stash; |
|
182 |
my %snapshot = map { $_ => $stash->{$_} } |
|
183 |
grep { !/^mojo\./ and defined $stash->{$_} } keys %$stash; |
|
184 | ||
185 |
# Render with fallbacks |
|
186 |
my $mode = $app->mode; |
|
187 |
my $renderer = $app->renderer; |
|
188 |
my $options = { |
|
189 |
exception => $e, |
|
190 |
snapshot => \%snapshot, |
|
191 |
template => "exception.$mode", |
|
192 |
format => $stash->{format} || $renderer->default_format, |
|
193 |
handler => undef, |
|
194 |
status => 500 |
|
195 |
}; |
|
196 |
my $inline = $renderer->_bundled( |
|
197 |
$mode eq 'development' ? 'exception.development' : 'exception'); |
|
198 |
return $self if $self->_fallbacks($options, 'exception', $inline); |
|
199 |
$self->_fallbacks({%$options, format => 'html'}, 'exception', $inline); |
|
200 |
return $self; |
|
201 |
} |
|
202 | ||
203 |
sub render_later { shift->stash('mojo.rendered' => 1) } |
|
204 | ||
205 |
sub render_maybe { shift->render(@_, 'mojo.maybe' => 1) } |
|
206 | ||
207 |
sub render_not_found { |
|
208 |
my $self = shift; |
|
209 | ||
210 |
# Render with fallbacks |
|
211 |
my $app = $self->app; |
|
212 |
my $mode = $app->mode; |
|
213 |
my $renderer = $app->renderer; |
|
214 |
my $format = $self->stash->{format} || $renderer->default_format; |
|
215 |
my $options |
|
216 |
= {template => "not_found.$mode", format => $format, status => 404}; |
|
217 |
my $inline = $renderer->_bundled( |
|
218 |
$mode eq 'development' ? 'not_found.development' : 'not_found'); |
|
219 |
return $self if $self->_fallbacks($options, 'not_found', $inline); |
|
220 |
$self->_fallbacks({%$options, format => 'html'}, 'not_found', $inline); |
|
221 |
return $self; |
|
222 |
} |
|
223 | ||
224 |
sub render_static { |
|
225 |
my ($self, $file) = @_; |
|
226 |
my $app = $self->app; |
|
227 |
return !!$self->rendered if $app->static->serve($self, $file); |
|
228 |
$app->log->debug(qq{File "$file" not found, public directory missing?}); |
|
229 |
return !$self->render_not_found; |
|
230 |
} |
|
231 | ||
232 |
sub rendered { |
|
233 |
my ($self, $status) = @_; |
|
234 | ||
235 |
# Disable auto rendering and make sure we have a status |
|
236 |
my $res = $self->render_later->res; |
|
237 |
$res->code($status || 200) if $status || !$res->code; |
|
238 | ||
239 |
# Finish transaction |
|
240 |
my $stash = $self->stash; |
|
241 |
unless ($stash->{'mojo.finished'}++) { |
|
242 | ||
243 |
# Stop timer |
|
244 |
my $app = $self->app; |
|
245 |
if (my $started = delete $stash->{'mojo.started'}) { |
|
246 |
my $elapsed = sprintf '%f', |
|
247 |
Time::HiRes::tv_interval($started, [Time::HiRes::gettimeofday()]); |
|
248 |
my $rps = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed; |
|
249 |
my $code = $res->code; |
|
250 |
my $msg = $res->message || $res->default_message($code); |
|
251 |
$app->log->debug("$code $msg (${elapsed}s, $rps/s)."); |
|
252 |
} |
|
253 | ||
254 |
$app->plugins->emit_hook_reverse(after_dispatch => $self); |
|
255 |
$app->sessions->store($self); |
|
256 |
} |
|
257 |
$self->tx->resume; |
|
258 |
return $self; |
|
259 |
} |
|
260 | ||
261 |
sub req { shift->tx->req } |
|
262 |
sub res { shift->tx->res } |
|
263 | ||
264 |
sub respond_to { |
|
265 |
my $self = shift; |
|
266 |
my $args = ref $_[0] ? $_[0] : {@_}; |
|
267 | ||
268 |
# Detect formats |
|
269 |
my $app = $self->app; |
|
270 |
my $req = $self->req; |
|
271 |
my @formats = @{$app->types->detect($req->headers->accept, $req->is_xhr)}; |
|
272 |
my $stash = $self->stash; |
|
273 |
unless (@formats) { |
|
274 |
my $format = $stash->{format} || $req->param('format'); |
|
275 |
push @formats, $format ? $format : $app->renderer->default_format; |
|
276 |
} |
|
277 | ||
278 |
# Find target |
|
279 |
my $target; |
|
280 |
for my $format (@formats) { |
|
281 |
next unless $target = $args->{$format}; |
|
282 |
$stash->{format} = $format; |
|
283 |
last; |
|
284 |
} |
|
285 | ||
286 |
# Fallback |
|
287 |
unless ($target) { |
|
288 |
return $self->rendered(204) unless $target = $args->{any}; |
|
289 |
delete $stash->{format}; |
|
290 |
} |
|
291 | ||
292 |
# Dispatch |
|
293 |
ref $target eq 'CODE' ? $target->($self) : $self->render(%$target); |
|
294 | ||
295 |
return $self; |
|
296 |
} |
|
297 | ||
298 |
sub send { |
|
299 |
my ($self, $msg, $cb) = @_; |
|
300 |
my $tx = $self->tx; |
|
301 |
Carp::croak 'No WebSocket connection to send message to' |
|
302 |
unless $tx->is_websocket; |
|
303 |
$tx->send($msg => sub { shift and $self->$cb(@_) if $cb }); |
|
304 |
return $self->rendered(101); |
|
305 |
} |
|
306 | ||
307 |
sub session { |
|
308 |
my $self = shift; |
|
309 | ||
310 |
# Hash |
|
311 |
my $session = $self->stash->{'mojo.session'} ||= {}; |
|
312 |
return $session unless @_; |
|
313 | ||
314 |
# Get |
|
315 |
return $session->{$_[0]} unless @_ > 1 || ref $_[0]; |
|
316 | ||
317 |
# Set |
|
318 |
%$session = (%$session, %{ref $_[0] ? $_[0] : {@_}}); |
|
319 | ||
320 |
return $self; |
|
321 |
} |
|
322 | ||
323 |
sub signed_cookie { |
|
324 |
my ($self, $name, $value, $options) = @_; |
|
325 | ||
326 |
# Response cookie |
|
327 |
my $secret = $self->stash->{'mojo.secret'}; |
|
328 |
return $self->cookie($name, |
|
329 |
"$value--" . Mojo::Util::hmac_sha1_sum($value, $secret), $options) |
|
330 |
if defined $value; |
|
331 | ||
332 |
# Request cookies |
|
333 |
my @results; |
|
334 |
for my $value ($self->cookie($name)) { |
|
335 | ||
336 |
# Check signature |
|
337 |
if ($value =~ s/--([^\-]+)$//) { |
|
338 |
my $sig = $1; |
|
339 | ||
340 |
# Verified |
|
341 |
my $check = Mojo::Util::hmac_sha1_sum $value, $secret; |
|
342 |
if (Mojo::Util::secure_compare $sig, $check) { push @results, $value } |
|
343 | ||
344 |
# Bad cookie |
|
345 |
else { |
|
346 |
$self->app->log->debug( |
|
347 |
qq{Bad signed cookie "$name", possible hacking attempt.}); |
|
348 |
} |
|
349 |
} |
|
350 | ||
351 |
# Not signed |
|
352 |
else { $self->app->log->debug(qq{Cookie "$name" not signed.}) } |
|
353 |
} |
|
354 | ||
355 |
return wantarray ? @results : $results[0]; |
|
356 |
} |
|
357 | ||
358 |
sub stash { |
|
359 |
my $self = shift; |
|
360 | ||
361 |
# Hash |
|
362 |
my $stash = $self->{stash} ||= {}; |
|
363 |
return $stash unless @_; |
|
364 | ||
365 |
# Get |
|
366 |
return $stash->{$_[0]} unless @_ > 1 || ref $_[0]; |
|
367 | ||
368 |
# Set |
|
369 |
my $values = ref $_[0] ? $_[0] : {@_}; |
|
370 |
for my $key (keys %$values) { |
|
371 |
$self->app->log->debug(qq{Careful, "$key" is a reserved stash value.}) |
|
372 |
if $RESERVED{$key}; |
|
373 |
$stash->{$key} = $values->{$key}; |
|
374 |
} |
|
375 | ||
376 |
return $self; |
|
377 |
} |
|
378 | ||
379 |
sub url_for { |
|
380 |
my $self = shift; |
|
381 |
my $target = shift // ''; |
|
382 | ||
383 |
# Absolute URL |
|
384 |
return $target if Scalar::Util::blessed $target && $target->isa('Mojo::URL'); |
|
385 |
return Mojo::URL->new($target) if $target =~ m!^(?:[^:/?#]+:|//)!; |
|
386 | ||
387 |
# Base |
|
388 |
my $url = Mojo::URL->new; |
|
389 |
my $req = $self->req; |
|
390 |
my $base = $url->base($req->url->base->clone)->base->userinfo(undef); |
|
391 | ||
392 |
# Relative URL |
|
393 |
my $path = $url->path; |
|
394 |
if ($target =~ m!^/!) { |
|
395 |
if (my $prefix = $self->stash->{path}) { |
|
396 |
my $real = $req->url->path->to_route; |
|
397 |
$real =~ s!/?$prefix$!$target!; |
|
398 |
$target = $real; |
|
399 |
} |
|
400 |
$url->parse($target); |
|
401 |
} |
|
402 | ||
403 |
# Route |
|
404 |
else { |
|
405 |
my ($generated, $ws) = $self->match->path_for($target, @_); |
|
406 |
$path->parse($generated) if $generated; |
|
407 |
$base->scheme($base->protocol eq 'https' ? 'wss' : 'ws') if $ws; |
|
408 |
} |
|
409 | ||
410 |
# Make path absolute |
|
411 |
my $base_path = $base->path; |
|
412 |
unshift @{$path->parts}, @{$base_path->parts}; |
|
413 |
$base_path->parts([])->trailing_slash(0); |
|
414 | ||
415 |
return $url; |
|
416 |
} |
|
417 | ||
418 |
sub validation { |
|
419 |
my $self = shift; |
|
420 |
return $self->stash->{'mojo.validation'} |
|
421 |
||= $self->app->validator->validation->input($self->req->params->to_hash); |
|
422 |
} |
|
423 | ||
424 |
sub write { |
|
425 |
my ($self, $chunk, $cb) = @_; |
|
426 |
($cb, $chunk) = ($chunk, undef) if ref $chunk eq 'CODE'; |
|
427 |
my $content = $self->res->content; |
|
428 |
$content->write($chunk => sub { shift and $self->$cb(@_) if $cb }); |
|
429 |
return $self->rendered; |
|
430 |
} |
|
431 | ||
432 |
sub write_chunk { |
|
433 |
my ($self, $chunk, $cb) = @_; |
|
434 |
($cb, $chunk) = ($chunk, undef) if ref $chunk eq 'CODE'; |
|
435 |
my $content = $self->res->content; |
|
436 |
$content->write_chunk($chunk => sub { shift and $self->$cb(@_) if $cb }); |
|
437 |
return $self->rendered; |
|
438 |
} |
|
439 | ||
440 |
sub _fallbacks { |
|
441 |
my ($self, $options, $template, $inline) = @_; |
|
442 | ||
443 |
# Mode specific template |
|
444 |
return 1 if $self->render_maybe(%$options); |
|
445 | ||
446 |
# Normal template |
|
447 |
return 1 if $self->render_maybe(%$options, template => $template); |
|
448 | ||
449 |
# Inline template |
|
450 |
my $stash = $self->stash; |
|
451 |
return undef unless $stash->{format} eq 'html'; |
|
452 |
delete @$stash{qw(extends layout)}; |
|
453 |
return $self->render_maybe(%$options, inline => $inline, handler => 'ep'); |
|
454 |
} |
|
455 | ||
456 |
1; |
|
457 | ||
458 |
=encoding utf8 |
|
459 | ||
460 |
=head1 NAME |
|
461 | ||
462 |
Mojolicious::Controller - Controller base class |
|
463 | ||
464 |
=head1 SYNOPSIS |
|
465 | ||
466 |
# Controller |
|
467 |
package MyApp::Foo; |
|
468 |
use Mojo::Base 'Mojolicious::Controller'; |
|
469 | ||
470 |
# Action |
|
471 |
sub bar { |
|
472 |
my $self = shift; |
|
473 |
my $name = $self->param('name'); |
|
474 |
$self->res->headers->cache_control('max-age=1, no-cache'); |
|
475 |
$self->render(json => {hello => $name}); |
|
476 |
} |
|
477 | ||
478 |
=head1 DESCRIPTION |
|
479 | ||
480 |
L<Mojolicious::Controller> is the base class for your L<Mojolicious> |
|
481 |
controllers. It is also the default controller class unless you set |
|
482 |
L<Mojolicious/"controller_class">. |
|
483 | ||
484 |
=head1 ATTRIBUTES |
|
485 | ||
486 |
L<Mojolicious::Controller> inherits all attributes from L<Mojo::Base> and |
|
487 |
implements the following new ones. |
|
488 | ||
489 |
=head2 app |
|
490 | ||
491 |
my $app = $c->app; |
|
492 |
$c = $c->app(Mojolicious->new); |
|
493 | ||
494 |
A reference back to the application that dispatched to this controller, |
|
495 |
defaults to a L<Mojolicious> object. |
|
496 | ||
497 |
# Use application logger |
|
498 |
$c->app->log->debug('Hello Mojo!'); |
|
499 | ||
500 |
# Generate path |
|
501 |
my $path = $c->app->home->rel_file('templates/foo/bar.html.ep'); |
|
502 | ||
503 |
=head2 match |
|
504 | ||
505 |
my $m = $c->match; |
|
506 |
$c = $c->match(Mojolicious::Routes::Match->new); |
|
507 | ||
508 |
Router results for the current request, defaults to a |
|
509 |
L<Mojolicious::Routes::Match> object. |
|
510 | ||
511 |
# Introspect |
|
512 |
my $foo = $c->match->endpoint->pattern->defaults->{foo}; |
|
513 |
my $bar = $c->match->stack->[-1]{bar}; |
|
514 | ||
515 |
=head2 tx |
|
516 | ||
517 |
my $tx = $c->tx; |
|
518 |
$c = $c->tx(Mojo::Transaction::HTTP->new); |
|
519 | ||
520 |
The transaction that is currently being processed, usually a |
|
521 |
L<Mojo::Transaction::HTTP> or L<Mojo::Transaction::WebSocket> object. Note |
|
522 |
that this reference is usually weakened, so the object needs to be referenced |
|
523 |
elsewhere as well when you're performing non-blocking operations and the |
|
524 |
underlying connection might get closed early. |
|
525 | ||
526 |
# Check peer information |
|
527 |
my $address = $c->tx->remote_address; |
|
528 |
my $port = $c->tx->remote_port; |
|
529 | ||
530 |
# Perform non-blocking operation without knowing the connection status |
|
531 |
my $tx = $c->tx; |
|
532 |
Mojo::IOLoop->timer(2 => sub { |
|
533 |
$c->app->log->debug($tx->is_finished ? 'Finished.' : 'In progress.'); |
|
534 |
}); |
|
535 | ||
536 |
=head1 METHODS |
|
537 | ||
538 |
L<Mojolicious::Controller> inherits all methods from L<Mojo::Base> and |
|
539 |
implements the following new ones. |
|
540 | ||
541 |
=head2 continue |
|
542 | ||
543 |
$c->continue; |
|
544 | ||
545 |
Continue dispatch chain. |
|
546 | ||
547 |
=head2 cookie |
|
548 | ||
549 |
my $value = $c->cookie('foo'); |
|
550 |
my @values = $c->cookie('foo'); |
|
551 |
$c = $c->cookie(foo => 'bar'); |
|
552 |
$c = $c->cookie(foo => 'bar', {path => '/'}); |
|
553 | ||
554 |
Access request cookie values and create new response cookies. |
|
555 | ||
556 |
# Create response cookie with domain and expiration date |
|
557 |
$c->cookie(user => 'sri', {domain => 'example.com', expires => time + 60}); |
|
558 | ||
559 |
=head2 finish |
|
560 | ||
561 |
$c = $c->finish; |
|
562 |
$c = $c->finish(1000); |
|
563 |
$c = $c->finish(1003 => 'Cannot accept data!'); |
|
564 |
$c = $c->finish('Bye!'); |
|
565 | ||
566 |
Close WebSocket connection or long poll stream gracefully. |
|
567 | ||
568 |
=head2 flash |
|
569 | ||
570 |
my $foo = $c->flash('foo'); |
|
571 |
$c = $c->flash({foo => 'bar'}); |
|
572 |
$c = $c->flash(foo => 'bar'); |
|
573 | ||
574 |
Data storage persistent only for the next request, stored in the |
|
575 |
L</"session">. |
|
576 | ||
577 |
# Show message after redirect |
|
578 |
$c->flash(message => 'User created successfully!'); |
|
579 |
$c->redirect_to('show_user', id => 23); |
|
580 | ||
581 |
=head2 on |
|
582 | ||
583 |
my $cb = $c->on(finish => sub {...}); |
|
584 | ||
585 |
Subscribe to events of L</"tx">, which is usually a L<Mojo::Transaction::HTTP> |
|
586 |
or L<Mojo::Transaction::WebSocket> object. Note that this method will |
|
587 |
automatically respond to WebSocket handshake requests with a C<101> response |
|
588 |
status. |
|
589 | ||
590 |
# Do something after the transaction has been finished |
|
591 |
$c->on(finish => sub { |
|
592 |
my $c = shift; |
|
593 |
$c->app->log->debug('We are done!'); |
|
594 |
}); |
|
595 | ||
596 |
# Receive WebSocket message |
|
597 |
$c->on(message => sub { |
|
598 |
my ($c, $msg) = @_; |
|
599 |
$c->app->log->debug("Message: $msg"); |
|
600 |
}); |
|
601 | ||
602 |
# Receive JSON object via WebSocket message |
|
603 |
$c->on(json => sub { |
|
604 |
my ($c, $hash) = @_; |
|
605 |
$c->app->log->debug("Test: $hash->{test}"); |
|
606 |
}); |
|
607 | ||
608 |
# Receive WebSocket "Binary" message |
|
609 |
$c->on(binary => sub { |
|
610 |
my ($c, $bytes) = @_; |
|
611 |
my $len = length $bytes; |
|
612 |
$c->app->log->debug("Received $len bytes."); |
|
613 |
}); |
|
614 | ||
615 |
=head2 param |
|
616 | ||
617 |
my @names = $c->param; |
|
618 |
my $foo = $c->param('foo'); |
|
619 |
my @foo = $c->param('foo'); |
|
620 |
my ($foo, $bar) = $c->param(['foo', 'bar']); |
|
621 |
$c = $c->param(foo => 'ba;r'); |
|
622 |
$c = $c->param(foo => qw(ba;r ba;z)); |
|
623 | ||
624 |
Access route placeholder values that are not reserved stash values, file |
|
625 |
uploads and GET/POST parameters, in that order. Note that this method is |
|
626 |
context sensitive in some cases and therefore needs to be used with care, |
|
627 |
there can always be multiple values, which might have unexpected consequences. |
|
628 |
Parts of the request body need to be loaded into memory to parse POST |
|
629 |
parameters, so you have to make sure it is not excessively large. |
|
630 | ||
631 |
# List context is ambiguous and should be avoided |
|
632 |
my $hash = {foo => $self->param('foo')}; |
|
633 | ||
634 |
# Better enforce scalar context |
|
635 |
my $hash = {foo => scalar $self->param('foo')}; |
|
636 | ||
637 |
# The multi name form can also enforce scalar context |
|
638 |
my $hash = {foo => $self->param(['foo'])}; |
|
639 | ||
640 |
For more control you can also access request information directly. |
|
641 | ||
642 |
# Only GET parameters |
|
643 |
my $foo = $c->req->url->query->param('foo'); |
|
644 | ||
645 |
# Only GET and POST parameters |
|
646 |
my $foo = $c->req->param('foo'); |
|
647 | ||
648 |
# Only file uploads |
|
649 |
my $foo = $c->req->upload('foo'); |
|
650 | ||
651 |
=head2 redirect_to |
|
652 | ||
653 |
$c = $c->redirect_to('named', foo => 'bar'); |
|
654 |
$c = $c->redirect_to('named', {foo => 'bar'}); |
|
655 |
$c = $c->redirect_to('/perldoc'); |
|
656 |
$c = $c->redirect_to('http://mojolicio.us/perldoc'); |
|
657 | ||
658 |
Prepare a C<302> redirect response, takes the same arguments as L</"url_for">. |
|
659 | ||
660 |
# Conditional redirect |
|
661 |
return $c->redirect_to('login') unless $c->session('user'); |
|
662 | ||
663 |
# Moved permanently |
|
664 |
$c->res->code(301); |
|
665 |
$c->redirect_to('some_route'); |
|
666 | ||
667 |
=head2 render |
|
668 | ||
669 |
my $bool = $c->render; |
|
670 |
my $bool = $c->render(controller => 'foo', action => 'bar'); |
|
671 |
my $bool = $c->render(template => 'foo/index'); |
|
672 |
my $bool = $c->render(template => 'index', format => 'html'); |
|
673 |
my $bool = $c->render(data => $bytes); |
|
674 |
my $bool = $c->render(text => 'Hello!'); |
|
675 |
my $bool = $c->render(json => {foo => 'bar'}); |
|
676 |
my $bool = $c->render(handler => 'something'); |
|
677 |
my $bool = $c->render('foo/index'); |
|
678 |
my $output = $c->render('foo/index', partial => 1); |
|
679 | ||
680 |
Render content using L<Mojolicious::Renderer/"render"> and emit hook |
|
681 |
L<Mojolicious/"after_render"> unless the result is C<partial>. If no template |
|
682 |
is provided a default one based on controller and action or route name will be |
|
683 |
generated, all additional values get merged into the L</"stash">. |
|
684 | ||
685 |
=head2 render_exception |
|
686 | ||
687 |
$c = $c->render_exception('Oops!'); |
|
688 |
$c = $c->render_exception(Mojo::Exception->new('Oops!')); |
|
689 | ||
690 |
Render the exception template C<exception.$mode.$format.*> or |
|
691 |
C<exception.$format.*> and set the response status code to C<500>. Also sets |
|
692 |
the stash values C<exception> to a L<Mojo::Exception> object and C<snapshot> |
|
693 |
to a copy of the L</"stash"> for use in the templates. |
|
694 | ||
695 |
=head2 render_later |
|
696 | ||
697 |
$c = $c->render_later; |
|
698 | ||
699 |
Disable automatic rendering to delay response generation, only necessary if |
|
700 |
automatic rendering would result in a response. |
|
701 | ||
702 |
# Delayed rendering |
|
703 |
$c->render_later; |
|
704 |
Mojo::IOLoop->timer(2 => sub { |
|
705 |
$c->render(text => 'Delayed by 2 seconds!'); |
|
706 |
}); |
|
707 | ||
708 |
=head2 render_maybe |
|
709 | ||
710 |
my $bool = $c->render_maybe; |
|
711 |
my $bool = $c->render_maybe(controller => 'foo', action => 'bar'); |
|
712 |
my $bool = $c->render_maybe('foo/index', format => 'html'); |
|
713 | ||
714 |
Try to render content but do not call L</"render_not_found"> if no response |
|
715 |
could be generated, takes the same arguments as L</"render">. |
|
716 | ||
717 |
# Render template "index_local" only if it exists |
|
718 |
$self->render_maybe('index_local') or $self->render('index'); |
|
719 | ||
720 |
=head2 render_not_found |
|
721 | ||
722 |
$c = $c->render_not_found; |
|
723 | ||
724 |
Render the not found template C<not_found.$mode.$format.*> or |
|
725 |
C<not_found.$format.*> and set the response status code to C<404>. |
|
726 | ||
727 |
=head2 render_static |
|
728 | ||
729 |
my $bool = $c->render_static('images/logo.png'); |
|
730 |
my $bool = $c->render_static('../lib/MyApp.pm'); |
|
731 | ||
732 |
Render a static file using L<Mojolicious::Static/"serve">, usually from the |
|
733 |
C<public> directories or C<DATA> sections of your application. Note that this |
|
734 |
method does not protect from traversing to parent directories. |
|
735 | ||
736 |
=head2 rendered |
|
737 | ||
738 |
$c = $c->rendered; |
|
739 |
$c = $c->rendered(302); |
|
740 | ||
741 |
Finalize response and emit hook L<Mojolicious/"after_dispatch">, defaults to |
|
742 |
using a C<200> response code. |
|
743 | ||
744 |
=head2 req |
|
745 | ||
746 |
my $req = $c->req; |
|
747 | ||
748 |
Get L<Mojo::Message::Request> object from L<Mojo::Transaction/"req">. |
|
749 | ||
750 |
# Longer version |
|
751 |
my $req = $c->tx->req; |
|
752 | ||
753 |
# Extract request information |
|
754 |
my $url = $c->req->url->to_abs; |
|
755 |
my $userinfo = $c->req->url->to_abs->userinfo; |
|
756 |
my $host = $c->req->url->to_abs->host; |
|
757 |
my $agent = $c->req->headers->user_agent; |
|
758 |
my $bytes = $c->req->body; |
|
759 |
my $str = $c->req->text; |
|
760 |
my $hash = $c->req->json; |
|
761 |
my $foo = $c->req->json('/23/foo'); |
|
762 |
my $dom = $c->req->dom; |
|
763 |
my $bar = $c->req->dom('div.bar')->first->text; |
|
764 | ||
765 |
=head2 res |
|
766 | ||
767 |
my $res = $c->res; |
|
768 | ||
769 |
Get L<Mojo::Message::Response> object from L<Mojo::Transaction/"res">. |
|
770 | ||
771 |
# Longer version |
|
772 |
my $res = $c->tx->res; |
|
773 | ||
774 |
# Force file download by setting a custom response header |
|
775 |
$c->res->headers->content_disposition('attachment; filename=foo.png;'); |
|
776 | ||
777 |
=head2 respond_to |
|
778 | ||
779 |
$c = $c->respond_to( |
|
780 |
json => {json => {message => 'Welcome!'}}, |
|
781 |
html => {template => 'welcome'}, |
|
782 |
any => sub {...} |
|
783 |
); |
|
784 | ||
785 |
Automatically select best possible representation for resource from C<Accept> |
|
786 |
request header, C<format> stash value or C<format> GET/POST parameter, |
|
787 |
defaults to rendering an empty C<204> response. Since browsers often don't |
|
788 |
really know what they actually want, unspecific C<Accept> request headers with |
|
789 |
more than one MIME type will be ignored, unless the C<X-Requested-With> header |
|
790 |
is set to the value C<XMLHttpRequest>. |
|
791 | ||
792 |
$c->respond_to( |
|
793 |
json => sub { $c->render(json => {just => 'works'}) }, |
|
794 |
xml => {text => '<just>works</just>'}, |
|
795 |
any => {data => '', status => 204} |
|
796 |
); |
|
797 | ||
798 |
=head2 send |
|
799 | ||
800 |
$c = $c->send({binary => $bytes}); |
|
801 |
$c = $c->send({text => $bytes}); |
|
802 |
$c = $c->send({json => {test => [1, 2, 3]}}); |
|
803 |
$c = $c->send([$fin, $rsv1, $rsv2, $rsv3, $op, $bytes]); |
|
804 |
$c = $c->send($chars); |
|
805 |
$c = $c->send($chars => sub {...}); |
|
806 | ||
807 |
Send message or frame non-blocking via WebSocket, the optional drain callback |
|
808 |
will be invoked once all data has been written. Note that this method will |
|
809 |
automatically respond to WebSocket handshake requests with a C<101> response |
|
810 |
status. |
|
811 | ||
812 |
# Send "Text" message |
|
813 |
$c->send('I ♥ Mojolicious!'); |
|
814 | ||
815 |
# Send JSON object as "Text" message |
|
816 |
$c->send({json => {test => 'I ♥ Mojolicious!'}}); |
|
817 | ||
818 |
# Send JSON object as "Binary" message |
|
819 |
use Mojo::JSON 'j'; |
|
820 |
$c->send({binary => j({test => 'I ♥ Mojolicious!'})}); |
|
821 | ||
822 |
# Send "Ping" frame |
|
823 |
$c->send([1, 0, 0, 0, 9, 'Hello World!']); |
|
824 | ||
825 |
# Make sure previous message has been written before continuing |
|
826 |
$c->send('First message!' => sub { |
|
827 |
my $c = shift; |
|
828 |
$c->send('Second message!'); |
|
829 |
}); |
|
830 | ||
831 |
For mostly idle WebSockets you might also want to increase the inactivity |
|
832 |
timeout, which usually defaults to C<15> seconds. |
|
833 | ||
834 |
# Increase inactivity timeout for connection to 300 seconds |
|
835 |
Mojo::IOLoop->stream($c->tx->connection)->timeout(300); |
|
836 | ||
837 |
=head2 session |
|
838 | ||
839 |
my $session = $c->session; |
|
840 |
my $foo = $c->session('foo'); |
|
841 |
$c = $c->session({foo => 'bar'}); |
|
842 |
$c = $c->session(foo => 'bar'); |
|
843 | ||
844 |
Persistent data storage, all session data gets serialized with L<Mojo::JSON> |
|
845 |
and stored C<Base64> encoded in C<HMAC-SHA1> signed cookies. Note that cookies |
|
846 |
usually have a 4096 byte limit, depending on browser. |
|
847 | ||
848 |
# Manipulate session |
|
849 |
$c->session->{foo} = 'bar'; |
|
850 |
my $foo = $c->session->{foo}; |
|
851 |
delete $c->session->{foo}; |
|
852 | ||
853 |
# Expiration date in seconds from now (persists between requests) |
|
854 |
$c->session(expiration => 604800); |
|
855 | ||
856 |
# Expiration date as absolute epoch time (only valid for one request) |
|
857 |
$c->session(expires => time + 604800); |
|
858 | ||
859 |
# Delete whole session by setting an expiration date in the past |
|
860 |
$c->session(expires => 1); |
|
861 | ||
862 |
=head2 signed_cookie |
|
863 | ||
864 |
my $value = $c->signed_cookie('foo'); |
|
865 |
my @values = $c->signed_cookie('foo'); |
|
866 |
$c = $c->signed_cookie(foo => 'bar'); |
|
867 |
$c = $c->signed_cookie(foo => 'bar', {path => '/'}); |
|
868 | ||
869 |
Access signed request cookie values and create new signed response cookies. |
|
870 |
Cookies failing C<HMAC-SHA1> signature verification will be automatically |
|
871 |
discarded. |
|
872 | ||
873 |
=head2 stash |
|
874 | ||
875 |
my $hash = $c->stash; |
|
876 |
my $foo = $c->stash('foo'); |
|
877 |
$c = $c->stash({foo => 'bar'}); |
|
878 |
$c = $c->stash(foo => 'bar'); |
|
879 | ||
880 |
Non persistent data storage and exchange, application wide default values can |
|
881 |
be set with L<Mojolicious/"defaults">. Some stash values have a special |
|
882 |
meaning and are reserved, the full list is currently C<action>, C<app>, C<cb>, |
|
883 |
C<controller>, C<data>, C<extends>, C<format>, C<handler>, C<json>, C<layout>, |
|
884 |
C<namespace>, C<partial>, C<path>, C<status>, C<template> and C<text>. Note |
|
885 |
that all stash values with a C<mojo.*> prefix are reserved for internal use. |
|
886 | ||
887 |
# Remove value |
|
888 |
my $foo = delete $c->stash->{foo}; |
|
889 | ||
890 |
=head2 url_for |
|
891 | ||
892 |
my $url = $c->url_for; |
|
893 |
my $url = $c->url_for(name => 'sebastian'); |
|
894 |
my $url = $c->url_for({name => 'sebastian'}); |
|
895 |
my $url = $c->url_for('test', name => 'sebastian'); |
|
896 |
my $url = $c->url_for('test', {name => 'sebastian'}); |
|
897 |
my $url = $c->url_for('/perldoc'); |
|
898 |
my $url = $c->url_for('//mojolicio.us/perldoc'); |
|
899 |
my $url = $c->url_for('http://mojolicio.us/perldoc'); |
|
900 |
my $url = $c->url_for('mailto:sri@example.com'); |
|
901 | ||
902 |
Generate a portable L<Mojo::URL> object with base for a route, path or URL. |
|
903 | ||
904 |
# "http://127.0.0.1:3000/perldoc" if application has been started with Morbo |
|
905 |
$c->url_for('/perldoc')->to_abs; |
|
906 | ||
907 |
# "/perldoc?foo=bar" if application is deployed under "/" |
|
908 |
$c->url_for('/perldoc')->query(foo => 'bar'); |
|
909 | ||
910 |
# "/myapp/perldoc?foo=bar" if application is deployed under "/myapp" |
|
911 |
$c->url_for('/perldoc')->query(foo => 'bar'); |
|
912 | ||
913 |
You can also use the helper L<Mojolicious::Plugin::DefaultHelpers/"url_with"> |
|
914 |
to inherit query parameters from the current request. |
|
915 | ||
916 |
# "/list?q=mojo&page=2" if current request was for "/list?q=mojo&page=1" |
|
917 |
$c->url_with->query([page => 2]); |
|
918 | ||
919 |
=head2 validation |
|
920 | ||
921 |
my $validation = $c->validation; |
|
922 | ||
923 |
Get L<Mojolicious::Validator::Validation> object for current request to |
|
924 |
validate GET/POST parameters. Parts of the request body need to be loaded into |
|
925 |
memory to parse POST parameters, so you have to make sure it is not |
|
926 |
excessively large. |
|
927 | ||
928 |
my $validation = $c->validation; |
|
929 |
$validation->required('title')->size(3, 50); |
|
930 |
my $title = $validation->param('title'); |
|
931 | ||
932 |
=head2 write |
|
933 | ||
934 |
$c = $c->write; |
|
935 |
$c = $c->write($bytes); |
|
936 |
$c = $c->write(sub {...}); |
|
937 |
$c = $c->write($bytes => sub {...}); |
|
938 | ||
939 |
Write dynamic content non-blocking, the optional drain callback will be |
|
940 |
invoked once all data has been written. |
|
941 | ||
942 |
# Keep connection alive (with Content-Length header) |
|
943 |
$c->res->headers->content_length(6); |
|
944 |
$c->write('Hel' => sub { |
|
945 |
my $c = shift; |
|
946 |
$c->write('lo!') |
|
947 |
}); |
|
948 | ||
949 |
# Close connection when finished (without Content-Length header) |
|
950 |
$c->write('Hel' => sub { |
|
951 |
my $c = shift; |
|
952 |
$c->write('lo!' => sub { |
|
953 |
my $c = shift; |
|
954 |
$c->finish; |
|
955 |
}); |
|
956 |
}); |
|
957 | ||
958 |
For Comet (long polling) you might also want to increase the inactivity |
|
959 |
timeout, which usually defaults to C<15> seconds. |
|
960 | ||
961 |
# Increase inactivity timeout for connection to 300 seconds |
|
962 |
Mojo::IOLoop->stream($c->tx->connection)->timeout(300); |
|
963 | ||
964 |
=head2 write_chunk |
|
965 | ||
966 |
$c = $c->write_chunk; |
|
967 |
$c = $c->write_chunk($bytes); |
|
968 |
$c = $c->write_chunk(sub {...}); |
|
969 |
$c = $c->write_chunk($bytes => sub {...}); |
|
970 | ||
971 |
Write dynamic content non-blocking with C<chunked> transfer encoding, the |
|
972 |
optional drain callback will be invoked once all data has been written. |
|
973 | ||
974 |
# Make sure previous chunk has been written before continuing |
|
975 |
$c->write_chunk('He' => sub { |
|
976 |
my $c = shift; |
|
977 |
$c->write_chunk('ll' => sub { |
|
978 |
my $c = shift; |
|
979 |
$c->finish('o!'); |
|
980 |
}); |
|
981 |
}); |
|
982 | ||
983 |
You can call L</"finish"> at any time to end the stream. |
|
984 | ||
985 |
2 |
|
986 |
He |
|
987 |
2 |
|
988 |
ll |
|
989 |
2 |
|
990 |
o! |
|
991 |
0 |
|
992 | ||
993 |
=head1 HELPERS |
|
994 | ||
995 |
In addition to the attributes and methods above you can also call helpers on |
|
996 |
L<Mojolicious::Controller> objects. This includes all helpers from |
|
997 |
L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>. |
|
998 | ||
999 |
$c->layout('green'); |
|
1000 |
$c->title('Welcome!'); |
|
1001 | ||
1002 |
=head1 SEE ALSO |
|
1003 | ||
1004 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
1005 | ||
1006 |
=cut |