package Mojolicious::Lite; use Mojo::Base 'Mojolicious'; # "Bender: Bite my shiny metal ass!" use File::Basename 'dirname'; use File::Spec::Functions 'catdir'; use Mojo::UserAgent; sub import { my $class = shift; # Executable $ENV{MOJO_EXE} ||= (caller)[1]; # Home local $ENV{MOJO_HOME} = catdir(split '/', dirname($ENV{MOJO_EXE})) unless $ENV{MOJO_HOME}; # Initialize app no strict 'refs'; my $caller = caller; push @{"${caller}::ISA"}, 'Mojo'; my $app = $class->new; # Initialize routes my $routes = $app->routes->namespace(''); # Default static and template class $app->static->classes->[0] = $app->renderer->classes->[0] = $caller; # Export no warnings 'redefine'; my $root = $routes; for my $name (qw(any get options patch post put websocket)) { *{"${caller}::$name"} = sub { $routes->$name(@_) }; } *{"${caller}::new"} = *{"${caller}::app"} = sub {$app}; *{"${caller}::del"} = sub { $routes->delete(@_) }; *{"${caller}::group"} = sub (&) { my $old = $root; $_[0]->($root = $routes); ($routes, $root) = ($root, $old); }; *{"${caller}::helper"} = sub { $app->helper(@_) }; *{"${caller}::hook"} = sub { $app->hook(@_) }; *{"${caller}::plugin"} = sub { $app->plugin(@_) }; *{"${caller}::under"} = sub { $routes = $root->under(@_) }; # Make sure there's a default application for testing Mojo::UserAgent->app($app) unless Mojo::UserAgent->app; # Lite apps are strict! Mojo::Base->import(-strict); } 1; =pod =encoding utf-8 =head1 文档 Mojolicious::Lite - Real-time micro web framework =head1 概述 # 自动打开 "strict", "warnings" 和 Perl 5.10 特性 use Mojolicious::Lite; # 占位符的路径选择 get '/:foo' => sub { my $c = shift; my $foo = $c->param('foo'); $c->render(text => "Hello from $foo."); }; # 启用 Mojolicious 命令行体系 app->start; =head1 描述 L 是由 L 做的一个实时 web 框架. 更新到版本 5.05. =head1 教程 一个简单的例子来给你介绍, 在 L 中的奇妙之处. 你会在这里学到的大多数也适用于全功能的 L 应用. =head2 Hello World 下面是一个简单的 Hello World 的应用程序.因为在 Perl 5.10 中会默认启用 L, L 几个功能.并导入 L 的功能, 让你的脚本本成一个全功能的 web 应用. #!/usr/bin/env perl use Mojolicious::Lite; get '/' => sub { my $c = shift; $c->render(text => 'Hello World!'); }; app->start; 这有一个辅助命令来生成这个例子中的应用程序 $ mojo generate lite_app myapp.pl =head2 Commands 这些标准的 L 中会提供一些常用的命令行功能. 注意它可以自动的对 CGI 和 PSGI 的环境检查出来.可 以不提供参数. $ ./myapp.pl daemon Server available at http://127.0.0.1:3000. $ ./myapp.pl daemon -l http://*:8080 Server available at http://127.0.0.1:8080. $ ./myapp.pl cgi ...CGI output... $ ./myapp.pl get / Hello World! $ ./myapp.pl ...List of available commands (or automatically detected environment)... 你可以在 app->start 的调用中加入参数, 来替换掉标准的从 C<@ARGV> 中接收参数. app->start('cgi'); =head2 修改代码后自动加载 如果你想你的应用在修改后能自动的 reload 加载你的修改的程序, 建议你使用 morbo 的开发用的 web 服务器, 这样你就不用每次修改后重起. $ morbo myapp.pl Server available at http://127.0.0.1:3000. 译者注: 默认只监控 "lib" 和 "templates" 的文件夹内的改变, 如果你想加入指定的路径的文件进行监控修改后自动重起, 可以使用 -w 参数 $ morbo -w /myhome/lib myapp.pl 关如何部署你的应用程序的更多信息, 请看 L. =head2 Routes Routes 只是对请求过来的路径指向不同的函数. 可以在路径中包含不同的占位符. 传进来的第一个参数是 C<$c> 是 L 对象本身, 它也包含着 HTTP request 和 HTTP response 的对象. use Mojolicious::Lite; # /foo get '/foo' => sub { my $c = shift; $c->render(text => 'Hello World!'); }; app->start; 响应的内容通常是由 L 的动作产生的, 后面会讲到. =head2 GET/POST 传进来的参数 全部的 C 和 C 的参数只需要通过 L 来访问. use Mojolicious::Lite; # /foo?user=sri get '/foo' => sub { my $c = shift; my $user = $c->param('user'); $c->render(text => "Hello $user."); }; app->start; =head2 Stash 和模板 ( templates ) 这个 L 是用来传一些数据给模板技术使用, 在这我们使用的模板是存储在 C 这个部分中的 @@ 标记的位置内容. use Mojolicious::Lite; # /bar get '/bar' => sub { my $c = shift; $c->stash(one => 23); $c->render('baz', two => 24); }; app->start; __DATA__ @@ baz.html.ep The magic numbers are <%= $one %> and <%= $two %>. 更多有关模板的信息可以看看 L. =head2 HTTP 相关的对象 L 和 L 的对象调用提供了全功能的 HTTP 的支持. use Mojolicious::Lite; # 访问请求的信息, 通过 req 的对象. get '/agent' => sub { my $c = shift; my $host = $c->req->url->to_abs->host; my $ua = $c->req->headers->user_agent; $c->render(text => "Request by $ua reached $host."); }; # 通过 res 这个对象定制输出的 header. post '/echo' => sub { my $c = shift; $c->res->headers->header('X-Bender' => 'Bite my shiny metal ass!'); $c->render(data => $c->req->body); }; app->start; 您可以直接从使用命令行测试,这个高级的例子如下 $ ./myapp.pl get -v -M POST -c 'test' /echo =head2 内置异常 C 和 C 网页 在开发的时候, 有时会因为我们的一些错误我们需要用到这些网页, 这个中包含着很多有利于我们 debug 的信息. use Mojolicious::Lite; # Not found (404) get '/missing' => sub { shift->render('does_not_exist') }; # Exception (500) get '/dies' => sub { die 'Intentional error' }; app->start; 你还可以在命令行下使用 L 加上 CSS 选择器来导入你感兴趣的信息. $ ./myapp.pl get /dies '#error' =head2 Route 关联名称 全部的 Routes 会关联到指定的相关的名字, 会自动的取得相关的名字的模板. 这个名字, 可以直接在 L 和 L 中使用, 可以很方便的帮到你. use Mojolicious::Lite; # / get '/' => sub { my $c = shift; $c->render; } => 'index'; # /hello get '/hello'; app->start; __DATA__ @@ index.html.ep <%= link_to Hello => 'hello' %>. <%= link_to Reload => 'index' %>. @@ hello.html.ep Hello World! =head2 Layouts 模板是可以设计成组件来布局, 你只需要选择 helper 中的 L. 通过 L 给结果放到模板. use Mojolicious::Lite; # /with_layout get '/with_layout' => sub { my $c = shift; $c->render('with_layout'); }; app->start; __DATA__ @@ with_layout.html.ep % title 'Green'; % layout 'green'; Hello World! @@ layouts/green.html.ep <%= title %> <%= content %> 有 layout 中你可以使用 stash 或者 helpers 来给一些附加的数据放进来, 就象上面使用的 L . =head2 模板中的代码块 模板技术中, 可以使用 C 和 C 的关键字分隔成的代码块来做成象 Perl 本身一样的函数. 以方便在模板技术中重用. use Mojolicious::Lite; # /with_block get '/with_block' => 'block'; app->start; __DATA__ @@ block.html.ep % my $link = begin % my ($url, $name) = @_; Try <%= link_to $url => begin %><%= $name %><% end %>. % end Sebastians frameworks %= $link->('http://mojolicio.us', 'Mojolicious') %= $link->('http://catalystframework.org', 'Catalyst') =head2 Helpers 你可以在 L 中通过自己的 helper 来扩展应用, 全部的原生的 helper 可以查看 L 和 L. use Mojolicious::Lite; # A helper to identify visitors helper whois => sub { my $c = shift; my $agent = $c->req->headers->user_agent || 'Anonymous'; my $ip = $c->tx->remote_address; return "$agent ($ip)"; }; # Use helper in action and template get '/secret' => sub { my $c = shift; my $user = $c->whois; $c->app->log->debug("Request from $user."); }; app->start; __DATA__ @@ secret.html.ep We know who you are <%= whois %>. =head2 占位符 路径选择的占位符可以让你取得一些请求中的路径的参数. 结果会可以通过 L 和 L 来访问. use Mojolicious::Lite; # /foo/test # /foo/test123 get '/foo/:bar' => sub { my $c = shift; my $bar = $c->stash('bar'); $c->render(text => "Our :bar placeholder matched $bar"); }; # /testsomething/foo # /test123something/foo get '/(:bar)something/foo' => sub { my $c = shift; my $bar = $c->param('bar'); $c->render(text => "Our :bar placeholder matched $bar"); }; app->start; =head2 松懈占位符 松懈占位可以让你匹配直接到 C . use Mojolicious::Lite; # /test/hello # /test123/hello # /test.123/hello get '/#you/hello' => 'groovy'; app->start; __DATA__ @@ groovy.html.ep Your name is <%= $you %>. =head2 通配位符 配位符可以匹配到任何东西, 包括 C 和 C<.>. use Mojolicious::Lite; # /hello/test # /hello/test123 # /hello/test.123/test/123 get '/hello/*you' => 'groovy'; app->start; __DATA__ @@ groovy.html.ep Your name is <%= $you %>. =head2 HTTP 方法 路径选择器可以通过指定 HTTP 的方法来限定. use Mojolicious::Lite; # GET /hello get '/hello' => sub { my $c = shift; $c->render(text => 'Hello World!'); }; # PUT /hello put '/hello' => sub { my $c = shift; my $size = length $c->req->body; $c->render(text => "You uploaded $size bytes to /hello."); }; # GET|POST|PATCH /bye any [qw(GET POST PATCH)] => '/bye' => sub { my $c = shift; $c->render(text => 'Bye World!'); }; # * /whatever any '/whatever' => sub { my $c = shift; my $method = $c->req->method; $c->render(text => "You called /whatever with $method."); }; app->start; =head2 可选的占位符 全部的占位符都需要一个值, 但我们可以通过分配一个默认值, 让占位符的捕获是可选的. 默认的值会简单的和取到的值进行合并在存储在 stash 中. 可选的占位符. use Mojolicious::Lite; # /hello # /hello/Sara get '/hello/:name' => {name => 'Sebastian', day => 'Monday'} => sub { my $c = shift; $c->render('groovy', format => 'txt'); }; app->start; __DATA__ @@ groovy.txt.ep My name is <%= $name %> and it is <%= $day %>. =head2 限制性占位符 有个最简单的方法, 让你的占位符有严格的范围, 您只需写出可能值的列表. use Mojolicious::Lite; # /test # /123 any '/:foo' => [foo => [qw(test 123)]] => sub { my $c = shift; my $foo = $c->param('foo'); $c->render(text => "Our :foo placeholder matched $foo"); }; app->start; 所有的占位符被编译到成正则表达式, 所以在这也可以很容易地定制你的这个. use Mojolicious::Lite; # /1 # /123 any '/:bar' => [bar => qr/\d+/] => sub { my $c = shift; my $bar = $c->param('bar'); $c->render(text => "Our :bar placeholder matched $bar"); }; app->start; 只要确保不使用 C<^> 和 C<$> 还有捕获组 C<(...)>, 因为这些占位符会和内置的组成一个更大的正则表达式. =head2 Under 通过共享代码进行认证, 在多个路径层次之间很容易实现, 只需要桥接生成的路由并使用 C 的声明. 判断是否是认证通过的路径选择器, 只需要看看返回是否为 true 就知道了. use Mojolicious::Lite; # Authenticate based on name parameter under sub { my $c = shift; # Authenticated my $name = $c->param('name') || ''; return 1 if $name eq 'Bender'; # Not authenticated $c->render('denied'); return undef; }; # / (with authentication) get '/' => 'index'; app->start; __DATA__; @@ denied.html.ep You are not Bender, permission denied. @@ index.html.ep Hi Bender. 要实现相同前缀的多个路径选择器, 也是一个使用的 C 的好理由. use Mojolicious::Lite; # /foo under '/foo'; # /foo/bar get '/bar' => {text => 'foo bar'}; # /foo/baz get '/baz' => {text => 'foo baz'}; # / under '/' => {message => 'whatever'}; # /bar get '/bar' => {inline => '<%= $message %> works'}; app->start; 你也可以使用 C 来组织相关的 routes , 这可以对多个路径进行 C 的声明. use Mojolicious::Lite; # Global logic shared by all routes under sub { my $c = shift; return 1 if $c->req->headers->header('X-Bender'); $c->render(text => "You're not Bender."); return undef; }; # Admin section group { # Local logic shared only by routes in this group under '/admin' => sub { my $c = shift; return 1 if $c->req->heaers->header('X-Awesome'); $c->render(text => "You're not awesome enough."); return undef; }; # GET /admin/dashboard get '/dashboard' => {text => 'Nothing to see here yet.'}; }; # GET /welcome get '/welcome' => {text => 'Hi Bender.'}; app->start; =head2 输出格式 ( 请求后缀 ) 这其实是指的后缀, 这个会自动根据后缀来选择. use Mojolicious::Lite; # /detection.html # /detection.txt get '/detection' => sub { my $c = shift; $c->render('detected'); }; app->start; __DATA__ @@ detected.html.ep Detected HTML was detected. @@ detected.txt.ep TXT was detected. 限制性的的占位符也可以使用. use Mojolicious::Lite; # /hello.json # /hello.txt get '/hello' => [format => [qw(json txt)]] => sub { my $c = shift; return $c->render_json({hello => 'world'}) if $c->stash('format') eq 'json'; $c->render_text('hello world'); }; app->start; 你也可以禁用格式检查. use Mojolicious::Lite; # /hello get '/hello' => [format => 0] => {text => 'No format detection.'}; # Disable detection and allow the following routes selective re-enabling under [format => 0]; # /foo get '/foo' => {text => 'No format detection again.'}; # /bar.txt get '/bar' => [format => 'txt'] => {text => ' Just one format.'}; app->start; =head2 内容协商 不同的表示方法和需要使用 C 的来协商时, 你可以看看 L. use Mojolicious::Lite; # /hello (Accept: application/json) # /hello (Accept: application/xml) # /hello.json # /hello.xml # /hello?format=json # /hello?format=xml get '/hello' => sub { my $c = shift; $c->respond_to( json => {json => {hello => 'world'}}, xml => {text => 'world'}, any => {data => '', status => 204} ); }; app->start; 有关 MIME type 的相关后缀的对应关系看 L. app->types->type(rdf => 'application/rdf+xml'); =head2 静态文件 和模板技术一样, 只是静态文件可以是单个文件, 也可以从内部 C 的部分 ( 可以是 Base64 的编码 ). use Mojolicious::Lite; app->start; __DATA__ @@ something.js alert('hello!'); @@ test.txt (base64) dGVzdCAxMjMKbGFsYWxh 外部静态文件不限于一个单独的文件, 如果存在 C 会自动从这个下面查找所需要的文件. $ mkdir public $ mv something.js public/something.js $ mv mojolicious.tar.gz public/mojolicious.tar.gz =head2 外部模板 外部模板会从你的应用目录中的 C 目录中来查找, 如果不存在就会查找你的 C 块中是否存在. use Mojolicious::Lite; # /external any '/external' => sub { my $c = shift; # templates/foo/bar.html.ep $c->render('foo/bar'); }; app->start; =head2 Conditions 条件可以是象 C 和 C 之类的信息, 是从 L 中来的东西.可以提供强大的路由限制. use Mojolicious::Lite; # /foo (Firefox) get '/foo' => (agent => qr/Firefox/) => sub { my $c = shift; $c->render(text => 'Congratulations, you are using a cool browser.'); }; # /foo (Internet Explorer) get '/foo' => (agent => qr/Internet Explorer/) => sub { my $c = shift; $c->render(text => 'Dude, you really need to upgrade to Firefox.'); }; # http://mojolicio.us/bar get '/bar' => (host => 'mojolicio.us') => sub { my $c = shift; $c->render(text => 'Hello Mojolicious.'); }; app->start; =head2 Sessions 签名 cookie 基于你的 session , 这个原生可以使用.直接通过 L 这个来访问到, 所以的全部的 session 的数据序列化是通过 L 实现的. use Mojolicious::Lite; get '/counter' => sub { my $c = shift; $c->session->{counter}++; }; app->start; __DATA__ @@ counter.html.ep Counter: <%= session 'counter' %> 需要注意的是, 你应该使用一个自定义的 L 来签署 Cookie 才会真正的安全. app->secrets(['My secret passphrase here']); =head2 File uploads 所有上传的文件只要是 C 的请求会自动转为 L 对象处理. 你不用担心内存的使用, 因为超过 C<250KB> 的所有文件将被自动到一个临时文件. use Mojolicious::Lite; # Upload form in DATA section get '/' => 'form'; # Multipart upload handler post '/upload' => sub { my $c = shift; # Check file size return $c->render(text => 'File is too big.', status => 200) if $c->req->is_limit_exceeded; # Process uploaded file return $c->redirect_to('form') unless my $example = $c->param('example'); my $size = $example->size; my $name = $example->filename; $c->render(text => "Thanks for uploading $size byte file $name."); }; app->start; __DATA__ @@ form.html.ep Upload %= form_for upload => (enctype => 'multipart/form-data') => begin %= file_field 'example' %= submit_button 'Upload' % end 为了保护您避免过大的文件, 这也有一个默认极限的值 C<10MB>.你可以使用 L 或者 C 的环境变量来调整这个. # Increase limit to 1GB $ENV{MOJO_MAX_MESSAGE_SIZE} = 1073741824; =head2 User agent 使用 L , 可以通过 L 这个 helper, 它是一个全功能的 HTTP 和 WebSocket 的用户代理, 当你使用 L 和 L 组合使用时非常的强大. use Mojolicious::Lite; # Blocking get '/headers' => sub { my $c = shift; my $url = $c->param('url') || 'http://mojolicio.us'; my $dom = $c->ua->get($url)->res->dom; $c->render(json => [$dom->find('h1, h2, h3')->text->each]); }; # Non-blocking get '/title' => sub { my $c = shift; $c->ua->get('mojolicio.us' => sub { my ($ua, $tx) = @_; $c->render(data => $tx->res->dom->at('title')->text); }); }; # Concurrent non-blocking get '/titles' => sub { my $c = shift; my $delay = Mojo::IOLoop->delay(sub { my ($delay, @titles) = @_; $c->render(json => \@titles); }); for my $url ('http://mojolicio.us', 'https://metacpan.org') { my $end = $delay->begin(0); $c->ua->get($url => sub { my ($ua, $tx) = @_; $end->($tx->res->dom->html->head->title->text); }); } }; app->start; 更多用法请看 L. =head2 WebSockets WebSocket 的应用程序从未如此简单. 接收信息通过 L 中的事件订阅 L. 并通过 L 返回就行了. use Mojolicious::Lite; websocket '/echo' => sub { my $c = shift; $c->on(json => sub { my ($c, $hash) = @_; $hash->{msg} = "echo: $hash->{msg}"; $c->send({json => $hash}); }); }; get '/' => 'index'; app->start; __DATA__ @@ index.html.ep Echo 更我相关的信息可以查看 L. =head2 模式 你可以通过 L 中的 L 方法收集调试信息, 并通过 L 修改模式为 production 来禁用这个. 也可以通过 L 来取到属性. use Mojolicious::Lite; # Prepare mode specific message during startup my $msg = app->mode eq 'development' ? 'Development!' : 'Something else!'; get '/' => sub { my $c = shift; $c->app->log->debug('Rendering mode specific message.'); $c->render(text => $msg); }; app->log->debug('Starting application.'); app->start; 默认模式是使用的 C. 这个可以通过命令行参数或者 C and C 的环境变量的值来修改它, 模式修改的其它改变是日志级别由 C 变成 C. $ ./myapp.pl daemon -m production 全部的日志信息默认会写到标准输出或者如果存在 log 目录就会写到 C 中. $ mkdir log 模式的改变, 这也会影响到模板其它的地方, 如 C 和 C 的模板. =head2 Testing 创建一个 C 目录和进行 Perl 的单元测试, 测试您的应用程序在这也一样简单. use Test::More; use Test::Mojo; use FindBin; require "$FindBin::Bin/../myapp.pl"; my $t = Test::Mojo->new; $t->get_ok('/')->status_is(200)->content_like(qr/Funky/); done_testing(); 使用 L 命令运行全部的单元测试. $ ./myapp.pl test $ ./myapp.pl test -v 如果你想你的测试报告更加多的信息, 你也可以直接在您的测试文件中更改该应用程序的日志级别. $t->app->log->level('debug'); =head2 更多 你接下来可以看 L . =head1 功能 L 实现了下列的功能. =head2 C my $route = any '/:foo' => sub {...}; my $route = any '/:foo' => {foo => 'bar'} => sub {...}; my $route = any '/:foo' => [foo => qr/\w+/] => sub {...}; my $route = any [qw(GET POST)] => '/:foo' => sub {...}; 由 L 生成的路由, 会匹配所有的 http 请求. =head2 C my $app = app; 这是 L 的应用的对象本身. =head2 C my $route = del '/:foo' => sub {...}; my $route = del '/:foo' => {foo => 'bar'} => sub {...}; my $route = del '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L route , 只对 C 的请求有效, 以看详细教程的参数. =head2 C my $route = get '/:foo' => sub {...}; my $route = get '/:foo' => {foo => 'bar'} => sub {...}; my $route = get '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L 的 route , 只对 C 的请求有效, 以看详细教程的参数. =head2 C group {...}; 创建一个新的 route 组. =head2 C helper foo => sub {...}; 增加一个新的 L. =head2 C hook after_dispatch => sub {...}; 使用 L 共享你的功能. =head2 C my $route = options '/:foo' => sub {...}; my $route = options '/:foo' => {foo => 'bar'} => sub {...}; my $route = options '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L 的 route , 只对 C 的请求有效, 以看详细教程的参数. =head2 C my $route = patch '/:foo' => sub {...}; my $route = patch '/:foo' => {foo => 'bar'} => sub {...}; my $route = patch '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L 的路径, 只对 C 的请求有效, 以看详细教程的参数. =head2 C plugin SomePlugin => {foo => 23}; 加载插件 L. =head2 C my $route = post '/:foo' => sub {...}; my $route = post '/:foo' => {foo => 'bar'} => sub {...}; my $route = post '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L 的 route, 只对 C 的请求有效, 可以看详细教程的参数. =head2 C my $route = put '/:foo' => sub {...}; my $route = put '/:foo' => {foo => 'bar'} => sub {...}; my $route = put '/:foo' => [foo => qr/\w+/] => sub {...}; 生成 L 的 route , 只对 C 的请求有效, 可以看详细教程的参数. =head2 C my $bridge = under sub {...}; my $bridge = under '/:foo' => sub {...}; my $bridge = under '/:foo' => [foo => qr/\w+/]; my $bridge = under {format => 0}; 通过 L 生成桥接的路由, 主要用来转发路径选择并自动的附加上前面的, 用于前缀选择之类. =head2 C my $route = websocket '/:foo' => sub {...}; my $route = websocket '/:foo' => {foo => 'bar'} => sub {...}; my $route = websocket '/:foo' => [foo => qr/\w+/] => sub {...}; 通过 L 来生成路径选择, 只匹配 C 的握手. =head1 属性 L 继承全部的属性从 L. =head1 方法 L 继承全部的方法从 L. =head1 SEE ALSO L, L, L. =cut