| Mojolicious documentation | Contained in the Mojolicious distribution. |
Mojolicious::Controller - Controller Base Class
use Mojo::Base 'Mojolicious::Controller';
Mojolicious::Controller is the base class for your Mojolicious
controllers.
It is also the default controller class for Mojolicious unless you set
controller_class in your application.
Mojolicious::Controller inherits all attributes from Mojo::Base and implements the following new ones.
appmy $app = $c->app; $c = $c->app(Mojolicious->new);
A reference back to the Mojolicious application that dispatched to this controller.
matchmy $m = $c->match;
A Mojolicious::Routes::Match object containing the routes results for the current request.
txmy $tx = $c->tx;
The transaction that is currently being processed, defaults to a Mojo::Transaction::HTTP object.
Mojolicious::Controller inherits all methods from Mojo::Base and implements the following new ones.
cookiefinish $c->finish;
$c->finish('Bye!');
Gracefully end WebSocket connection or long poll stream.
flash my $flash = $c->flash;
my $foo = $c->flash('foo');
$c = $c->flash({foo => 'bar'});
$c = $c->flash(foo => 'bar');
Data storage persistent for the next request, stored in the session.
$c->flash->{foo} = 'bar';
my $foo = $c->flash->{foo};
delete $c->flash->{foo};
on_finish $c->on_finish(sub {...});
Callback to be invoked when the transaction has been finished.
$c->on_finish(sub {
my $c = shift;
});
on_message $c = $c->on_message(sub {...});
Callback to be invoked when new WebSocket messages arrive.
$c->on_message(sub {
my ($c, $message) = @_;
});
param my @names = $c->param;
my $foo = $c->param('foo');
my @foo = $c->param('foo');
$c = $c->param(foo => 'ba;r');
Access GET/POST parameters and route captures that are not reserved stash values.
# Only GET parameters
my $foo = $c->req->url->query->param('foo');
# Only GET and POST parameters
my $foo = $c->req->param('foo');
redirect_to $c = $c->redirect_to('named');
$c = $c->redirect_to('named', foo => 'bar');
$c = $c->redirect_to('/path');
$c = $c->redirect_to('http://127.0.0.1/foo/bar');
Prepare a 302 redirect response, takes the exact same arguments as
url_for.
return $c->redirect_to('login') unless $c->session('user');
render $c->render;
$c->render(controller => 'foo', action => 'bar');
$c->render({controller => 'foo', action => 'bar'});
$c->render(text => 'Hello!');
$c->render(template => 'index');
$c->render(template => 'foo/index');
$c->render(template => 'index', format => 'html', handler => 'epl');
$c->render(handler => 'something');
$c->render('foo/bar');
$c->render('foo/bar', format => 'html');
This is a wrapper around Mojolicious::Renderer exposing pretty much all
functionality provided by it.
It will set a default template to use based on the controller and action name
or fall back to the route name.
You can call it with a hash of options which can be preceded by an optional
template name.
It will also run the before_render plugin hook.
render_content my $output = $c->render_content;
my $output = $c->render_content('header');
my $output = $c->render_content(header => 'Hello world!');
my $output = $c->render_content(header => sub { 'Hello world!' });
Contains partial rendered templates, used for the renderers layout and
extends features.
render_data$c->render_data($bits); $c->render_data($bits, format => 'png');
Render the given content as raw bytes, similar to render_text but data
will not be encoded.
render_exception $c->render_exception('Oops!');
$c->render_exception(Mojo::Exception->new('Oops!'));
Render the exception template exception.$mode.html.$handler or
exception.html.$handler and set the response status code to 500.
render_json $c->render_json({foo => 'bar'});
$c->render_json([1, 2, -3]);
Render a data structure as JSON.
render_later$c->render_later;
Disable auto rendering, especially for long polling this can be quite useful.
$c->render_later;
Mojo::IOLoop->timer(2 => sub {
$c->render(text => 'Delayed by 2 seconds!');
});
render_not_found$c->render_not_found; $c->render_not_found($resource);
Render the not found template not_found.$mode.html.$handler or
not_found.html.$handler and set the response status code to 404.
render_partial my $output = $c->render_partial('menubar');
my $output = $c->render_partial('menubar', format => 'txt');
Same as render but returns the rendered result.
render_static my $success = $c->render_static('images/logo.png');
my $success = $c->render_static('../lib/MyApp.pm');
Render a static file using Mojolicious::Static relative to the
public directory of your application.
render_text $c->render_text('Hello World!');
$c->render_text('Hello World', layout => 'green');
Render the given content as Perl characters, which will be encoded to bytes.
See render_data for an alternative without encoding.
Note that this does not change the content type of the response, which is
text/html;charset=UTF-8 by default.
$c->render_text('Hello World!', format => 'txt');
rendered$c = $c->rendered; $c = $c->rendered(302);
Finalize response and run after_dispatch plugin hook.
reqmy $req = $c->req;
Alias for $c->tx->req.
Usually refers to a Mojo::Message::Request object.
resmy $res = $c->res;
Alias for $c->tx->res.
Usually refers to a Mojo::Message::Response object.
send_message $c = $c->send_message('Hi there!');
$c = $c->send_message('Hi there!', sub {...});
Send a message via WebSocket, only works if there is currently a WebSocket connection in progress.
session my $session = $c->session;
my $foo = $c->session('foo');
$c = $c->session({foo => 'bar'});
$c = $c->session(foo => 'bar');
Persistent data storage, by default stored in a signed cookie. Note that cookies are generally limited to 4096 bytes of data.
$c->session->{foo} = 'bar';
my $foo = $c->session->{foo};
delete $c->session->{foo};
signed_cookiestash my $stash = $c->stash;
my $foo = $c->stash('foo');
$c = $c->stash({foo => 'bar'});
$c = $c->stash(foo => 'bar');
Non persistent data storage and exchange.
$c->stash->{foo} = 'bar';
my $foo = $c->stash->{foo};
delete $c->stash->{foo};
uamy $ua = $c->ua;
A Mojo::UserAgent prepared for the current environment.
# Blocking
my $tx = $c->ua->get('http://mojolicio.us');
my $tx = $c->ua->post_form('http://kraih.com/login' => {user => 'mojo'});
# Non-blocking
$c->ua->get('http://mojolicio.us' => sub {
my $tx = pop;
$c->render_data($tx->res->body);
});
url_for my $url = $c->url_for;
my $url = $c->url_for(controller => 'bar', action => 'baz');
my $url = $c->url_for('named', controller => 'bar', action => 'baz');
my $url = $c->url_for('/perldoc');
my $url = $c->url_for('http://mojolicio.us/perldoc');
Generate a portable Mojo::URL object with base for a route, path or URL.
write $c->write;
$c->write('Hello!');
$c->write(sub {...});
$c->write('Hello!', sub {...});
Write dynamic content chunk wise, the optional drain callback will be invoked once all data has been written to the kernel send buffer or equivalent.
# Keep connection alive (with Content-Length header)
$c->res->headers->content_length(6);
$c->write('Hel', sub {
my $c = shift;
$c->write('lo!')
});
# Close connection when done (without Content-Length header)
$c->write('Hel', sub {
my $c = shift;
$c->write('lo!', sub {
my $c = shift;
$c->finish;
});
});
write_chunk $c->write_chunk;
$c->write_chunk('Hello!');
$c->write_chunk(sub {...});
$c->write_chunk('Hello!', sub {...});
Write dynamic content chunk wise with the chunked Transfer-Encoding
which doesn't require a Content-Length header, the optional drain callback
will be invoked once all data has been written to the kernel send buffer or
equivalent.
$c->write_chunk('He', sub {
my $c = shift;
$c->write_chunk('ll', sub {
my $c = shift;
$c->finish('o!');
});
});
You can call finish at any time to end the stream.
2 He 2 ll 2 o! 0
Mojolicious, Mojolicious::Guides, http://mojolicio.us.
| Mojolicious documentation | Contained in the Mojolicious distribution. |
package Mojolicious::Controller; use Mojo::Base -base; use Mojo::Asset::File; use Mojo::ByteStream; use Mojo::Cookie::Response; use Mojo::Exception; use Mojo::Transaction::HTTP; use Mojo::URL; use Mojo::Util; require Carp; require File::Basename; require File::Spec; # "Scalpel... blood bucket... priest." has [qw/app match/]; has tx => sub { Mojo::Transaction::HTTP->new }; # Template directory my $T = File::Spec->catdir(File::Basename::dirname(__FILE__), 'templates'); # Exception template our $EXCEPTION = Mojo::Asset::File->new(path => File::Spec->catfile($T, 'exception.html.ep')) ->slurp; # Exception template (development) our $DEVELOPMENT_EXCEPTION = Mojo::Asset::File->new( path => File::Spec->catfile($T, 'exception.development.html.ep'))->slurp; # Not found template our $NOT_FOUND = Mojo::Asset::File->new(path => File::Spec->catfile($T, 'not_found.html.ep')) ->slurp; # Not found template (development) our $DEVELOPMENT_NOT_FOUND = Mojo::Asset::File->new( path => File::Spec->catfile($T, 'not_found.development.html.ep'))->slurp; # Reserved stash values my @RESERVED = ( qw/action app cb class controller data exception extends format handler/, qw/json layout method namespace partial path status template text/ ); my %RESERVED; $RESERVED{$_}++ for @RESERVED; # "Is all the work done by the children? # No, not the whipping." sub AUTOLOAD { my $self = shift; # Method my ($package, $method) = our $AUTOLOAD =~ /^([\w\:]+)\:\:(\w+)$/; # Call helper Carp::croak(qq/Can't locate object method "$method" via package "$package"/) unless my $helper = $self->app->renderer->helpers->{$method}; $self->$helper(@_); } sub DESTROY { } # "For the last time, I don't like lilacs! # Your first wife was the one who liked lilacs! # She also liked to shut up!" sub cookie { my ($self, $name, $value, $options) = @_; return unless $name; # Response cookie if (defined $value) { # Cookie too big $self->app->log->error(qq/Cookie "$name" is bigger than 4096 bytes./) if length $value > 4096; # Create new cookie $options ||= {}; my $cookie = Mojo::Cookie::Response->new( name => $name, value => $value, %$options ); $self->res->cookies($cookie); return $self; } # Request cookie unless (wantarray) { return unless my $cookie = $self->req->cookie($name); return $cookie->value; } # Request cookies my @cookies = $self->req->cookie($name); map { $_->value } @cookies; } # "Something's wrong, she's not responding to my poking stick." sub finish { my ($self, $chunk) = @_; # WebSocket my $tx = $self->tx; return $tx->finish if $tx->is_websocket; # Chunked stream if ($tx->res->is_chunked) { $self->write_chunk($chunk) if defined $chunk; return $self->write_chunk(''); } # Normal stream $self->write($chunk) if defined $chunk; $self->write(''); } # "You two make me ashamed to call myself an idiot." sub flash { my $self = shift; # Get my $session = $self->stash->{'mojo.session'}; if ($_[0] && !defined $_[1] && !ref $_[0]) { return unless $session && ref $session eq 'HASH'; return unless my $flash = $session->{flash}; return unless ref $flash eq 'HASH'; return $flash->{$_[0]}; } # Initialize $session = $self->session; my $flash = $session->{new_flash}; $flash = {} unless $flash && ref $flash eq 'HASH'; $session->{new_flash} = $flash; # Hash return $flash unless @_; # Set my $values = exists $_[1] ? {@_} : $_[0]; $session->{new_flash} = {%$flash, %$values}; $self; } # "My parents may be evil, but at least they're stupid." sub on_finish { my ($self, $cb) = @_; $self->tx->on_finish(sub { shift and $self->$cb(@_) }); } sub on_message { my $self = shift; my $tx = $self->tx; Carp::croak('No WebSocket connection to receive messages from') unless $tx->is_websocket; my $cb = shift; $tx->on_message(sub { shift and $self->$cb(@_) }); $self->rendered(101); $self; } # "Just make a simple cake. And this time, if someone's going to jump out of # it make sure to put them in *after* you cook it." sub param { my $self = shift; my $name = shift; # List my $p = $self->stash->{'mojo.captures'} || {}; unless (defined $name) { my %seen; my @keys = grep { !$seen{$_}++ } $self->req->param; push @keys, grep { !$RESERVED{$_} && !$seen{$_}++ } keys %$p; return sort @keys; } # Override value if (@_) { $p->{$name} = $_[0]; return $self; } # Captured unreserved value return $p->{$name} if !$RESERVED{$name} && exists $p->{$name}; # Param value $self->req->param($name); } # "Is there an app for kissing my shiny metal ass? # Several! # Oooh!" sub redirect_to { my $self = shift; my $headers = $self->res->headers; $headers->location($self->url_for(@_)->to_abs); $headers->content_length(0); $self->rendered(302); $self; } # "Mamma Mia! The cruel meatball of war has rolled onto our laps and ruined # our white pants of peace!" sub render { my $self = shift; # Recursion my $stash = $self->stash; if ($stash->{'mojo.rendering'}) { $self->app->log->debug(qq/Can't render in "before_render" hook./); return ''; } # Template may be first argument my $template; $template = shift if @_ % 2 && !ref $_[0]; my $args = ref $_[0] ? $_[0] : {@_}; # Template $args->{template} = $template if $template; unless ($stash->{template} || $args->{template}) { # Default template my $controller = $args->{controller} || $stash->{controller}; my $action = $args->{action} || $stash->{action}; # Normal default template if ($controller && $action) { $self->stash->{template} = join('/', split(/-/, $controller), $action); } # Try the route name if we don't have controller and action elsif ($self->match && $self->match->endpoint) { $self->stash->{template} = $self->match->endpoint->name; } } # Render my $app = $self->app; { local $stash->{'mojo.rendering'} = 1; $app->plugins->run_hook_reverse(before_render => $self, $args); } my ($output, $type) = $app->renderer->render($self, $args); return unless defined $output; return $output if $args->{partial}; # Prepare response my $res = $self->res; $res->body($output) unless $res->body; my $headers = $res->headers; $headers->content_type($type) unless $headers->content_type; $self->rendered($stash->{status}); 1; } sub render_content { my $self = shift; my $name = shift; my $content = pop; # Initialize my $stash = $self->stash; my $c = $stash->{'mojo.content'} ||= {}; $name ||= 'content'; # Set if (defined $content) { # Reset with multiple values if (@_) { $c->{$name} = ''; for my $part (@_, $content) { $c->{$name} .= ref $part eq 'CODE' ? $part->() : $part; } } # First come else { $c->{$name} ||= ref $content eq 'CODE' ? $content->() : $content; } } # Get $content = $c->{$name}; $content = '' unless defined $content; Mojo::ByteStream->new("$content"); } sub render_data { shift->render(data => shift, @_) } # "The path to robot hell is paved with human flesh. # Neat." sub render_exception { my ($self, $e) = @_; $e = Mojo::Exception->new($e); $self->app->log->error($e); # Recursion return if $self->stash->{'mojo.exception'}; # Filtered stash snapshot my $snapshot = {}; my $stash = $self->stash; for my $key (keys %$stash) { next if $key =~ /^mojo\./; next unless defined(my $value = $stash->{$key}); $snapshot->{$key} = $value; } # Mode specific template my $mode = $self->app->mode; my $options = { template => "exception.$mode", format => 'html', handler => undef, status => 500, snapshot => $snapshot, exception => $e, 'mojo.exception' => 1 }; unless ($self->render($options)) { # Template $options->{template} = 'exception'; unless ($self->render($options)) { # Inline template delete $stash->{layout}; delete $stash->{extends}; delete $options->{template}; $options->{inline} = $mode eq 'development' ? $DEVELOPMENT_EXCEPTION : $EXCEPTION; $options->{handler} = 'ep'; $self->render($options); } } } # DEPRECATED in Smiling Face With Sunglasses! sub render_inner { warn <<EOF; Mojolicious::Controller->render_inner is DEPRECATED in favor of Mojolicious::Controller->render_content!!! EOF shift->render_content(@_); } # "If you hate intolerance and being punched in the face by me, # please support Proposition Infinity." sub render_json { my $self = shift; my $json = shift; my $args = ref $_[0] ? $_[0] : {@_}; $args->{json} = $json; $self->render($args); } sub render_later { shift->stash->{'mojo.rendered'} = 1 } # "Excuse me, sir, you're snowboarding off the trail. # Lick my frozen metal ass." sub render_not_found { my ($self, $resource) = @_; $self->app->log->debug(qq/Resource "$resource" not found./) if $resource; # Recursion my $stash = $self->stash; return if $stash->{'mojo.exception'}; return if $stash->{'mojo.not_found'}; # Check for POD plugin my $guide = $self->app->renderer->helpers->{pod_to_html} ? $self->url_for('/perldoc') : 'http://mojolicio.us/perldoc'; # Mode specific template my $mode = $self->app->mode; my $options = { template => "not_found.$mode", format => 'html', status => 404, guide => $guide, 'mojo.not_found' => 1 }; unless ($self->render($options)) { # Template $options->{template} = 'not_found'; unless ($self->render($options)) { # Inline template delete $options->{layout}; delete $options->{extends}; delete $options->{template}; $options->{inline} = $mode eq 'development' ? $DEVELOPMENT_NOT_FOUND : $NOT_FOUND; $options->{handler} = 'ep'; $self->render($options); } } } # "You called my thesis a fat sack of barf, and then you stole it? # Welcome to academia." sub render_partial { my $self = shift; my $template = @_ % 2 ? shift : undef; my $args = {@_}; $args->{template} = $template if defined $template; $args->{partial} = 1; Mojo::ByteStream->new($self->render($args)); } sub render_static { my ($self, $file) = @_; my $app = $self->app; unless ($app->static->serve($self, $file)) { $app->log->debug( qq/Static file "$file" not found, public directory missing?/); return; } $self->rendered; 1; } sub render_text { shift->render(text => shift, @_) } # "On the count of three, you will awaken feeling refreshed, # as if Futurama had never been canceled by idiots, # then brought back by bigger idiots. One. Two." sub rendered { my ($self, $status) = @_; # Disable auto rendering $self->render_later; # Make sure we have a status my $res = $self->res; $res->code($status) if $status; # Finish transaction my $stash = $self->stash; unless ($stash->{'mojo.finished'}) { $res->code(200) unless $res->code; my $app = $self->app; $app->plugins->run_hook_reverse(after_dispatch => $self); $app->sessions->store($self); $stash->{'mojo.finished'} = 1; } $self->tx->resume; $self; } sub req { shift->tx->req } sub res { shift->tx->res } sub send_message { my ($self, $message, $cb) = @_; my $tx = $self->tx; Carp::croak('No WebSocket connection to send message to') unless $tx->is_websocket; $tx->send_message($message, sub { shift and $self->$cb(@_) if $cb }); $self->rendered(101); $self; } # "Why am I sticky and naked? Did I miss something fun?" sub session { my $self = shift; # Get my $stash = $self->stash; my $session = $stash->{'mojo.session'}; if ($_[0] && !defined $_[1] && !ref $_[0]) { return unless $session && ref $session eq 'HASH'; return $session->{$_[0]}; } # Hash $session = {} unless $session && ref $session eq 'HASH'; $stash->{'mojo.session'} = $session; return $session unless @_; # Set my $values = exists $_[1] ? {@_} : $_[0]; $stash->{'mojo.session'} = {%$session, %$values}; $self; } sub signed_cookie { my ($self, $name, $value, $options) = @_; return unless $name; # Response cookie my $secret = $self->app->secret; if (defined $value) { # Sign value my $signature = Mojo::Util::hmac_md5_sum $value, $secret; $value = $value .= "--$signature"; # Create cookie my $cookie = $self->cookie($name, $value, $options); return $cookie; } # Request cookies my @values = $self->cookie($name); my @results; for my $value (@values) { # Check signature if ($value =~ s/\-\-([^\-]+)$//) { my $signature = $1; my $check = Mojo::Util::hmac_md5_sum $value, $secret; # Verified if ($signature eq $check) { push @results, $value } # Bad cookie else { $self->app->log->debug( qq/Bad signed cookie "$name", possible hacking attempt./); } } # Not signed else { $self->app->log->debug(qq/Cookie "$name" not signed./) } } wantarray ? @results : $results[0]; } # "All this knowledge is giving me a raging brainer." sub stash { my $self = shift; # Hash $self->{stash} ||= {}; return $self->{stash} unless @_; # Get return $self->{stash}->{$_[0]} unless @_ > 1 || ref $_[0]; # Set my $values = ref $_[0] ? $_[0] : {@_}; for my $key (keys %$values) { $self->app->log->debug(qq/Careful, "$key" is a reserved stash value./) if $RESERVED{$key}; $self->{stash}->{$key} = $values->{$key}; } $self; } sub ua { shift->app->ua } # "Behold, a time traveling machine. # Time? I can't go back there! # Ah, but this machine only goes forward in time. # That way you can't accidentally change history or do something disgusting # like sleep with your own grandmother. # I wouldn't want to do that again." sub url_for { my $self = shift; my $target = shift || ''; # Absolute URL return Mojo::URL->new($target) if $target =~ /^\w+\:\/\//; # Make sure we have a match for named routes my $match; unless ($match = $self->match) { $match = Mojolicious::Routes::Match->new(get => '/'); $match->root($self->app->routes); } # Base my $url = Mojo::URL->new; my $req = $self->req; $url->base($req->url->base->clone); my $base = $url->base; $base->userinfo(undef); # Relative URL my $path = $url->path; if ($target =~ /^\//) { if (my $e = $self->stash->{path}) { my $real = $req->url->path->to_abs_string; Mojo::Util::url_unescape($real); my $backup = $real; Mojo::Util::decode('UTF-8', $real); $real = $backup unless defined $real; $real =~ s/\/?$e$/$target/; $target = $real; } $url->parse($target); } # Route else { my ($p, $ws) = $match->path_for($target, @_); $path->parse($p) if $p; # Fix scheme for WebSockets $base->scheme(($base->scheme || '') eq 'https' ? 'wss' : 'ws') if $ws; } # Make path absolute my $base_path = $base->path; unshift @{$path->parts}, @{$base_path->parts}; $base_path->parts([]); $url; } # "I wax my rocket every day!" sub write { my ($self, $chunk, $cb) = @_; if (ref $chunk && ref $chunk eq 'CODE') { $cb = $chunk; $chunk = undef; } $self->res->write($chunk, sub { shift and $self->$cb(@_) if $cb }); $self->rendered; $self; } sub write_chunk { my ($self, $chunk, $cb) = @_; if (ref $chunk && ref $chunk eq 'CODE') { $cb = $chunk; $chunk = undef; } $self->res->write_chunk($chunk, sub { shift and $self->$cb(@_) if $cb }); $self->rendered; $self; } 1; __END__