Dancer 是 Perl 的 web 开发框架,在 metacpan 上有 100 多个 like。其语法结构都起源自 Ruby 的 sinatra 框架,sinatra 曾经在自己官网上悬挂“perldancer is good”标语以示对 perldancer 的支持。Dancer 官网见: http://perldancer.org/ 本文系本人在部门 Wiki 上稍微写的几行介绍性质的笔记。
Dancer 作为微框架,可以直接单文件快速运行简单的 web 功能。示例如下:
use Dancer;
get '/' => sub {
return "hello world";
};
dance;
然后直接通过 perl test.pl
命令既可以在 localhost:3000 运行起来一个 hello world 页面了。
完整的 Dancer 应用,可以通过 dancer -a MyApp
命令创建,目录结构如下:
MyApp/
├── bin
│ └── app.pl # 程序运行入口,可以直接通过./app.pl运行,也可以通过plackup -s Starman app.pl来切换其他高性能服务器
├── config.yml # 主配置文件
├── environments
│ ├── development.yml
│ └── production.yml
├── lib
│ └── MyApp.pm # Perl代码入口,route、controller、ORM 等都在 lib 下
├── Makefile.PL
├── MANIFEST
├── MANIFEST.SKIP
├── public # public/ 下的文件会直接作为静态文件发布,相当于 DocumentRoot
│ ├── 404.html
│ ├── 500.html
│ ├── css
│ │ ├── error.css
│ │ └── style.css
│ ├── dispatch.cgi
│ ├── dispatch.fcgi
│ ├── favicon.ico
│ ├── images
│ │ ├── perldancer-bg.jpg
│ │ └── perldancer.jpg
│ └── javascripts
│ └── jquery.js
├── t
│ ├── 001_base.t
│ └── 002_index_route.t
└── views # views/ 下的文件是页面模板,在 lib 里通过 template('index') 方式调用
├── index.tt
└── layouts
└── main.tt # layouts 是页面模板的底层模板,主底层模板可以在 config.yml 里指定
目前用 Dancer 写的 CdnManage 平台,用到的插件包括:
采用 Text::Xslate 作为模板引擎。xslate 引擎是用 XS 写的类 Perl6 语法模板引擎,性能很好。语法示例如下:
<: $object.accessor :>
<: $str :>
<: $array.0 :>
<: $hash.key :>
: for $arrayref -> $item {
index: <: $~item :> value: <: $item :>
: }
: if ( $var == nil ) {
: } else if ( $val == "text" ) {
: } else {
: while $dbh.fetch() -> $item {
: }
: }
注意,CdnManage 中,因为是从 TT2 模板迁移到 xslate 里的,所以单独配置了 config.yml,没有用 : 号而是沿用了 % 号。
采用 YAML 存储 session,这个作为内部应用足够了,升级的话应该用 mysql、mongo、elasticsearch之类的存储,都有现成插件。
上面两个作为给 public/ 下文件加缓存和压缩的优化。在 config.yml 里添加如下配置即可使用:
plack_middlewares:
-
- Plack::Middleware::Deflater
- Plack::Middleware::ETag
给 route 加认证功能,有 require_role 和 require_user 两种形式,示例如下:
get '/admin' => require_user 'admin' => sub {};
post '/purge' => require_role qr/^purge_\w+/ => sub {};
发邮件
将需要较长时间运行完的任务通过 gearman 分发到其他后台任务脚本上去完成。
数据库插件,可以直接按照 DBI 操作,也提供了简单的 quick_select/insert 等指令。示例如下:
get '/users/:id' => sub {
template 'display_user', {
person => database->quick_select('users', { id => params->{id} }),
};
};
如果在 config.yml 定义了多个库,则通过 database('name')
的方式来调用。
Database:
connections:
puppet:
driver: "SQLite"
database: "/etc/puppet/webui/node_info.db"
cdnmanage:
driver: "mysql"
database: "cdnmanage"
host: "127.0.0.1"
port: 3306
username: "user"
password: "pass"
connection_check_threshold: 10
on_connect_do: ["SET NAMES 'utf8'", "SET CHARACTER SET 'utf8'" ]
更完善的 ORM 使用,见 Dancer::Plugin::DBIC 插件,他使用的是 DBIx::Class 框架做 ORM,示例如下:
get '/users/:user_id' => sub {
my $user = schema('default')->resultset('User')->find(param 'user_id');
# 如果只有一个默认的schema在config.yml里那么上面这行可以简写成下行
$user = rset('User')->find(param 'user_id');
template user_profile => {
user => $user
};
};
elasticsearch 插件,类似 Dancer::Plugin::Database;所以同理,也有更偏 ORM 一点的 Dancer::Plugin::ElasticModel 插件。
页面消息提示插件。使用示例:
hook before => sub {
if ( request->uri =~ m#^/puppetdb/#
and request->uri !~ m#^/puppetdb/api/#
and !user_has_role('SOM') )
{
deferred error => 'no permission';
redirect '/';
}
};
然后在底层模板layouts/main.tt 中:
%% if $deferred.error {
<div class="alert alert-success"> [% $deferred.error %] </div>
%% }
扩展默认的 get/post/delete/put 指令,提供 ajax 指令。
提供简便的数据库 CRUD 操作表单。目前 Puppet 的 SQLite 操作实例如下:
simple_crud(
db_connection_name => 'puppet',
db_table => 'node_info',
key_column => 'id',
prefix => 'node_info',
record_title => 'Puppet Node',
deleteable => 1,
paginate => 50,
validation => {
classes => '/^(\w,?)+$/',
role => '/^\w+$/',
environment => '/^\w+$/',
},
message => {
classes => 'enter like "puppetd,repos"',
role => 'an english word only',
},
display_columns => [qw(node_fqdn environment role)],
custom_columns => {
include_classes => {
raw_column => 'classes',
transform => sub {
my @classes = split( /,/, shift );
my $self = shift;
my $role = $self->{'role'};
my $env = $self->{'environment'};
my @lines;
push @lines, "<a href='/puppetdb/$env/$_/$role/view'>$_</a>"
for @classes;
return join( " / ", @lines );
},
},
},
);