取势 明道 优术

作者为 扶 凯 发表

 


名称

Dancer::Tutorial – 这是一个简单的例子告诉你怎么使用 Dancer

 


什么是 Dancer

Dancer 是一个迷你的 Web 的框架,在 Ruby 一个叫 Sinatra 的架构思想影响下开发的框架,主要是建立一个基于 HTTP 的动作列表的处理框架,URLs (叫 routes ) 和方法来处理相应的事情.

  use Dancer;

  get '/' => sub {
        return 'Hello World!';
  };

  start;

这个例子显示了怎么样处理一个 HTTP 的动作 "GET" 目标的网址 "/" 来和一个匿名的子程序关联并返回一串字符 "Hello World!" .如果你运行这个例子,他能在你的浏览器中显示 "Hello World!" 在你的 http://localhost:3000 的地址中.

 


需要的 Perl 模块

很明显,必须使用 Dancer ,你也必须使用 Template Toolkit, the File::Slurp manpagethe DBD::SQLite manpage. 这些安装,你可以使用 cpanm 来安装

  cpanm Dancer Template File::Slurp DBD::SQLite

 


数据库

我们不打算在数据库上花大量的时间,因为这不是这个教程的主要要点,这是我们建表的语句子,放到一个叫 schema.sql 的文件中;

  create table if not exists entries (
    id integer primary key autoincrement,
    title string not null,
    text string not null
  );

我们这只有一个单一的表,有 id, title, 和 text.这三个字段.这个 id 是主健.当每次插入行时自增的 ID .

我想我们的应用程序开始的时候自动的初始化数据库.所以打开人们的文本编辑器,建一个叫 dance.pl 的文件. 我们来写下面子函数的到这个文件中.

  sub connect_db {
    my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database')) or
       die $DBI::errstr;

    return $dbh;
  }

  sub init_db {
    my $db = connect_db();
    my $schema = read_file('./schema.sql');
    $db->do($schema) or die $db->errstr;
  }

这没有任何花哨的东西,只是使用使用了标准的 DBI 来设置 C<setting('database')>. 当这个不存在时,就会读 schema.sql 来建一个表.

有可能你想深入了解,你可以看看 the Dancer::Plugin::Database manpage 的模块.可以非常容易的在 Dancer 中配置和管理数据库的连接.但在本教程中不在提到.

 


路由选择

我们的第一个路由的处理;

我们现在先来分析我们的第一个 route 的处理,这是我们网站的根(/)的处理.看下面

  get '/' => sub {
    my $db = connect_db();
    my $sql = 'select id, title, text from entries order by id desc';
    my $sth = $db->prepare($sql) or die $db->errstr;
    $sth->execute or die $sth->errstr;
    template 'show_entries.tt', { 
       'msg'           => get_flash(),
       'add_entry_url' => uri_for('/add'),
       'entries'       => $sth->fetchall_hashref('id'),
    };
  };

正如你所见到的,这个处理会建立一个对 HTTP 的 'get' 动作的处理,这个处理需要匹配到 URL 是 '/' 才行. 注意这个子函数的最后面的分号,因为这是实际上是一个 coderef 所以一定需要分号来结束.

让我们在细看这个程序,第一行是一个标准的 DBI ,后面的处理在 Dancer 中直接交给了 template 来直接处理网页的 输出显示部分.这告诉 Dancer 程序的输出直接通过模板引擎就好.以我们这个例子中我们是使用 的 Template Toolkit 来做输出因为他非常的容易使用使用,也是 Dancer 默认支持的模板引擎之一.

template 第一个参数,是使用哪个模板输出网页内容,全部都在 views/ 的目录中来处理,会找这个目录 下的 show_entries.tt 文件来显示,别外一个可选的你能在 "layout" 中来提供一致的外观比如加载 css 之类. 我们这个教程中,在 layout 中建一个叫 main.tt 的模板文件来提供.

template 的第二个参数是一个 hash 的引用.主要提供给第一个参数给模板使用.全部的参数都会传给模板. 我们有那些 msg 字段需要在每次用户 post 新的条目是显示一次, 当用户登陆时调用 'flash' 的信息,这个登陆信息只要显示一次.并不是每次调用 / 时都会触发这个事件.

这个 url_for 的指令告诉 Dancer 来跳转到指定的路由处理中.在这个例子中这的路由是 post 一些新 的内容到数据库.你也许讲为什么我不能简单一点加入 /add 这个来硬编码到应用模板中来显示. 这最好的解决方法是不要放到那个中,因为使用 "mount" 的功能在你的应用中修改会更加容易. 例如我们目前的 root 的 URL 是 /,我们也许将来想更新这个 URL 的路由到 /dancr 这个目录. 这样就不用下次更新这个 url 的时候还需要到模板的目录中修改,还不要忘记任何一个地方都修改过来. 在 Dancer 中使用 uri_for 的方法,我们可以很容易的在应用程序中修改这个.

最后面,这个 entries 条目中是包含的一个 database 查询的结果的引用.这个结果将在模块内容来显示 解析出来.所以这个地方我们直接传给模板就行了.

下面我们看看 show_entries.tt 的模板中的内容.如下

  <% IF session.logged_in %>
    <form action="<% add_entry_url %>" method=post class=add-entry>
      <dl>
        <dt>Title:
        <dd><input type=text size=30 name=title>
        <dt>Text:
        <dd><textarea name=text rows=5 cols=40></textarea>
        <dd><input type=submit value=Share>
      </dl>
    </form>
  <% END %>
  <ul class=entries>
  <% IF entries.size %>
    <% FOREACH id IN entries.keys.nsort %>
      <li><h2><% entries.$id.title %></h2><% entries.$id.text %>
    <% END %>
  <% ELSE %>
    <li><em>Unbelievable.  No entries here so far</em>
  <% END %>
  </ul>

在次声明,我们这个教程并不是教 Template Toolkit,所以只简单的介绍,可以看看其它的文档 来详细了解 Template Toolkit .这个部分的语法从 <ul class=entries> 开始起, 这个部分讲的都是数据库中进行查询和显示. 你可以看看上面有关 session 的议论.

 


其它的 HTTP 的动作

这有 8 个 RFC2616 的协议 默认定义好的 HTTP 动作 OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT.在这个中, 其中大部分的 Web 的应用的重点都是给这些动作映射成 CRUD (Create, Retrieve, Update, Delete)的对数据库的操作.

Dancer 当前支持 GET, PUT, POST, DELETE, OPTIONS 这几个动作映射到各自的 Retrieve, Update, Create, Delete 的操作.让我们看下面的 /add 的路由的处理操作一个 POST 的操作:

  post '/add' => sub {
     if ( not session('logged_in') ) {
        send_error("Not logged in", 401);
     }

     my $db = connect_db();
     my $sql = 'insert into entries (title, text) values (?, ?)';
     my $sth = $db->prepare($sql) or die $db->errstr;
     $sth->execute(params->{'title'}, params->{'text'}) or die $sth->errstr;

     set_flash('New entry posted!');
     redirect '/';
  };

这个 HTTP 的动作开始处理,会转到路由处理,通过使用 add 后面这个子函数来实现一些功能.在这个例子中,会插入一些新的条目到数据库.

停止处理.如果登陆了,就连接标准的 DBI .在这使用 INSERT 的 SQL 的声明中使用参数来插入. 这你需要保证你的这个插入是可靠的,因为这很容易被 SQL 注入.(http://www.bobby-tables.com 有多个语言版本的 INSERT 的例子.) 我们使用 params 从当前的 HTTP 的请求中取得合适的参数.(你可以看到从 show_entries.tt 中取到的 'title' 和 'text' 的参数) 这些值插入到数据库中.最后为用户设置 flash 的信息并重定向你到 root URL.

这个部分,这没提到这个 "flash message" ,但应用中其它的部分指定了这个.

 


登陆的会话管理

Dancer 自己本身有一个简单的内存的 session 的管理.他也支持其它的会话引擎.象 YAML, memcached,browser cookies 和其它. 在这个例子中,只是存在内存中,所以当你自己做应用时最好存在一个可以持久化的东西中,这样当你的服务重起时也能使用.

 

配置选项

在我们的应用中要使用 session 的管理,需要告诉 Dancer 启动 session 的处理管理器.在这个地方,我们只要配置如 下的指令在我们的 dancer.pl 的文件中就行.下面我们还设置了一些其它有用的一些东西.

  set 'session'      => 'Simple';
  set 'template'     => 'template_toolkit';
  set 'logger'       => 'console';
  set 'log'          => 'debug';
  set 'show_errors'  => 1;
  set 'startup_info' => 1;
  set 'warnings'     => 1;

我想上面这些都是不言自明的,我们想启用简单的 session 引擎管理, Template Toolkit 的模板引擎.logging 的激活.还有给 debug 的级别的信息设置成 console 来输出.当我们想显示出错的显示在浏览器中时,可以这样设置.

如果你是有经验的开发者,我们想这信息你都会设置在 YAML 的配置文件中,在这个教程中,为了简单,直接写在程序文件中.我们这还使用 set 的语法来设置二个程序需要的变量.用户和密码.

  set 'username' => 'admin';
  set 'password' => 'password';

我希望没人猜到我们这么复杂的密码 ^-^.

 

登陆

在 Dancer 中来处理 session 的注册.当用户打开 /login 的 route 时会设置 session.

  any ['get', 'post'] => '/login' => sub {
     my $err;

     if ( request->method() eq "POST" ) {
       # process form input
       if ( params->{'username'} ne setting('username') ) {
         $err = "Invalid username";
       }
       elsif ( params->{'password'} ne setting('password') ) {
         $err = "Invalid password";
       }
       else {
         session 'logged_in' => true;
         set_flash('You are logged in.');
         return redirect '/';
       }
    }

    # display login form
    template 'login.tt', { 
      'err' => $err,
    };
  };

这个处理中,我们同时支持二种不同的动作属性,就是支持当用户 GET 取得数据和 POST 在个应用程序中提交用户输入到应用中,这个会做相应的不同的处理. 我们会检查当使用 POST 时,会显示用户的信息,正确的话并显示 'You are logged in.',不然会调用 F<login.tt> 的模板来显示出错的信息:

  <h2>Login</h2>
  <% IF err %><p class=error><strong>Error:</strong> <% err %><% END %>
  <form action="<% login_url %>" method=post>
    <dl>
      <dt>Username:
      <dd><input type=text name=username>
      <dt>Password:
      <dd><input type=password name=password>
      <dd><input type=submit value=Login>
    </dl>
  </form>

这和 show_entries.tt 是同样相同简单的模板.但是等等, 这 login_url 的参数和我们的出错的参数会在这显示.这可能会没有参数?怎么办,我们可以在 before_template 设置要发送给模板的默认参数.

当用户写完 login.tt 模板然后提交到 /login 的路由选项的函数中处理,现在会在次检查用户的输入,如果出错警告用户,接下来应用会设置 logged_in 中的 session 参数为 true() 的值. Dancer 会导出 true()false() 给合适的方法使用.之后设置 flash 的信息并登陆成功后退到root URL的处理中.

 

登出

用户登陆成功后,当用户要退出时做如下的处理:

  get '/logout' => sub {
     session->destroy;
     set_flash('You are logged out.');
     redirect '/';
  };

session->destroy; 是 Dancer 用来删除存着的 session 的方式.我们要注意到 user 在导出然后在次返回到根.

 


布局和静态文件

在这,还有些难题,首先,我们怎么使用 Dancer 来显示 CSS 风格,第二,我那个 flash 的信息怎么显示,第三,什么是 before_template.

 

显示静态文件

在 Dancer 中静态文件需要存到 public/ 的目录中,但是应用中显示的话,需要确保使用 public/ 中存在这些元素. 在这个例子中,css 的风格文件就是存在 dancr/public/css/style.css 中的,所以显示就是使用的地址 http://localhost:3000/css/style.css.

如果你想建一个静态的网站,就很容易,只要简单的如下面这个路由的处理就行.直接 send 文件:

  get '/' => sub {
     send_file 'index.html';
  };

这个地方的 index.html 需要在你的 public/ 的目录中.

这个 send_file 在这的功能是,加载静态文件并给这个内容发给用户.

 

布局

在这个教程中,只需要建一个布局的模板就行,在 Dancer 中,会自动在每个路由中调用这个做为网页的布局, 并给路由的输出当内容插入到 layout 中

  set layout => 'main';

在你的 web 应用程序的上面.告诉了 Dancer 的模板引擎,它应该寻找为在 dancr/views/layouts/ 中的 main.tt 来显示内容,插入时直接调用模板技术以参数的 方式调用 content. 在这个 web 的程序中,布局模板内容如下:

  <!doctype html>
  <html>
  <head>
    <title>Dancr</title>
    <link rel=stylesheet type=text/css href="<% css_url %>">
  </head>
  <body>
    <div class=page>
    <h1>Dancr</h1>
       <div class=metanav>
       <% IF not session.logged_in %>
         <a href="<% login_url %>">log in</a>
       <% ELSE %>
         <a href="<% logout_url %>">log out</a>
       <% END %>
    </div>
    <% IF msg %>
      <div class=flash> <% msg %> </div>
    <% END %>
    <% content %>
  </div>
  </body>
  </html>

你可以见到 flash 的信息 msg的参数在这显示,这也显示了其它的路由的处理是怎么以 content 参数插入到这个中的.

 

使用 before_template

Dancer 有能力在模板输出之前,修改传送给模板的参数.这个功能就是 before_template 使用这个指令. 你能全局设置 URIs 从 /login/logout 的路由来处理还有你的网站风格的路径.象一些需要重复使用和设置的内容,可以直接放到这个中进行整体的设置, 不用放到每一个路由都复杂的处理.

  before_template sub {
     my $tokens = shift;
        
     $tokens->{'css_url'} = request->base . 'css/style.css';
     $tokens->{'login_url'} = uri_for('/login');
     $tokens->{'logout_url'} = uri_for('/logout');
  };

这在次使用 uri_for 转向到路由,上面这个代码块是在任何模板被解析之前执行的,所以模板的参数,可以在这个地方就先设置.

 


全部的代码

完整的 dancer.pl 的脚本.

 use Dancer;
 use DBI;
 use File::Spec;
 use File::Slurp;
 use Template;
 
 set 'database'     => File::Spec->catfile(File::Spec->tmpdir(), 'dancr.db');
 set 'session'      => 'Simple';
 set 'template'     => 'template_toolkit';
 set 'logger'       => 'console';
 set 'log'          => 'debug';
 set 'show_errors'  => 1;
 set 'startup_info' => 1;
 set 'warnings'     => 1;
 set 'username'     => 'admin';
 set 'password'     => 'password';
 set 'layout'       => 'main';
 
 my $flash;
 
 sub set_flash {
        my $message = shift;
 
        $flash = $message;
 }
 
 sub get_flash {
 
        my $msg = $flash;
        $flash = "";
 
        return $msg;
 }
 
 sub connect_db {
        my $dbh = DBI->connect("dbi:SQLite:dbname=".setting('database')) or
                die $DBI::errstr;
 
        return $dbh;
 }
 
 sub init_db {
        my $db = connect_db();
        my $schema = read_file('./schema.sql');
        $db->do($schema) or die $db->errstr;
 }
 
 before_template sub {
        my $tokens = shift;
        
        $tokens->{'css_url'} = request->base . 'css/style.css';
        $tokens->{'login_url'} = uri_for('/login');
        $tokens->{'logout_url'} = uri_for('/logout');
 };
 
 get '/' => sub {
        my $db = connect_db();
        my $sql = 'select id, title, text from entries order by id desc';
        my $sth = $db->prepare($sql) or die $db->errstr;
        $sth->execute or die $sth->errstr;
        template 'show_entries.tt', { 
                'msg' => get_flash(),
                'add_entry_url' => uri_for('/add'),
                'entries' => $sth->fetchall_hashref('id'),
        };
 };
 
 post '/add' => sub {
        if ( not session('logged_in') ) {
                send_error("Not logged in", 401);
        }
 
        my $db = connect_db();
        my $sql = 'insert into entries (title, text) values (?, ?)';
        my $sth = $db->prepare($sql) or die $db->errstr;
        $sth->execute(params->{'title'}, params->{'text'}) or die $sth->errstr;
 
        set_flash('New entry posted!');
        redirect '/';
 };
 
 any ['get', 'post'] => '/login' => sub {
        my $err;
 
        if ( request->method() eq "POST" ) {
                # process form input
                if ( params->{'username'} ne setting('username') ) {
                        $err = "Invalid username";
                }
                elsif ( params->{'password'} ne setting('password') ) {
                        $err = "Invalid password";
                }
                else {
                        session 'logged_in' => true;
                        set_flash('You are logged in.');
                        return redirect '/';
                }
        }

        # display login form
        template 'login.tt', { 
                'err' => $err,
        };

 };

 get '/logout' => sub {
        session->destroy;
        set_flash('You are logged out.');
        redirect '/';
 };

 init_db();
 start;

 


高级的路由

这有很多的路由匹配的方法,象这个例子中,你可以使用正则,你也可以使用命名参数如 /hello/:name 这种方式.

 


使用 Dancer 快乐

希望这个教程能有效的帮你喜欢上 Dancer .

 


AUTHORS

Dancer contributors – see AUTHORS file. 译者: 扶凯

 


NOTE

本文由个人根据 cpan 翻译,可能由于水平有限,译文质量望读者见谅,不屑一顾者请绕道远行,英文好的请直接看原著(最好也帮助翻译). — PerlChina 重建计划作品

来了就留个评论吧! 5个评论



    yfeng 2011年07月13日 的 02:02

    HI..想问下把Dancer用在生产环境中问题大吗?

    yingzi 2011年10月9日 的 00:53

    你好 扶凯,这应该是你的真名吧.我想今天看了你的这个教程.觉得这个 dancer 的架构很灵活,我想知道我如何才能修改默认的 3000 端口为 80 端口呢. 多谢~!!~!

      扶 凯 2011年10月9日 的 12:16

      使用 psgi 来启动 app 就行了,你可以看看相关的教程, 也可以看看 /lib/app.pl 的帮助,直接可以修改的。