Mojolicious-Rendering-文档

所有的模板应该被放在应用目录下的 templates 目录中.

Embedded Perl

Mojolicious 中包含的一个极简的模板格式, 称为 Embedded Perl 或 EP. 其基于 Mojo::Template, 允许用一些特殊的标签和 line start characters 将 Perl 代码嵌入到框架中.

所有的 templates 会自动启用 strict, warnings, utf8Perl 5.16.

两种格式:

  • tags, 如:

  • lines, 如:

tags 和 lines 工作起来类似, 但根据内容使用更好.

示例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<% 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>

区分下面格式:

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

后者禁用了对 <, >, &, '" 的转义, 可以防范 XSS 攻击. (实际渲染出来似乎没有区别)

在一个 tag 的最后加上等号 = 可以去除表达式周围的空白字符:

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

Basic

Automatic rendering

renderer 可以用 Mojolicious::Controllerrender 方法来调用, 如:

1
$c->render;

但当这里没有任何 rendered 发生时, 其会被自动调用. 此时需将 routes 指向 templates.

可以用:

1
$c->render_later;

来禁用 automatic rendering.

Rendering templates

指定 templates 为 foo/bar/baz.*.*:

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

其等价于:

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

(因为指定 template 很频繁)

指定 templates 的 formathandler:

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

如果不清楚 template 是否存在, 可以用 Mojolicious::Controller 对象的 render_maybe 方法, 如:

1
$c->render_maybe('localized/baz');

Stash data

可以通过 Mojolicious::Controller 中的 stash 来将普通的 Perl 数据结构传递给模板中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use Data::Dumper;

get '/' => sub ($c) {
$c->stash(description => 'web framework');
$c->stash(frameworks => ['Catalyst', 'Mojolicious', 'mojo.js']);
$c->stash(spinoffs => {minion => 'job queue'});
$c->render(template => 'index');
};

app->start;

__DATA__

@@ index.html.ep
% layout 'default';
% title 'Welcome';
<h1>Welcome to the Mojolicious real-time web framework!</h1>
%= $description
%= $frameworks->[1]
%= $spinoffs->{minion}

Helpers

Helpers 是可以用在 templates, application and controller code 中的小函数.

如应用默认的 helper 函数 dumper:

1
2
3
4
5
6
7
8
# Template
%= dumper [1, 2, 3]

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

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

Mojolicious::Plugin::DefaultHelpers 中定义了一些 general purpose helper 函数.

Mojolicious::Plugin::TagHelpers 中定义了一些 tag helpers, 其主要用于 template 以及生成 HTML tags, 如:

1
%= link_to Mojolicious => "https://mojolicious.org"

可以用 Mojolicious::Controller 对象的 helpers 方法来调用 helper 函数:

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

静态文件 Static files

静态文件一般位于应用的 public 目录下, 也可以用 Mojolicious::Staticpaths 方法来指定.

静态文件也可以位于 __DATA__ 中, 用 Mojolicious::Static 中的 classes 方法指定.

可以通过 Mojolicious::Plugin::DefaultHelpers 中的 reply->static 方法或者 reply->file 方法来指定, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 use Mojolicious::Lite -signatures;

get '/' => sub ($c) {
$c->reply->static('index.html');
};

get '/some_download' => sub ($c) {
$c->res->headers->content_disposition('attachment; filename=bar.png;');
$c->reply->static('foo/bar.png');
};

get '/leak' => sub ($c) {
$c->reply->file('/etc/passwd');
};

app->start;
  • reply->static 通过相对路径来指定, 文件位于 public 目录下, 或者 __DATA__
  • reply->file 通过绝对路径指定

Static assets

什么是 asset files?

Asset file(资源文件)是指在计算机程序中使用的一种文件格式,通常是包含了程序所需的各种资源,如图像、音频、视频、文本等。这些文件通常被打包在一起,以便程序可以轻松地访问和使用这些资源。

在移动应用程序开发中,asset file通常是指嵌入在应用程序包中的资源文件,这些文件可以在应用程序运行时动态加载和使用。例如,一个游戏应用程序可能包含多个图像、音效和视频文件,这些文件可以作为asset file打包在应用程序中,并在游戏运行时动态加载和使用。

在Web开发中,asset file同样是指嵌入在Web应用程序中的资源文件,如CSS样式表、JavaScript文件、图像、字体等。这些文件可以通过HTTP协议动态加载和使用,以提高Web应用程序的性能和用户体验。


asset files 可以为任意类型的文件, 其命名格式为 [name].[checksum].[extensions]. (“checksum” 的意思是校验和)

可以用:

  • Mojolicious::Controller 中的 url_for_asset
  • Mojolicious::Plugin::TagHelpers 中的 asset_tag

来生成 URLs, 且不需要知道 checksum:

1
2
3
4
5
# "/assets/myapp.ab1234cd5678ef.js"
$c->url_for_asset('/myapp.js');

# "<script src="/assets/myapp.ab1234cd5678ef.js"></script>"
$c->asset_tag('/myapp.js');

内容协商 Content Negotiation

什么是 Content Negotiation?

Content negotiation(内容协商)是指在客户端和服务器之间进行的一种交互式协商过程,用于确定要在HTTP请求和响应中使用的最佳内容格式。这个过程允许客户端和服务器在多个可接受的内容格式之间进行选择,并选择最适合请求的格式。

在HTTP协议中,内容协商通常在请求头部字段Accept和响应头部字段Content-Type之间进行交互。客户端将请求头部字段Accept设置为它所支持的内容类型及其优先级顺序,而服务器则在响应头部字段Content-Type中指定所提供的内容类型。在服务器收到请求后,它会检查请求头部字段Accept中指定的内容类型,并选择最佳的内容类型作为响应头部字段Content-Type中的值,然后将响应发送回客户端。


具体见文档.

渲染 exceptionnot_found page

Mojolicious 有内置的 404 (Not Found) 和 500 (Server Error) 页面. 可以用 Mojolicious::Plugin::DefaultHelpers 模块中的:

  • reply->exception
  • reply->not_found

来手动渲染这两个 pages, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use Mojolicious::Lite -signatures;
use Scalar::Util qw(looks_like_number);

get '/divide/:dividend/by/:divisor' => sub ($c) {

my $dividend = $c->param('dividend');
my $divisor = $c->param('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;

可以改变触发这两个页面的错误码, 如:

1
return $c->reply->exception('Division by zero!')->rendered(400) if $divisor == 0;

这里将触发 exception 页面改为 400 错误码.

自定义 这些 pages 的 template, renderer 在启用默认的模板之前会自动查找 exception.$mode.$format.*not_found.$mode.$format.* 文件, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Mojolicious::Lite;

get '/dies' => sub { die 'Intentional error' };

app->start;
__DATA__

@@ 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>

Mojolicious::Plugin::DefaultHelpers 中的 exception_format 来修改返回的 exception 文件的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Mojolicious::Lite -signatures;

app->exception_format('json');

get '/json' => sub ($c) {
die 'Just a test';
};

get '/txt' => sub ($c) {
$c->exception_format('txt');
die 'Just a test';
};

app->start;

可以用 hookbefore_render 运行在 render 之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Mojolicious::Lite -signatures;

hook before_render => sub ($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
return unless $c->accepts('json');
$args->{json} = {exception => $c->stash('exception')};
};

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

app->start;

hook 是一种事件钩子机制,允许应用程序在指定的事件发生时执行一些额外的代码。事件钩子是一种非常灵活的方式,可以让开发人员在不修改核心代码的情况下扩展应用程序的功能。

在这个例子中,hook before_render 表示在渲染模板之前,会执行一个回调函数,该回调函数接收 $c 对象和 $args 参数作为输入。在这个回调函数中,我们检查了要渲染的模板是否为异常模板,如果是,则检查客户端是否接受 JSON 格式的响应,如果是,则将异常信息转换为 JSON 格式,以便客户端可以更好地处理它。

布局 Layouts

在 Mojolicious 中,Layouts 是一种模板继承机制,允许开发人员将通用的页面结构和样式与特定页面的内容分离开来。使用 Layouts 可以减少代码的重复,提高代码的可维护性.

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use Mojolicious::Lite;

get '/' => {template => 'foo/bar'};

app->start;

__DATA__

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

@@ layouts/mylayout.html.ep
<!DOCTYPE html>
<html>
<head><title>MyApp</title></head>
<body><%= content %></body>
</html>是什么含义

这个例子中,定义了一个名为 foo/bar.html.ep 的模板,并使用 helper 函数 layout 指定了该模板的布局模板为 mylayout。这意味着 foo/bar.html.ep 模板将继承 layouts/mylayout.html.ep 模板的结构和样式,并将其内容插入到 helper 函数 content 中。

layouts/mylayout.html.ep 模板定义了一个 HTML 页面的基本结构,其中包括标题和主体部分。在主体部分中,我们使用 <%= content %> 占位符来插入子模板的内容。子模板的内容将替换 content 占位符,从而生成最终的 HTML 页面。

在这个例子中,最终生成的 HTML 页面将包括 layouts/mylayout.html.ep 模板中定义的结构和样式,以及 foo/bar.html.ep 模板中特定页面的内容.

也可以用 layout 函数来传递 stash 变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
use Mojolicious::Lite;

get '/' => {template => 'foo/bar'};

app->start;
__DATA__

@@ 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>

下列代码中:

1
2
@@ foo/bar.html.ep
% layout 'mylayout';

% layout 'mylayout'; 可以用 render 中的 layout 参数来代替:

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

全局设置可以用:

1
$app->defaults(layout => 'mylayout');

局部模板 Partial templates

可以将一个大模板拆分为多个小模板后, 在一个小模板中包含另一个模板来进行组合. 用 Mojolicious::Plugin::DefaultHelpers 中的 include 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Mojolicious::Lite;

get '/' => {template => 'foo/bar'};

app->start;
__DATA__

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

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

可复用的模板块

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use Mojolicious::Lite;

get '/' => 'welcome';

app->start;
__DATA__

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

添加 helpers

helper 关键字创建, 将 controller 对象作为第一个参数, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Mojolicious::Lite -signatures;

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

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

app->start;
__DATA__

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

注意, 在 Mojolicious 中,模板文件中可以直接调用控制器(controller)中定义的 helper 方法,而不需要显式传递控制器对象。这是因为模板文件的上下文对象(context object)会自动包含控制器对象的 helper 方法。

这个例子中,在 index.html.ep 模板文件中调用了 debug helper 方法,而不需要将控制器对象传递给该方法.

可以给 helper 函数前添加 prefix 来作为命名空间, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use Mojolicious::Lite -signatures;

helper 'cache_control.no_caching' => sub ($c) { $c->res->headers->cache_control('private, max-age=0, no-cache') };
helper 'cache_control.five_minutes' => sub ($c) { $c->res->headers->cache_control('public, max-age=300') };

get '/news' => sub ($c) {
$c->cache_control->no_caching;
$c->render(text => 'Always up to date.');
};

get '/some_older_story' => sub ($c) {
$c->cache_control->five_minutes;
$c->render(text => 'This one can be cached for a bit.');
};

app->start;

Content blocks

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use Mojolicious::Lite;

get '/' => 'foo';

app->start;
__DATA__

@@ foo.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 'header' %></head>
<body><%= content %></body>
</html>

表单 Forms

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use Mojolicious::Lite -signatures;

get '/' => 'form';

# PUT /nothing
# POST /nothing?_method=PUT
put '/nothing' => sub ($c) {

# Prevent double form submission with redirect
my $value = $c->param('whatever');
$c->flash(confirmation => "We did nothing with your value ($value).");
$c->redirect_to('form');
};

app->start;
__DATA__

@@ form.html.ep
<!DOCTYPE html>
<html>
<body>
% if (my $confirmation = flash 'confirmation') {
<p><%= $confirmation %></p>
% }
%= form_for nothing => begin
%= text_field whatever => 'I ♥ Mojolicious!'
%= submit_button
% end
</body>
</html>

Mojolicious-Rendering-文档
http://example.com/2023/05/17/Mojolicious-Rendering-文档/
作者
Jie
发布于
2023年5月17日
许可协议