File Coverage

blib/lib/WebNano.pm
Criterion Covered Total %
statement 44 49 89.8
branch 6 8 75.0
condition 2 6 33.3
subroutine 13 15 86.6
pod 5 5 100.0
total 70 83 84.3


line stmt bran cond sub pod time code
1 4     4   215776 use strict;
  4         10  
  4         158  
2 4     4   22 use warnings;
  4         9  
  4         182  
3              
4             package WebNano;
5             BEGIN {
6 4     4   81 $WebNano::VERSION = '0.007';
7             }
8              
9 4     4   1900 use WebNano::FindController 'find_nested';
  4         13  
  4         220  
10              
11 4     4   3380 use Plack::Response;
  4         44437  
  4         9104  
12 4     4   40 use Scalar::Util qw(blessed);
  4         8  
  4         260  
13 4     4   4038 use Object::Tiny::RW qw( renderer );
  4         1442  
  4         24  
14 4     4   5478 use Encode;
  4         57775  
  4         2418  
15              
16 70   33 70 1 684 sub DEBUG { return defined( $ENV{PLACK_ENV} ) && $ENV{PLACK_ENV} eq 'development'; }
17              
18             sub psgi_callback {
19 0     0 1 0 my $self = shift;
20              
21 0         0 warn 'psgi_callback is DEPRECATED! Use psgi_app instead';
22             sub {
23 0     0   0 $self->handle( shift );
24 0         0 };
25             }
26              
27             sub psgi_app {
28 5     5 1 223 my $self = shift;
29              
30             sub {
31 42     42   152627 $self->handle( shift );
32 5         71 };
33             }
34              
35 42     42 1 381 sub controller_search_path { [ ref(shift) ] };
36              
37             sub handle {
38 42     42 1 94 my( $self, $env ) = @_;
39 42         72 my $path = $env->{PATH_INFO};
40 42         132 my $c_class = find_nested( '', $self->controller_search_path );
41 42         175 $path =~ s{^/}{};
42 42 50       91 die 'Cannot find root controller' if !$c_class;
43 42         146 my @parts = split /\//, $path;
44 42         271 my $out = $c_class->handle(
45             path => [ @parts ],
46             app => $self,
47             env => $env,
48             self_url => '/',
49             );
50 41 100 33     283 if( not defined $out ){
    50          
    100          
51 6         37 my $res = Plack::Response->new(404);
52 6         100 $res->content_type('text/plain');
53 6         142 $res->body( 'No such page' );
54 6         90 return $res->finalize;
55             }
56             elsif( blessed $out and $out->isa( 'Plack::Response' ) ){
57 0         0 return $out->finalize;
58             }
59             elsif( ref $out eq 'CODE' ){
60 2         15 return $out;
61             }
62             else{
63 33         224 my $res = Plack::Response->new(200);
64 33         644 $res->content_type('text/html');
65 33         888 $res->body( encode( 'utf8', $out ) );
66 33         1384 return $res->finalize;
67             }
68             }
69              
70             1;
71              
72              
73              
74             =pod
75              
76             =head1 NAME
77              
78             WebNano - A minimalistic PSGI based web framework.
79              
80             =head1 VERSION
81              
82             version 0.007
83              
84             =head1 SYNOPSIS
85              
86             A minimal WebNano application can be an
87             app.psgi file like this:
88              
89             {
90             package MyApp;
91             use base 'WebNano';
92             1;
93             }
94            
95             {
96             package MyApp::Controller;
97             use base 'WebNano::Controller';
98            
99             sub index_action {
100             my $self = shift;
101             return 'This is my home';
102             }
103             1;
104             }
105            
106             my $app = MyApp->new();
107             $app->psgi_app;
108              
109             You can then run it with L.
110             A more practical approach is to split this into three different files.
111              
112             =head1 DESCRIPTION
113              
114             Every WebNano application has at least three parts - the application
115             class, at least one controller class and the
116             L file (or
117             something else that uses L
118             run the app).
119              
120             The application object is instantiated only once and is used to hold all the
121             other constant data objects - like the connection to the database, a template
122             renderer object (if it is too heavy to be created per request) and general
123             stuff that is too heavy to be rebuilt with each request. In contrast the
124             controller objects are recreated for each request.
125              
126             The dispatching implemented by L is a simple namespace matching
127             of HTTP request paths into method calls as in the following examples:
128              
129             '/page' -> 'MyApp::Controller->page_action()'
130             '/Some/Very/long/pa/th' -> 'MyApp::Controller::Some::Very->long_action( 'pa', 'th' )
131              
132             The dispatching to methods inside a controller is always available - to get actions
133             dispatched to controllers in subdirs you need to override the C
134             method and make it return a true value: C.
135             Usually in your root controller should do that.
136             Other controllers also can do it as well - but only if they
137             do not do their own dispatching to sub-controllers. If a controller has custom
138             dispatching then you should leave the default C to avoid
139             intruducing possible security risks from the automatic dispatching which could
140             bypass your controller's logic.
141              
142             Additionally if the last part of the path is empty then C is added to it - so C is
143             mapped to C and C is mapped to
144             Cindex_action>.
145              
146             You can override the C<_action> suffix with the C controller attribute which
147             maps URLs to functions just like the C attribute in C:
148              
149             $self->url_map( { 'mapped url' => 'mapped_url' } );
150              
151             or a list of approved methods to be dispached by name:
152              
153             $self->url_map( [ 'safe_method' ] );
154              
155             More advanced dispatching can be done by overriding the C method in
156             the Controller class:
157              
158             around 'local_dispatch' => sub {
159             my( $orig, $self ) = @_;
160             my( $id, $method, @args ) = @{ $self->path };
161             $method ||= 'view';
162             if( $id && $id =~ /^\d+$/ && $self->record_methods->{ $method } ){
163             my $rs = $self->app->schema->resultset( 'Dvd' );
164             my $record = $rs->find( $id );
165             if( ! $record ) {
166             my $res = $self->req->new_response(404);
167             $res->content_type('text/plain');
168             $res->body( 'No record with id: ' . $id );
169             return $res;
170             }
171             return $self->$method( $record, @args );
172             }
173             return $self->$orig();
174             };
175              
176             This example checks if the first part of the path is a number - if it is it uses
177             it to look for a Dvd object by primary key. If it cannot find such a Dvd then
178             it returns a 404. If it finds that dvd it then redispatches by the next path
179             part and passes that dvd object as the first parameter to that method call.
180             Note the need to check if the called method is an allowed one.
181             If the first part of the url is not a number - then the request is dispatched in
182             the normal way.
183              
184             More examples you can find in the C subdir.
185              
186             The primary design goal here is to provide basic functionality that should cover most
187             use cases and offer a easy way to override and extend it for special cases.
188             In general it is easy to write your own dispatcher that work for your limited use
189             case - and here you just need to do that, you can override the dispatching only for a
190             particular controller and you don't need to warry about the general cases.
191              
192             =head2 Controller object live in the request scope (new controller per request)
193              
194             If you need to build a heavy
195             structure used in the controller you can always build it as an
196             application attribute and use it in the controller as it has access to
197             the application object. However, since all the controller's work is done
198             in the request scope (i.e. creating the request) - then it makes sense
199             that the whole object should live in that scope. This is the same as
200             Tatsumaki handlers (and probably many non-Perl
201             frameworks), but different from Catalyst.
202              
203             =head2 Things that you can do with WebNano even though it does not actively support them
204              
205             There is a tendency in other frameworks to add interfaces to any other CPAN
206             library. With WebNano the goal is to keep it small, both in code and in its
207             interface. Instead of adding new interfaces for things that can be used
208             directly, but WebNano tries to make direct usage as simple as possible.
209              
210             A WebNano script is a PSGI application so you can immediately use all the Plack
211             tools.
212             For example to use sessions you can add following line to your app.psgi file:
213              
214             enable 'session'
215              
216             Read
217             L
218             about the additional options that you can enable here. See also
219             L
220             to read about the sweetened syntax you can use in your app.psgi file
221             and L
222             to find out what other Plack::Middleware packages are available.
223              
224             The same goes for MVC. WebNano does not have any methods or attributes for
225             models, not because I don't structure my web application using the 'web MVC'
226             pattern - but rather because I don't see any universal attribute or method of
227             the possible models. Users are free to add their own methods. For example most
228             of my code uses L
229             - and I add these lines to my application:
230              
231             has schema => ( is => 'ro', isa => 'DBIx::Class::Schema', lazy_build => 1 );
232              
233             sub _build_schema {
234             my $self = shift;
235             my $config = $self->config->{schema};
236             return DvdDatabase::DBSchema->connect( $config->{dbi_dsn},
237             $config->{user}, $config->{pass}, $config->{dbi_params} );
238             }
239              
240             then I use it with C<$self-Eapp-Eschema> in the controller objects.
241              
242             As to Views - I've added some support for two templating engines for WebNano,
243             but this is only because I wanted to experiment with 'template inheritance'. If
244             you don't want to use 'template inheritance' you can use Template::Tookit
245             directly in your controller actions or you can use directly any templating
246             engine in your controller actions - like
247             C<$self-Eapp-Emy_templating-Eprocess('template_name' )>
248             or even C<$self-Emy_templating-Eprocess( ... )> as long as it
249             returns a string.
250              
251             =head3 Streaming
252              
253             You can use the original L
254             The streaming_action method in F can be used as an example.
255              
256             =head3 Authentication
257              
258             https://github.com/zby/Plack-Middleware-Auth-Form soon on CPAN.
259              
260             =head3 Authorization
261              
262             Example:
263              
264             around 'local_dispatch' => sub {
265             my $orig = shift;
266             my $self = shift;
267             if( !$self->env->{user} ){
268             return $self->render( template => 'login_required.tt' );
269             }
270             $self->$orig( @_ );
271             };
272              
273             C is called before the controll is passed to child controllers,
274             so if you put that into the C controller - then both
275             all local actions and actions in child controllers (for example
276             C) would be guarded agains unauthorized usage.
277              
278             =head1 ATTRIBUTES and METHODS
279              
280             =head2 psgi_app
281              
282             This is a method which returns a subroutine reference suitable for PSGI.
283             The returned subrourine ref is a closure over the application object.
284              
285             =head2 psgi_callback
286              
287             This method is deprecated - use psgi_app instead.
288              
289             =head2 controller_search_path
290              
291             Experimental.
292              
293             =head2 handle
294              
295             Application method that acts as the PSGI callback - takes environment
296             as input and returns the response.
297              
298             =head2 renderer
299              
300             Nearly every web application uses some templating engine - this is the
301             attribute to keep the templating engine object. It is not mandatory that you
302             follow this rule.
303              
304             =head2 DEBUG
305              
306             If set prints out some debugging information to stdout. By default checks if
307             C<$ENV{PLACK_ENV} eq 'development'>.
308              
309             =head1 DIAGNOSTICS
310              
311             =for author to fill in:
312              
313             =over
314              
315             =back
316              
317             =head1 SEE ALSO
318              
319             L - Template Toolkit renderer with template inheritance
320              
321             L (experimental),
322              
323             L - example blog engine using WebNano
324              
325             =head1 DEPENDENCIES
326              
327             See Makefile.PL
328              
329             =head1 INCOMPATIBILITIES
330              
331             None reported.
332              
333             =head1 BUGS AND LIMITATIONS
334              
335             No bugs have been reported.
336              
337             Please report any bugs or feature requests to
338             C, or through the web interface at
339             L.
340              
341             =head1 CONTRIBUTORS
342              
343             Jeff Doozan
344              
345             =head1 AUTHOR
346              
347             Zbigniew Lukasiak
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is Copyright (c) 2010 by Zbigniew Lukasiak .
352              
353             This is free software, licensed under:
354              
355             The Artistic License 2.0 (GPL Compatible)
356              
357             =cut
358              
359              
360             __END__