WebNano - A minimalistic PSGI based web framework.
version 0.007
A minimal WebNano application can be an app.psgi file like this:
{ package MyApp; use base 'WebNano'; 1; } { package MyApp::Controller; use base 'WebNano::Controller'; sub index_action { my $self = shift; return 'This is my home'; } 1; } my $app = MyApp->new(); $app->psgi_app;
You can then run it with plackup. A more practical approach is to split this into three different files.
Every WebNano application has at least three parts - the application class, at least one controller class and the app.psgi file (or something else that uses Plack::Runner run the app).
The application object is instantiated only once and is used to hold all the other constant data objects - like the connection to the database, a template renderer object (if it is too heavy to be created per request) and general stuff that is too heavy to be rebuilt with each request. In contrast the controller objects are recreated for each request.
The dispatching implemented by WebNano::Controller is a simple namespace matching of HTTP request paths into method calls as in the following examples:
'/page' -> 'MyApp::Controller->page_action()' '/Some/Very/long/pa/th' -> 'MyApp::Controller::Some::Very->long_action( 'pa', 'th' )
The dispatching to methods inside a controller is always available - to get actions dispatched to controllers in subdirs you need to override the search_subcontrollers method and make it return a true value: sub search_subcontrollers { 1 }. Usually in your root controller should do that. Other controllers also can do it as well - but only if they do not do their own dispatching to sub-controllers. If a controller has custom dispatching then you should leave the default search_subcontrollers to avoid intruducing possible security risks from the automatic dispatching which could bypass your controller's logic.
search_subcontrollers
sub search_subcontrollers { 1 }
Additionally if the last part of the path is empty then index is added to it - so / is mapped to index_action and /SomeController/ is mapped to MyApp::SomeController->index_action.
index
/
index_action
/SomeController/
MyApp::SomeController->index_action
You can override the _action suffix with the url_map controller attribute which maps URLs to functions just like the run_modes attribute in CGI::Application:
_action
url_map
run_modes
CGI::Application
$self->url_map( { 'mapped url' => 'mapped_url' } );
or a list of approved methods to be dispached by name:
$self->url_map( [ 'safe_method' ] );
More advanced dispatching can be done by overriding the local_dispatch method in the Controller class:
local_dispatch
around 'local_dispatch' => sub { my( $orig, $self ) = @_; my( $id, $method, @args ) = @{ $self->path }; $method ||= 'view'; if( $id && $id =~ /^\d+$/ && $self->record_methods->{ $method } ){ my $rs = $self->app->schema->resultset( 'Dvd' ); my $record = $rs->find( $id ); if( ! $record ) { my $res = $self->req->new_response(404); $res->content_type('text/plain'); $res->body( 'No record with id: ' . $id ); return $res; } return $self->$method( $record, @args ); } return $self->$orig(); };
This example checks if the first part of the path is a number - if it is it uses it to look for a Dvd object by primary key. If it cannot find such a Dvd then it returns a 404. If it finds that dvd it then redispatches by the next path part and passes that dvd object as the first parameter to that method call. Note the need to check if the called method is an allowed one. If the first part of the url is not a number - then the request is dispatched in the normal way.
More examples you can find in the examples subdir.
examples
The primary design goal here is to provide basic functionality that should cover most use cases and offer a easy way to override and extend it for special cases. In general it is easy to write your own dispatcher that work for your limited use case - and here you just need to do that, you can override the dispatching only for a particular controller and you don't need to warry about the general cases.
If you need to build a heavy structure used in the controller you can always build it as an application attribute and use it in the controller as it has access to the application object. However, since all the controller's work is done in the request scope (i.e. creating the request) - then it makes sense that the whole object should live in that scope. This is the same as Tatsumaki handlers (and probably many non-Perl frameworks), but different from Catalyst.
There is a tendency in other frameworks to add interfaces to any other CPAN library. With WebNano the goal is to keep it small, both in code and in its interface. Instead of adding new interfaces for things that can be used directly, but WebNano tries to make direct usage as simple as possible.
A WebNano script is a PSGI application so you can immediately use all the Plack tools. For example to use sessions you can add following line to your app.psgi file:
enable 'session'
Read Plack::Middleware::Session about the additional options that you can enable here. See also Plack::Builder to read about the sweetened syntax you can use in your app.psgi file and http://search.cpan.org/search?query=Plack+Middleware&mode=all to find out what other Plack::Middleware packages are available.
The same goes for MVC. WebNano does not have any methods or attributes for models, not because I don't structure my web application using the 'web MVC' pattern - but rather because I don't see any universal attribute or method of the possible models. Users are free to add their own methods. For example most of my code uses DBIx::Class - and I add these lines to my application:
has schema => ( is => 'ro', isa => 'DBIx::Class::Schema', lazy_build => 1 ); sub _build_schema { my $self = shift; my $config = $self->config->{schema}; return DvdDatabase::DBSchema->connect( $config->{dbi_dsn}, $config->{user}, $config->{pass}, $config->{dbi_params} ); }
then I use it with $self->app->schema in the controller objects.
$self->app->schema
As to Views - I've added some support for two templating engines for WebNano, but this is only because I wanted to experiment with 'template inheritance'. If you don't want to use 'template inheritance' you can use Template::Tookit directly in your controller actions or you can use directly any templating engine in your controller actions - like $self->app->my_templating->process('template_name' ) or even $self->my_templating->process( ... ) as long as it returns a string.
$self->app->my_templating->process('template_name' )
$self->my_templating->process( ... )
You can use the original "Delayed_Reponse_and_Streaming_Body" in PSGI The streaming_action method in t/lib/MyApp/Controller.pm can be used as an example.
https://github.com/zby/Plack-Middleware-Auth-Form soon on CPAN.
Example:
around 'local_dispatch' => sub { my $orig = shift; my $self = shift; if( !$self->env->{user} ){ return $self->render( template => 'login_required.tt' ); } $self->$orig( @_ ); };
local_dispatch is called before the controll is passed to child controllers, so if you put that into the MyApp::Controller::Admin controller - then both all local actions and actions in child controllers (for example MyApp::Controller::Admin::User) would be guarded agains unauthorized usage.
MyApp::Controller::Admin
MyApp::Controller::Admin::User
This is a method which returns a subroutine reference suitable for PSGI. The returned subrourine ref is a closure over the application object.
This method is deprecated - use psgi_app instead.
Experimental.
Application method that acts as the PSGI callback - takes environment as input and returns the response.
Nearly every web application uses some templating engine - this is the attribute to keep the templating engine object. It is not mandatory that you follow this rule.
If set prints out some debugging information to stdout. By default checks if $ENV{PLACK_ENV} eq 'development'.
$ENV{PLACK_ENV} eq 'development'
WebNano::Renderer::TT - Template Toolkit renderer with template inheritance
WebNano::Controller::CRUD (experimental),
http://github.com/zby/Nblog - example blog engine using WebNano
See Makefile.PL
None reported.
No bugs have been reported.
Please report any bugs or feature requests to bug-webnano@rt.cpan.org, or through the web interface at http://rt.cpan.org.
bug-webnano@rt.cpan.org
Jeff Doozan
Zbigniew Lukasiak <zby@cpan.org>
This software is Copyright (c) 2010 by Zbigniew Lukasiak <zby@cpan.org>.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)
To install WebNano, copy and paste the appropriate command in to your terminal.
cpanm
cpanm WebNano
CPAN shell
perl -MCPAN -e shell install WebNano
For more information on module installation, please visit the detailed CPAN module installation guide.