TABLE OF CONTENTS

文档

Mojolicious::Guides::Rendering - 渲染

概述

本文档介绍通过 Mojolicious 来渲染生成网页展示用的内容. 本文档更新到 5.06

概念

Mojolicious 一定都知道这是什么.

Renderer

在这指的渲染器是一个很小黑盒, 用于把 stash 的数据转化为实际的响应, 其中利用到多种模板系统和数据编码模块.

{text => 'Hello.'}                 -> 200 OK, text/html, 'Hello.'
{json => {x => 3}}                 -> 200 OK, application/json, '{"x":3}'
{text => 'Oops.', status => '410'} -> 410 Gone, text/html, 'Oops.'

模板路径可以被自动检测到, 如果提供一些象 route 之类参数的信息. 模板名遵循 name.format.handler 规则, 其中 name 是定义的 controller/action 动作名或者是路由选择的名字, format 默认是 html, 其中 handler 是指由什么模板技术来处理, 默认是 ep.

{controller => 'users', action => 'list'} -> 'users/list.html.ep'
{name => 'foo', format => 'txt'}          -> 'foo.txt.ep'
{name => 'foo', handler => 'epl'}         -> 'foo.html.epl'

在这 controller 会使用 "decamelize" in Mojo::Util 来给变量值象 CamelCase 转换成 snake_case, 并会给 - 替换成 /.

{controller => 'My::Users', action => 'add'} -> 'my/users/add.html.ep'
{controller => 'my-users', action => 'show'} -> 'my/users/show.html.ep'

全部的模板文件放我们应用的 templates 的目录中, 我们可以通过 "paths" in Mojolicious::Renderer 来自定义, 或者是放在应用 "classes" in Mojolicious::RendererDATA 部分.

__DATA__

@@ time.html.ep
% use Time::Piece;
% my $now = localtime;
<!DOCTYPE html>
<html>
  <head><title>Time</title></head>
  <body>The time is <%= $now->hms %>.</body>
</html>

@@ hello.txt.ep
...

渲染器可以很容易地扩展, 以插件的方式来支持额外的模板系统.

Embedded Perl

Mojolicious 包含简单但非常强大的模板系统, 叫 Embedded Perl 或 ep. 它可以嵌入 Perl 代码, 使用特别的标记并转化为实际的内容. 在模板中, 会自动打开 strict, warnings, utf8, 如果是 Perl 5.10 会自动启动它的特性.

<% Perl 代码 %>
<%= Perl 表达式, 结果表达式的结果经过 XML 转义 %>
<%== Perl 表达式, 结果是什么就显示什么 %>
<%# 注释, 用于 debug %>
<%% 替换为 "<%", 用于生成模板 %>
% Perl 代码行, 和这个一样 "<% line =%>"
%= Perl 表达式,这个一样  "<%= line %>"
%== Perl 达式,这个一样 "<%== line %>"
%# 注释,这个一样 "<%# line =%>"
%% 换为 "%", 用于生成模板 

使用 <% 的 Tag 和使用 % 的行是一样的, 但是根据不同的上下文时, 选择使用不同的标签看起来会好些. 分号会自动追加到所有的表达式.

<% my $i = 10; %>
<ul>
  <% for my $j (1 .. $i) { %>
    <li>
      <%= $j %>
    </li>
  <% } %>
</ul>

% my $i = 10;
<ul>
  % for my $j (1 .. $i) {
    <li>
      %= $j
    </li>
  % }
</ul>

除了空白处理的差异, 这两个例子产生类似 Perl 功能代码, 我们可以天真的理解成这个样子.

my $output = '';
my $i = 10;
$output .= '<ul>';
for my $j (1 .. $i) {
  $output .= '<li>';
  $output .= xml_escape scalar $j;
  $output .= '</li>';
}
$output .= '</ul>';
return $output;

一些额外的等号可以用于禁用在 Perl 表达式中的转义的字符 <, >, &, '" . 这是默认设置, 以防止XSS攻击.

<%= 'I ♥ Mojolicious!' %>
<%== '<p>I ♥ Mojolicious!</p>' %>

Mojo::ByteStream 的对象是排除在自动转义之外的.

<%= b('<p>I ♥ Mojolicious!</p>') %>

您还可以对结束标签添加额外的等号, 它会自动删除所有周围的空白, 可以自由缩进, 而不破坏的结果.

<% for (1 .. 3) { %>
  <%= 'trim all whitespace characters around this expression' =%>
<% } %>

换行符可以用一个反斜杠转义.

This is <%= 1 + 1 %> a\
single line

一个换行符前的反斜杠, 可以用另一个反斜杠转义.

This will <%= 1 + 1 %> result\\
in multiple\\
lines

Stash 的值不会有无效的字符的, 因为它们的名字会在模板和 controller 的 $c 的对象中中自动初始化.

$c->stash(name => 'tester');

Hello <%= $name %> from <%= $c->tx->remote_address %>.

A prefix like myapp.* is commonly used for stash values that you don't want to expose in templates.

$c->stash('myapp.name' => 'tester');

在后面我们还会象很多的辅助功能

<%= dumper {foo => 'bar'} %>

基础

每个 Mojolicious 开发都都需要知道的最常用的功能.

自动渲染

渲染可以通过调用方法 "render" in Mojolicious::Controller 手动启动, 但通常没有必要, 因为路由完后会自动调用. 这也意味着你可以只给路由指向模板来输出, 但没有实际的 action.

$c->render;

这有个区别, 在手动调用它时, 是使用的当前的控制器对象, 并不是 "controller_class" in Mojolicious 中指定的默认的类.

$c->render_later;

使用 "render_later" in Mojolicious::Controller 这个会禁用自动渲染, 这对于我们要使用非阻塞来延迟渲染输出非常有用, 所以必须先执行这个.

渲染模板

渲染器会自动的检查合适的模板, 当然你也可以使用 stash 中的变量 template 来指定一个.

# foo/bar/baz.*.*
$c->render(template => 'foo/bar/baz');

还可以选择指定的 format 输出和 handler 处理也很容易.

# foo/bar/baz.txt.epl
$c->render(template => 'foo/bar/baz', format => 'txt', handler => 'epl');

因为最常用的功能是显示指定的模板, 所以上面的太过于复杂, 可以使用下面的做为代替.

$c->render('foo/bar');

如果你并不能提前知道这个模板是否存在, 可以使用 "render_maybe" in Mojolicious::Controller 这个方法来尝试是否存在.

$c->render_maybe('localized/baz') or $c->render('foo/bar/baz');

渲染成字符串

有时你可能想直接使用渲染的结果, 而并不是直接生成响应, 例如发送电子邮件, 这可以通过 "render_to_string" in Mojolicious::Controller 来做.

my $html = $c->render_to_string('mail');

上面执行时数据并不会被编码, 所以很容易重用它的结果在其它的模板或者生成进制的数据.

my $pdf = $c->render_to_string('invoice', format => 'pdf');
$c->render(data => $pdf, format => 'pdf');

All arguments passed will get localized automatically and are only available during this render operation.

Template variants

为了使你的应用程序在不同的设备上效果都能很好, 你也可以使用 variant 的 stash 的值来改变你所选择的不同的模板.

# foo/bar/baz.html+phone.ep
# foo/bar/baz.html.ep
$c->render('foo/bar/baz', variant => 'phone');

这个功能非常宽松, 因为只有它有合适的模板名字确实存在时才会生效, 不然会退回到普通的状态.

渲染 inline 模板

ep 之内, 也可以允许内联来使用.

$c->render(inline => 'The result is <%= 1 + 1%>.');

默认是使用自动来决定 handler 来做解析, 你也可以指定 handler.

$c->render(inline => "<%= shift->param('foo') %>", handler => 'epl');

渲染文本

在这我们可以直接显示 Perl 中的字符, 只要使用 text 就行了, 这个会自动编码成字节数据.

$c->render(text => 'I ♥ Mojolicious!');

渲染 data

原始字节可以使用 data 的选项, 这样不会对数据进行编码.

$c->render(data => $octets);

渲染 JSON

这个只需要给使用 json 的参数. 然后提供一个 Perl 的数据结构就行了.

$c->render(json => {foo => [1, 'test', 3]});

Partial rendering

Sometimes you might want to access the rendered result, for example to generate emails, this can be done using the partial stash value.

my $html = $c->render('mail', partial => 1);

Status code

响应码可以通过 status 的变量来进行修改.

$c->render(text => 'Oops.', status => 500);

Content 类型

这个 Content-Type 的头是由 format 的内容基于MIME类型映射的.

# Content-Type: text/plain
$c->render(text => 'Hello.', format => 'txt');

# Content-Type: image/png
$c->render(data => $bytes, format => 'png');

这个映射可以由 "types" in Mojolicious 中来扩展.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $self = shift;

  # Add new MIME type
  $self->types->type(txt => 'text/plain; charset=utf-8');
}

1;

Stash 数据

任何原生的 Perl 数组类型都可以通过 "stash" in Mojolicious::Controller 传给模板.

$c->stash(author     => 'Sebastian');
$c->stash(frameworks => [qw(Catalyst Mojolicious)]);
$c->stash(examples   => {tweetylicious => 'a microblogging app'});

%= $author
%= $frameworks->[1]
%= $examples->{tweetylicious}

所有的这些都是 Perl 标准的控制结构.

% for my $framework (@$frameworks) {
  <%= $framework %> was written by <%= $author %>.
% }

% while (my ($app, $description) = each %$examples) {
  <%= $app %> is a <%= $description %>.
% }

Content 关联

RESTful 的内容中, 可以对资源有不同的显示方式, 我们只需要使用 "respond_to" in Mojolicious::Controller 来替换掉 "render" in Mojolicious::Controller 来对不同的请求输出不同的东西.

# /hello (Accept: application/json) -> "json"
# /hello (Accept: application/xml)  -> "xml"
# /hello.json                       -> "json"
# /hello.xml                        -> "xml"
# /hello?format=json                -> "json"
# /hello?format=xml                 -> "xml"
$c->respond_to(
  json => {json => {hello => 'world'}},
  xml  => {text => '<hello>world</hello>'}
);

默认会自动的根据请求头中的 Accept, stash 值中的 format 或者 GET/POST 的参数中存储的的 format 来自动选择最好的表示方式. 要为 Accept 请求头或者 Content-Type 的响应头来更改 MIME 类型的映射关系, 可以使用 "types" in Mojolicious.

$c->respond_to(
  json => {json => {hello => 'world'}},
  html => sub {
    $c->content_for(head => '<meta name="author" content="sri" />');
    $c->render(template => 'hello', message => 'world')
  }
);

回调可以在单个渲染中实现非常复杂的内容协商的功能.

# /hello (Accept: application/json) -> "json"
# /hello (Accept: text/html)        -> "html"
# /hello (Accept: image/png)        -> "any"
# /hello.json                       -> "json"
# /hello.html                       -> "html"
# /hello.png                        -> "any"
# /hello?format=json                -> "json"
# /hello?format=html                -> "html"
# /hello?format=png                 -> "any"
$c->respond_to(
  json => {json => {hello => 'world'}},
  html => {template => 'hello', message => 'world'},
  any  => {text => '', status => 204}
);

任何没有发现的请求都会使用 any 这个来响应一个空的 204 的响应.

# /hello                      -> "html"
# /hello (Accept: text/html)  -> "html"
# /hello (Accept: text/xml)   -> "xml"
# /hello (Accept: text/plain) -> undef
# /hello.html                 -> "html"
# /hello.xml                  -> "xml"
# /hello.txt                  -> undef
# /hello?format=html          -> "html"
# /hello?format=xml           -> "xml"
# /hello?format=txt           -> undef
if (my $format = $c->accepts('html', 'xml')) {
        ...
}

更加先进的关联逻辑的关系, 你可以使用 "accepts" in Mojolicious::Plugin::DefaultHelpers 的 helper.

渲染 exception 和 not_found 的网页

到现在你可能见过了内置默认的的 404 (未找到) 和 500 (服务器错误) 的网页, 当出错时会自动被渲染输出. 特别是在开发过程中, 这些错误是一个很有帮助的. 我们可以通过 "reply->exception" in Mojolicious::Plugin::DefaultHelpers"reply->not_found" in Mojolicious::Plugin::DefaultHelpers 的方法来定制自己的网页.

use Mojolicious::Lite;
use Scalar::Util 'looks_like_number';

get '/divide/:dividend/by/:divisor' => sub {
  my $c = shift;
  my ($dividend, $divisor) = $c->param(['dividend', 'divisor']);

  # 404
  return $c->reply->not_found
    unless looks_like_number $dividend && looks_like_number $divisor;

  # 500
  return $c->reply->exception('Division by zero!') if $divisor == 0;

  # 200
  $c->render(text => $dividend / $divisor);
};

app->start;

你也可以修改这些网页的模板, 因为你可能很希望对不同的用户定制出错的网页, 所以你可以使用内部的模板, 直接在内部使用 exception.$mode.$format.*not_found.$mode.$format.* 会默认被使用.

@@ exception.production.html.ep
<!DOCTYPE html>
<html>
  <head><title>Server error</title></head>
  <body>
    <h1>Exception</h1>
    <p><%= $exception->message %></p>
    <h1>Stash</h1>
    <pre><%= dumper $snapshot %></pre>
  </body>
</html>

"before_render" in Mojolicious 的这个 hook 点可以让你拦截并自定义修改传递给渲染器的参数.

use Mojolicious::Lite;

hook before_render => sub {
  my ($c, $args) = @_;

  # Make sure we are rendering the exception template
  return unless my $template = $args->{template};
  return unless $template eq 'exception';

  # Switch to JSON rendering if content negotiation allows it
  $args->{json} = {exception => $c->stash('exception')}
    if $c->accepts('json');
};

get '/' => sub { die "This sho...ALL GLORY TO THE HYPNOTOAD!\n" };

app->start;

Helpers

Helpers 是一些小功能, 你可以使用模板和控制器代码的功能.

%= dumper [1, 2, 3]

my $serialized = $c->dumper([1, 2, 3]);

这个 "dumper" in Mojolicious::Plugin::DefaultHelpers 的例子是使用的 Data::Dumper 来实例化你的数据结构, 这在 debug 的时候非常有效. 这个还可以用来生成一些 HTML 的标签.

%= javascript '/script.js'

%= javascript begin
  var a = 'b';
% end

全部的原生的 heler 可以看看 Mojolicious::Plugin::DefaultHelpersMojolicious::Plugin::TagHelpers.

Layouts

大多的时候, 我们是使用 ep 模板. 这个可用来封装你的 HTML , 布局之类. 非常的简单.

@@ foo/bar.html.ep
% layout 'mylayout';
Hello World!

@@ layouts/mylayout.html.ep
<!DOCTYPE html>
<html>
  <head><title>MyApp</title></head>
  <body><%= content %></body>
</html>

你需要使用 "layout" in Mojolicious::Plugin::DefaultHelpers 来选择合适的 layout 模板并给结果在当前的模板上通过 "content" in Mojolicious::Plugin::DefaultHelpers 来显示.

@@ foo/bar.html.ep
% layout 'mylayout', title => 'Hi there';
Hello World!

@@ layouts/mylayout.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= $title %></title></head>
  <body><%= content %></body>
</html>

替代 layout helper 也可以只调用 "render" in Mojolicious::Controller 中的 layout 参数.

$c->render(template => 'mytemplate', layout => 'mylayout');

你可以在你的应用中使用 "defaults" in Mojolicious 来修改你的默认的 layout 的 stash 值.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $c = shift;

  # Default layout
  $c->defaults(layout => 'mylayout');
}

1;

Layouts 也可以通过 "render_to_string" in Mojolicious::Controller 来被使用, 但 layout 需要给一个渲染参数.

my $html = $c->render_to_string('reminder', layout => 'mail');

包括部分模板

使用 "include" in Mojolicious::Plugin::DefaultHelpers 的 helper 是一个很方便的功能.

@@ foo/bar.html.ep
<!DOCTYPE html>
<html>
  %= include 'header'
  <body>Bar</body>
</html>

@@ header.html.ep
<head><title>Howdy</title></head>

一样, 你也可以调用 "render" in Mojolicious::Controller 中来使用 partial 的参数.

@@ foo/bar.html.ep
<!DOCTYPE html>
<html>
  %= $c->render('header', partial => 1)
  <body>Bar</body>
</html>

@@ header.html.ep
<head><title>Howdy</title></head>

这二个之间有一点点差别, 就是如果你给 stash 的值传给 include, 这会自动的本地化, 并可用在局部模板中.

@@ foo/bar.html.ep
<!DOCTYPE html>
<html>
  %= include 'header', title => 'Hello'
  <body>Bar</body>
</html>

@@ header.html.ep
<head><title><%= $title %></title></head>

重用模板块

如果总是重复一定不会有意思的, 这也是为什么要重用的原困, 在 ep 的模板中也可以用, 就象 Perl 的功能一样.

@@ welcome.html.ep
<% my $block = begin %>
  <% my $name = shift; %>
  Hello <%= $name %>.
<% end %>
<%= $block->('Sebastian') %>
<%= $block->('Sara') %>

块是由 beginend 关键字分隔的.

@@ welcome.html.ep
% my $block = begin
  % my $name = shift;
  Hello <%= $name %>.
% end
% for (1 .. 10) {
  %== $block->('Sebastian')
% }

我们可以简单理解成象下面这样的 Perl 代码.

@@ welcome.html.pl
my $output = '';
my $block  = sub {
  my $name   = shift;
  my $output = '';
  $output .= 'Hello ';
  $output .= xml_escape scalar $name;
  $output .= '.';
  return Mojo::ByteStream->new($output);
}
for (1 .. 10) {
  $output .= scalar $block->('Sebastian');
}
return $output;

Content blocks

块和 "content_for" in Mojolicious::Plugin::DefaultHelpers 的 helper 也可以被用来传递到布局的模板的整个部分.

@@ foo/bar.html.ep
% layout 'mylayout';
% content_for header => begin
  <meta http-equiv="Content-Type" content="text/html">
% end
<div>Hello World!</div>
% content_for header => begin
  <meta http-equiv="Pragma" content="no-cache">
% end

@@ layouts/mylayout.html.ep
<!DOCTYPE html>
<html>
  <head><%= content_for 'header' %></head>
  <body><%= content %></body>
</html>

模板继承

继承布局概念之上又进了一步, 使用 "content" in Mojolicious::Plugin::DefaultHelpers"extends" in Mojolicious::Plugin::DefaultHelpers 可以让你建立一个框架模板, 子块可以覆盖.

@@ first.html.ep
<!DOCTYPE html>
<html>
  <head><title>Hello</title></head>
  <body>
    %= content header => begin
      Default header
    % end
    <div>Hello World!</div>
    %= content footer => begin
      Default footer
    % end
  </body>
</html>

@@ second.html.ep
% extends 'first';
% content header => begin
  New header
% end

这是一种非常高级的模板重用

表单验证 Form validation

你可以使用 "validation" in Mojolicious::Controller 来验证你的 GET/POST 所提交给你的应用程序的参数. 这个对象只检查你给出的字段, 未知的都会被忽略, 所以你在检查前这些值之前, 就需要决定哪些那些字段是可选 ( optional ) 和必须 ( required ). 检查是立即生效的, 所以你可以立即使用检查的结果使用 "is_valid" in Mojolicious::Validator::Validation 之类的方法来构建更加高级的验证逻辑.

use Mojolicious::Lite;

get '/' => sub {
  my $c = shift;

  # 检查是否有提交的参数. 
  my $validation = $c->validation;
  return $c->render unless $validation->has_data;

  # 验证参数 ("pass_again" 依赖 "pass" 才能存在)
  $validation->required('user')->size(1, 20)->like(qr/^[e-t]+$/);
  $validation->required('pass_again')->equal_to('pass')
    if $validation->optional('pass')->size(7, 500)->is_valid;

  # 如果验证成功就渲染结果
  $c->render('thanks') unless $validation->has_error;
} => 'index';

app->start;
__DATA__

@@ index.html.ep
<!DOCTYPE html>
<html>
  <head>
    %= stylesheet begin
      label.field-with-error { color: #dd7e5e }
      input.field-with-error { background-color: #fd9e7e }
    % end
  </head>
  <body>
    %= form_for index => begin
      %= label_for user => 'Username (required, 1-20 characters, only e-t)'
      <br>
      %= text_field 'user'
      %= submit_button
      <br>
      %= label_for pass => 'Password (optional, 7-500 characters)'
      <br>
      %= password_field 'pass'
      <br>
      %= label_for pass_again => 'Password again (equal to the value above)'
      <br>
      %= password_field 'pass_again'
    % end
  </body>
</html>

@@ thanks.html.ep
<!DOCTYPE html>
<html><body>Thank you <%= validation->param('user') %>.</body></html>

上面的 form 元素是由 Mojolicious::Plugin::TagHelpers 这个 helper 自动生成, 当上面参数的字段检验出错时, 就会添加 field-with-error 的 class 的字段来显示不同的 CSS 风格来进行提示.

<label class="field-with-error" for="user">
  Username (required, only characters e-t)
</label>
<input class="field-with-error" type="text" name="user" value="sri" />

全部的 checks 的列表请看 "CHECKS" in Mojolicious::Validator.

增加自定义的检查

检验检查也可以自己通过 "add_check" in Mojolicious::Validator 来注册, 只要检查通过, 就会返回的值 falsh. 如果返回一直 true 会附加 "error" in Mojolicious::Validator::Validation 的一些信息.

use Mojolicious::Lite;

# Add "range" check
app->validator->add_check(range => sub {
  my ($validation, $name, $value, $min, $max) = @_;
  return $value < $min || $value > $max;
});

get '/' => 'form';

post '/test' => sub {
  my $c = shift;

  # Validate parameters with custom check
  my $validation = $c->validation;
  $validation->required('number')->range(3, 23);

  # Render form again if validation failed
  return $c->render('form') if $validation->has_error;

  # Prevent double submit with redirect
  $c->flash(number => $validation->param('number'));
  $c->redirect_to('form');
};

app->start;
__DATA__

@@ form.html.ep
<!DOCTYPE html>
<html>
  <body>
    % if (my $number = flash 'number') {
      <p>Thanks, the number <%= $number %> was valid.</p>
    % }
    %= form_for test => begin
      % if (my $err = validation->error('number')) {
        <p>
          %= 'Value is required.' if $err->[0] eq 'required'
          %= 'Value needs to be between 3 and 23.' if $err->[0] eq 'range'
        </p>
      % }
      %= text_field 'number'
      %= submit_button
    % end
  </html>
</html>

Cross-site request forgery

CSRF 是一种很常用的攻击方法, 用于欺骗用户提交他们并没打算提交的表单 . 所以我们需要做的就是保护用户免受这种攻击, 使用的方式是使用一个额外的隐藏字段 "csrf_field" in Mojolicious::Plugin::TagHelpers 添加到您应用表单中, 然后使用 "csrf_protect" in Mojolicious::Validator::Validation 来检验它.

use Mojolicious::Lite;

get '/' => {template => 'target'};

post '/' => sub {
  my $c = shift;

  # Check CSRF token
  my $validation = $c->validation;
  return $c->render(text => 'Bad CSRF token!', status => 403)
    if $validation->csrf_protect->has_error('csrf_token');

  my $city = $validation->required('city')->param('city');
  $c->render(text => "Low orbit ion cannon pointed at $city!")
    unless $validation->has_error;
} => 'target';

app->start;
__DATA__

@@ target.html.ep
<!DOCTYPE html>
<html>
  <body>
    %= form_for target => begin
      %= csrf_field
      %= label_for city => 'Which city to point low orbit ion cannon at?'
      %= text_field 'city'
      %= submit_button
    %= end
  </body>
</html>

这个 token 会在提交的时候加一个 X-CSRF-Token 的请求头.

增加 helpers

添加和重新定义 helper 是很容易的, 你可以用它们做所有的事.

use Mojolicious::Lite;

helper debug => sub {
  my ($c, $str) = @_;
  $c->app->log->debug($str);
};

get '/' => sub {
  my $c = shift;
  $c->debug('Hello from an action!');
} => 'index';

app->start;
__DATA__

@@ index.html.ep
% debug 'Hello from a template!';

Helpers 也可以使用 template 块做最后的参数, 比如使用它做一个标签的过滤器.

use Mojolicious::Lite;
use Mojo::ByteStream;

helper trim_newline => sub {
  my ($c, $block) = @_;
  my $result = $block->();
  $result =~ s/\n//g;
  return Mojo::ByteStream->new($result);
};

get '/' => 'index';

app->start;
__DATA__

@@ index.html.ep
%= trim_newline begin
  Some text.
  %= 1 + 1
  More text.
% end

封装完的结果做成一个 Mojo::ByteStream 的对象, 可以防止被多次转义.

Helper 插件

有些 helpers 可能非常有用, 我们想在多个应用程序之间来重复使用一段代码, 所以使用 plugins 会很方便.

package Mojolicious::Plugin::DebugHelper;
use Mojo::Base 'Mojolicious::Plugin';

sub register {
  my ($c, $app) = @_;
  $app->helper(debug => sub {
    my ($c, $string) = @_;
    $c->app->log->debug($string);
  });
}

1;

这个 register 的方法会加载调用到你的应用中.

use Mojolicious::Lite;

plugin 'DebugHelper';

get '/' => sub {
  my $c = shift;
  $c->debug('It works.');
  $c->render_text('Hello.');
};

app->start;

你可以使用下面的方法很容易的生成一个 CPAN 兼容的插件.

$ mojo generate plugin DebugHelper

当然这也提供了一个 PAUSE 访问的接口给你.

$ perl Makefile.PL
$ make test
$ make manifest
$ make dist
$ mojo cpanify -u USER -p PASS Mojolicious-Plugin-DebugHelper-0.01.tar.gz

Bundling assets with plugins

绑定静态文件和模板到你的应用和插件中很容易, 即使你打算给他们放到 CPAN.

$ mojo generate plugin AlertAssets
$ mkdir AlertAssets/lib/Mojolicious/Plugin/AlertAssets
$ cd AlertAssets/lib/Mojolicious/Plugin/AlertAssets
$ mkdir public
$ echo 'alert("Hello World!");' > public/alertassets.js
$ mkdir templates
$ echo '%= javascript "/alertassets.js"' > templates/alertassets.html.ep

只需在 register 调用的时候要追加其各自的目录到搜索路径的列表中.

package Mojolicious::Plugin::AlertAssets;
use Mojo::Base 'Mojolicious::Plugin';

use File::Basename 'dirname';
use File::Spec::Functions 'catdir';

sub register {
  my ($c, $app) = @_;

  # Append "templates" and "public" directories
  my $base = catdir(dirname(__FILE__), 'AlertAssets');
  push @{$app->renderer->paths}, catdir($base, 'templates');
  push @{$app->static->paths},   catdir($base, 'public');
}

1;

这个就象存在标准的 templatespublic 的目录一样, 只要你安装了插件并加载了.

use Mojolicious::Lite;

plugin 'AlertAssets';

get '/alert_me';

app->start;
__DATA__

@@ alert_me.html.ep
<!DOCTYPE html>
<html>
  <head>
    <title>Alert me!</title>
    %= include 'alertassets'
  </head>
  <body>You've been alerted.</body>
</html>

当然也可以使用 DATA 块部分的数据.

package Mojolicious::Plugin::AlertAssets;
use Mojo::Base 'Mojolicious::Plugin';

sub register {
  my ($c, $app) = @_;

  # Append class
  push @{$app->renderer->classes}, __PACKAGE__;
  push @{$app->static->classes},   __PACKAGE__;
}

1;
__DATA__

@@ alertassets.js
alert("Hello World!");

@@ alertassets.html.ep
%= javascript "/alertassets.js"

高级

较不常用的但更强大的功能.

渲染静态文件

如果自动渲染不足够你使用, 你可能还想手工的渲染你的 DATA 部分和 public 其它的东西, 你可以使用 "render_static" in Mojolicious::Controller.

$c->res->headers->content_disposition('attachment; filename=bar.png;');
$c->render_static('foo/bar.png');

定制的响应

你想完全定制你自己的响应, 象这个例子中, 从文件读取流式内容, 然后使用 "rendered" in Mojolicious::Controller 告诉 Mojo 使用自己定制的来生成.

$c->res->headers->content_type('text/plain');
$c->res->content->asset(Mojo::Asset::File->new(path => '/etc/passwd'));
$c->rendered(200);

后处理动态的内容

当响应生成的时候, 我们可以使用 "after_render" in Mojolicious hook 点来做渲染之后的一些操作.

use Mojolicious::Lite;
use IO::Compress::Gzip 'gzip';

hook after_render => sub {
  my ($c, $output, $format) = @_;

  # Check if "gzip => 1" has been set in the stash
  return unless $c->stash->{gzip};

  # Check if user agent accepts GZip compression
  return unless ($c->req->headers->accept_encoding // '') =~ /gzip/i;
  $c->res->headers->append(Vary => 'Accept-Encoding');

  # Compress content with GZip
  $c->res->headers->content_encoding('gzip');
  gzip $output, \my $compressed;
  $$output = $compressed;
};

get '/' => {template => 'hello', title => 'Hello', gzip => 1};

app->start;
__DATA__

@@ hello.html.ep
<!DOCTYPE html>
<html>
  <head><title><%= title %></title></head>
  <body>Compressed content.</body>
</html>

Chunked transfer encoding

对于动态的内容, 你并不能提前知道响应的 Content-Length 的大小, 这时 chunked Transfer-Encoding 就非常有用. 一个常见的用处是给 HTML 中发 head 部分到用户的浏览器来提前加快预载图象和样式表.

$c->write_chunk('<html><head><title>Example</title></head>' => sub {
  my $c = shift;
  $c->finish('<body>Example</body></html>');
});

上面这个能保证 drain 的回调会给先前的数据写入完后, 才会接着继续处理, 一个空的 chunk 或者调用 "finish" in Mojolicious::Controller 标志着这个流结束了.

29
<html><head><title>Example</title></head>
1b
<body>Example</body></html>
0

特别是结合长时间闲置超时的应用象 Comet (long polling) 时非常的有用. 不过由于一些 Web 服务器的限制, 并不能包证这个能在所有的地方都能正常工作.

Encoding

默认的模板是存成 UTF-8 的格式, 你也可以很容易的修改.

# Application
package MyApp;
use Mojo::Base 'Mojolicious';

sub startup {
  my $c = shift;

  # Different encoding
  $c->renderer->encoding('koi8-r');
}

1;

如果从 DATA 块中绑定模板到 Perl 的程序中时, 不要忘记使用 utf8 的编译指示.

use Mojolicious::Lite;
use utf8;

get '/heart';

app->start;
__DATA__

@@ heart.html.ep
I ♥ Mojolicious!

用 Base64 编码 DATA 内容

在你的应用程序中, 你可以很容易的静态文件, 如图片, 存储在 DATA 的部分, 类似于模板一样.

@@ favicon.ico (base64)
...base64 encoded image...

导出 DATA 模板

模板存储后, 会从 DATA 部分优先取得, 所以可以让你在你的应用程序中包含默认的模板. 当然你也可以以后进行自定义. 使用 inflate 的命令会给所有 DATA 部分的静态文件都写到 templatespublic 的目录中.

$ ./myapp.pl inflate

定制模板语法

你可以很容易的通过 Mojolicious::Plugin::EPRenderer 来定义你自己的模板语法的配置.

use Mojolicious::Lite;

plugin EPRenderer => {
  name     => 'mustache',
  template => {
    tag_start => '{{',
    tag_end   => '}}'
  }
};

get '/:name' => {name => 'Anonymous'} => 'index';

app->start;
__DATA__

@@ index.html.mustache
Hello {{= $name }}.

Mojo::Template 中含可用选项的完整的列表

增加你喜欢的模板系统

也许你喜欢其它的模板系统, 相比起 ep. 你可以使用 "add_handler" in Mojolicious::Renderer 来通过 register 给新的模板系统加入到你的应用中来.

package Mojolicious::Plugin::MyRenderer;
use Mojo::Base 'Mojolicious::Plugin';

sub register {
  my ($c, $app) = @_;

  # Add "mine" handler
  $app->renderer->add_handler(mine => sub {
    my ($renderer, $c, $output, $options) = @_;

    # Check for one-time use inline template
    my $inline = $options->{inline};

    # Check for absolute template path
    my $path = $renderer->template_path($options);

    # Check for appropriate template in DATA section
    my $data = $renderer->get_data_template($options);

    # This part is up to you and your template system :)
    ...

    # Just die if an error occurs
    die 'Something went wrong with the template';

    # Or pass the rendered result back to the renderer
    $$output = 'Hello World!';

    # And return true if something has been rendered or false otherwise
    return 1;
  });
}

1;

大多的模板系统都不支持从 DATA 部分来取得模板, renderer 的方法可以帮你实现.

use Mojolicious::Lite;

plugin 'MyRenderer';

get '/' => 'index';

app->start;
__DATA__

@@ index.html.mine
...

直接生成二进制未编码的数据

默认的渲染假定所有的处理的都是字符, 会自动的被 Mojo 编码一次, 但你可以很简单的禁用这个功能来直接生成 bytes 流.

use Mojolicious::Lite;
use Mango::BSON ':bson';

# Add "bson" handler
app->renderer->add_handler(bson => sub {
  my ($renderer, $c, $output, $options) = @_;

  # Disable automatic encoding
  delete $options->{encoding};

  # Encode BSON data from stash value
  $$output = bson_encode delete $c->stash->{bson};

  return 1;
});

get '/' => {bson => {i => '♥ mojolicious'}, handler => 'bson'};

app->start;

MORE

You can continue with Mojolicious::Guides now or take a look at the Mojolicious wiki, which contains a lot more documentation and examples by many different authors.