NAME
MVC::Neaf - Not Even A (Web Application) Framework
OVERVIEW
Neaf [ni:f]
stands for Not Even A Framework.
The Model is assumed to be just a regular Perl module, no restrictions are imposed on it.
The View is an object with one method, render
, receiving a hashref and returning rendered content as string plus optional content-type header.
The Controller is broken down into handlers associated with URI paths. Each such handler receives a MVC::Neaf::Request object containing all it needs to know about the outside world, and returns a simple \%hashref
which is forwarded to View.
Please see the example
directory in this distribution that demonstrates the features of Neaf.
SYNOPSIS
The following application, outputting a greeting, is ready to run as a CGI script, PSGI application, or Apache handler.
use strict;
use warnings;
use MVC::Neaf qw(:sugar);
get+post '/app' => sub {
my $req = shift;
my $name = $req->param( name => qr/[-'\w\s]+/ ) || "Mystical stranger";
return {
name => $name,
};
}, default => {
-view => 'TT',
-type => "text/plain",
-template => \"Hello, [% name %]",
};
neaf->run;
A neaf application has some command-line interface built in:
perl myapp.pl --list
Will give a summary of available routes.
perl myapp.pl --listen :31415
Will start a default plackup
server (plackup myapp.pl
works as well)
perl myapp.pl --post --upload foo=/path/to/file /bar?life=42 --view Dumper
Will run just one request and stop right before template processing, dumping stash instead.
CREATING AN APPLICATION
THE CONTROLLER
The handler sub receives one and only argument, the request object, and outputs a \%hashref
.
It may also die, which will be interpreted as an error 500, UNLESS error message starts with 3 digits and a whitespace, in which case this is considered the return status. E.g. die 404;
is a valid method to return a configurable "Not Found" page right away.
Handlers are set using the "route" method discussed below.
THE REQUEST
MVC::Neaf::Request interface is similar to that of CGI or Plack::Request with some minor differences:
# What was requested:
http(s)://server.name:1337/mathing/route/some/more/slashes?foo=1&bar=2
# What is being returned:
$req->http_version; # = HTTP/1.0 or HTTP/1.1
$req->scheme ; # = http or https
$req->method ; # = GET
$req->hostname ; # = server.name
$req->port ; # = 1337
$req->path ; # = /mathing/route/some/more/slashes
$req->script_name ; # = /mathing/route
$req->path_info ; # = /some/more/slashes
$req->param( foo => '\d+' ); # = 1
$req->get_cookie( session => '.+' ); # = whatever it was set to before
One major difference is that there's no (easy) way to fetch query parameters or cookies without validation. Just use pattern qr/.*/
if you know better. But see also "add_form", forms are quite powerful.
Also there are some methods that affect the reply, mainly the headers, like set_cookie
or redirect
. This is a step towards a know-it-all God object, however, mapping those properties into a hashref turned out to be too cumbersome.
THE RESPONSE
The response may contain regular keys, typically alphanumeric, as well as a predefined set of dash-prefixed keys to control Neaf itself.
return {
-view => 'TT',
-template => 'users.html',
users => \@list,
extras => \%hash,
};
And that's it.
-Note -that -dash-prefixed -options -look -antique even to the author of this writing. However, it is a concise and visible way to separate auxiliary parameters from users's data, without requiring a more complex return structure (two hashes, array of arrays etc).
The small but growing list of these -options is as follows:
-content - Return raw data and skip view processing. E.g. display generated image.
-continue - A callback that receives the Request object. It will be executed AFTER the headers and the first content chunk are served to the client, and may use
$req->write( $data );
and$req->close;
to output more data.-headers - Pass a hash or array of values for header generation. This is an alternative to MVC::Neaf::Request's
push_header
method.-jsonp - Used by
JS
view module as a callback name to produce a jsonp response. Callback MUST be a set of identifiers separated by dots. Otherwise it's ignored for security reasons.-location - HTTP Location: header for 3xx statuses. This is set by
$request->redirect(...)
.[DEPRECATED] This will be phased out at some point, use
-header => [ location => ... ]
instead.-serial - if present, the
JS
view will render this instead of the whole response hash. This can be used, for instance, to return non-hash data in a REST API.[EXPERIMENTAL] Name and meaning may change in the future.
-status - HTTP status (200, 404, 500 etc). Default is 200 if the handler managed to live through, and 500 if it died.
-template - Set template name for a text processing view (currently MVC::Neaf::View::TT based on Template).
-type - Content-type HTTP header. View module may set this parameter if unset. Default is generated by the renderer - see MVC::Neaf::View.
-view - select View module. Views are initialized lazily and cached by the framework.
TT
,JS
,Full::Module::Name
, and$view_predefined_object
are currently supported. New short aliases may be created byMVC::Neaf->load_view( "name" => $your_view );
(see below).The default is
JS
denoting the the MVC::Neaf::View::JS engine. Adding-template
key will cause switching toMVC::Neaf::View::TT
, but it is deprecated and will go away in v.0.25.
Though more dash-prefixed parameters may be returned and will be passed to the View module as of current, they are not guaranteed to work in the future. Please either avoid them, or send patches.
FUNCTIONAL AND OBJECT-ORIENTED API
A :sugar
keyword must be added to the use
statement to get access to the prototyped declarative API. The need to do so MAY be removed in the future.
All prototyped declarative functions described below are really frontends to a single MVC::Neaf object that accumulates the knowledge about your application.
Though more than one such objects can be created, there is little use in doing that so far.
Given the above, functional and object-oriented ways to declare the same thing will now follow in pairs.
Returned value, if unspecified, is always the Neaf object (but who cares).
neaf()
Without arguments, returns the default Neaf instance that is also used to handle all the prototyped calls. As in
neaf->oo_method_without_shortcut(...);
Just in case you're curious, the default instance is $MVC::Neaf::Inst
. This name MAY change in the future.
See complete description below.
route()
The route() function and its numerous aliases define a handler for given by URI path and HTTP method(s).
$neaf->route( '/path' => CODEREF, %options )
get '/path' => sub { CODE; }, %options;
Equivalent to
neaf->route( '/path' => sub { CODE; }, method => 'GET', %options );
post '/path' => sub { CODE; }, %options;
Ditto, but sets method => 'POST'
head ... - autogenerated by
get
, but can be specified explicitly if neededput ...
patch ...
del ... is for
DELETE
(becausedelete
is a Perl's own keyword).any [ 'get', 'post', 'CUSTOM_METHOD' ] => '/path' => \&handler
Short aliases can be combined using the +
sign, as in
get + post '/submit' => sub {
my $req = shift;
# do a lot of common stuff here
if ($req->is_post) {
# a few lines unique to POST method
$req->redirect('/done');
};
return { ... }
};
post + put + patch '/some/item' => sub {
my $req = shift;
# generate item from $req->body
};
Any incoming request to uri matching /path
(/path/something/else
too, but NOT /pathology
) will now be directed to CODEREF.
Longer paths are GUARANTEED to be checked first.
Dies if the same method and path combination is given twice (but see tentative
and override
below). Multiple methods may be given for the same path.
Exactly one leading slash will be prepended no matter what you do. (path
, /path
and /////path
are all the same).
The CODEREF
MUST accept exactly one argument, referred to as $request
or $req
hereafter, and return an unblessed hashref with response data.
%options may include:
method
- list of allowed HTTP methods. Default is [GET, POST]. Multiple handles can be defined for the same path, provided that methods do not intersect. HEAD method is automatically handled if GET is present, however, one MAY define a separate HEAD handler explicitly.path_info_regex
=>qr/.../
- allow URI subpaths to be handled by this handler.A 404 error will be generated unless
path_info_regex
is present and PATH_INFO matches the regex (without the leading slashes).If path_info_regex matches, it will be available in the controller as
$req->path_info
.If capture groups are present in said regular expression, their content will also be available as
$req->path_info_split
.[EXPERIMENTAL] Name and semantics MAY change in the future.
param_regex
=> { name =>qr/.../
, name2 =>'\d+'
}Add predefined regular expression validation to certain request parameters, so that they can be queried by name only. See
param()
in MVC::Neaf::Request.[EXPERIMENTAL] Name and semantics MAY change in the future.
view
- default View object for this Controller. Must be a name of preloaded view, an object with arender
method, or a CODEREF receiving hashref and returning a list of two scalars (content and content-type).[DEPRECATED] Use
-view
instead, meaning is exactly the same.cache_ttl
- if set, set Expires: HTTP header accordingly.[EXPERIMENTAL] Name and semantics MAY change in the future.
default
- a\%hash
of values that will be added to results EVERY time the handler returns. Consider usingneaf default ...
below if you need to append the same values to multiple paths.override
=> 1 - replace old route even if it exists. If not set, route collisions causes exception. Use this if you know better.This still issues a warning.
[EXPERIMENTAL] Name and meaning may change in the future.
tentative
=> 1 - if route is already defined, do nothing. If not, allow to redefine it later.[EXPERIMENTAL] Name and meaning may change in the future.
description
- just for information, has no action on execution. This will be displayed if application called with --list (see MVC::Neaf::CLI).public
=> 0|1 - a flag just for information. In theory, public endpoints should be searchable from the outside while non-public ones should only be reachable from other parts of application. This is not enforced whatsoever.
Also, any number of dash-prefixed keys MAY be present. This is the same as putting them into default
hash.
[NOTE] For some reason ability to add multicomponent paths like (foo => bar => \&code)
was added in the past, resulting in "/foo/bar" => \&code
.
It was never documented, will issue a warning, and will be removed for good it v.0.25.
static()
neaf static => '/path' => $local_path, %options;
neaf static => '/other/path' => [ "content", "content-type" ];
$neaf->static( $req_path => $file_path, %options )
Serve static content located under $file_path
. Both directories and single files may be added.
If an arrayref of [ $content, $content_type ]
is given as second argument, serve that content from memory instead.
%options may include:
buffer
=>nnn
- buffer size for reading/writing files. Default is 4096. Smaller values may be set, but are NOT recommended.cache_ttl
=>nnn
- if given, files below the buffer size will be stored in memory forcache_ttl
seconds.[EXPERIMENTAL] Cache API is not yet established.
allow_dots => 1|0 - if true, serve files/directories starting with a dot (.git etc), otherwise give a 404.
[EXPERIMENTAL]
dir_index => 1|0 - if true, generate index for a directory; otherwise a 404 is returned, and deliberately so, for security reasons.
[EXPERIMENTAL]
dir_template - specify template for directory listing (with images etc). A sane default is provided.
[EXPERIMENTAL]
view - specify view object for rendering directory template. By default a localized
TT
instance is used.[EXPERIMENTAL] Name MAY be changed (dir_view etc).
override - override the route that was here before. See
route
above.tentative - don't complain if replaced later.
description - comment. The default is "Static content at $directory"
public => 0|1 - a flag just for information. In theory, public endpoints should be searchable from the outside while non-public ones should only be reachable from other parts of application. This is not enforced whatsoever.
See MVC::Meaf::X::Files for implementation.
File type detection is based on extentions so far, and the list is quite short. This MAY change in the future. Known file types are listed in %MVC::Neaf::X::Files::ExtType
hash. Patches welcome.
It is probably a bad idea to serve files in production using a web application framework. Use a real web server instead. Not need to set up one for merely testing icons/js/css, though.
set_path_defaults()
neaf default => '/path' => \%values;
$neaf->set_path_defaults ( '/path' => \%values );
Use given values as defaults for ANY handler below given path. A value of '/' means global.
Longer paths override shorter ones; route-specific defaults override path-base defaults; explicit values returned from handler override all or the above.
For example,
neaf default '/api' => { view => 'JS', version => My::Model->VERSION };
add_hook()
neaf "phase" => sub { ... }, path => [ ... ], exclude => [ ... ];
$neaf->add_hook ( phase => CODEREF, %options );
Set hook that will be executed on a given request processing phase.
Valid phases include:
pre_route [die]
pre_logic [die]
pre_content
pre_render [die]
pre_reply [reverse]
pre_cleanup [reverse]
See "REQUEST PROCESSING PHASES AND HOOKS" below for detailed discussion of each phase.
The CODEREF receives one and only argument - the $request
object. Return value is ignored, see explanation below.
Use $request
's session
, reply
, and stash
methods for communication between hooks.
Dying in a hook MAY cause interruption of request processing or merely a warning, depending on the phase.
%options may include:
path => '/path' - where the hook applies. Default is '/'. Multiple locations may be supplied via
[ /foo, /bar ...]
exclude => '/path/skip' - don't apply to these locations, even if under '/path'. Multiple locations may be supplied via
[ /foo, /bar ...]
method => 'METHOD' || [ list ] List of request HTTP methods to which given hook applies.
prepend => 0|1 - all other parameters being equal, hooks will be executed in order of adding. This option allows to override this and run given hook first. Note that this does NOT override path bubbling order.
alias()
neaf alias $newpath => $oldpath
$neaf->alias( $newpath => $oldpath )
Create a new name for already registered route. The handler will be executed as is, but the hooks and defaults will be re-calculated. So be careful.
[CAUTION] As of 0.21, alias
does NOT follow tentative/override switches. This needs to be fixed in the future.
load_view()
neaf view => 'name' => 'Driver::Class' => %options;
$neaf->load_view( $name, $object || coderef || ($module_name, %options) )
Setup view under name $name
. Subsequent requests with -view = $name
would be processed by that view object.
Use get_view
to fetch the object itself.
if object is given, just save it.
if module name + parameters is given, try to load module and create new() instance.
Short aliases
JS
,TT
, andDumper
may be used for correspondingMVC::Neaf::View::*
modules.if coderef is given, use it as a
render
method.
Returns the view object, NOT the calling Neaf object.
set_session_handler()
neaf session => $engine => %options
$neaf->set_session_handler( %options )
Set a handler for managing sessions.
If such handler is set, the request object will provide session()
, save_session()
, and delete_session()
methods to manage cross-request user data.
% options may include:
engine
(required in method form, first argument in DSL form) - an object providing the storage primitives;ttl
- time to live for session (default is 0, which means until browser is closed);cookie
- name of cookie storing session id. The default is "session".view_as
- if set, add the whole session into data hash under this name before view processing.
The engine MUST provide the following methods (see MVC::Neaf::X::Session for details):
session_ttl (implemented in MVC::Neaf::X::Session);
session_id_regex (implemented in MVC::Neaf::X::Session);
get_session_id (implemented in MVC::Neaf::X::Session);
create_session (implemented in MVC::Neaf::X::Session);
save_session (required);
load_session (required);
delete_session (implemented in MVC::Neaf::X::Session);
add_form()
neaf form => name => \%spec, engine => ...
add_form( name => $validator )
Create a named form for future query data validation via $request->form("name")
. See "form" in MVC::Neaf::Request.
The $validator
is one of:
An object with
validate
method accepting one\%hashref
argument (the raw form data).A CODEREF accepting the same argument.
Whatever is returned by validator is forwarded into the controller.
Neaf comes with a set of predefined validator classes that return a convenient object that contains collected valid data, errors (if any), and an is_valid flag.
The engine
parameter of the functional form has predefined values Neaf
(the default), LIVR
, and Wildcard
(all case-insensitive) pointing towards MVC::Neaf::X::Form, MVC::Neaf::X::Form::LIVR, and MVC::Neaf::X::Form::Wildcard, respectively.
You are encouraged to use LIVR
(See Validator::LIVR and LIVR grammar) for anything except super-basic regex checks.
If an arbitrary class name is given instead, new()
will be called on that class with \%spec ref as first parameter.
Consider the following script:
use MVC::Neaf qw(:sugar);
neaf form => my => { foo => '\d+', bar => '[yn]' };
get '/check' => sub {
my $req = shift;
my $in = $req->form("my");
return $in->is_valid ? { ok => $in->data } : { error => $in->error };
};
neaf->run
And by running this one gets
bash$ curl http://localhost:5000/check?bar=xxx
{"error":{"bar":"BAD_FORMAT"}}
bash$ curl http://localhost:5000/check?bar=y
{"ok":{"bar":"y"}}
bash$ curl http://localhost:5000/check?bar=yy
{"error":{"bar":"BAD_FORMAT"}}
bash$ curl http://localhost:5000/check?foo=137\&bar=n
{"ok":{"bar":"n","foo":"137"}}
bash$ curl http://localhost:5000/check?foo=leet
{"error":{"foo":"BAD_FORMAT"}}
set_error_handler()
neaf 403 => sub { ... }
$neaf->set_error_handler ( $status => CODEREF( $request, %options ) )
Set custom error handler.
Status must be a 3-digit number (as in HTTP). Other allowed keys MAY appear in the future.
The following options will be passed to coderef:
status - status being returned;
caller - file:line where the route was set up; This is DEPRECATED and will silently disappear around version 0.25
error - exception, an MVC::Neaf::Exception object.
The coderef MUST return an unblessed hash just like a normal controller does.
In case of exception or unexpected return format default JSON-based error will be returned.
Also available as set_error_handler( status => \%hash )
.
This is a synonym to sub { +{ status => $status, ... } }
.
on_error()
$neaf->on_error( sub { my ($request, $error) = @_ } )
Install custom error handler for a dying controller. Neaf's own exceptions, redirects, and die \d\d\d
status returns will NOT trigger it.
E.g. write to log, or something.
Return value from this callback is ignored. If it dies, only a warning is emitted.
load_resources()
$neaf->load_resources( $file_name || \*FH )
Load pseudo-files from a file, like templates or static files.
The format is as follows:
@@ [TT] main.html
[% some_tt_template %]
@@ /favicon.ico format=base64 type=png
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAL
GPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hS<....more encoded lines>
This is obviously stolen from Mojolicious, in a slightly incompatible way.
If view is specified in brackets, preload template. A missing view is skipped, no error.
Otherwise file is considered a static resource.
Extra options may follow file name:
type=ext | mime/type
format=base64
Unknown options are skipped. Unknown format value will cause exception though.
[EXPERIMENTAL] This method and exact format of data is being worked on.
run()
neaf->run;
$neaf->run();
Run the application. This SHOULD be the last statement in your application's main file.
If called in void context, assumes execution as CGI
and prints results to STDOUT
. If command line options are present at the moment, enters debug mode via MVC::Neaf::CLI. Call perl yourapp.pl --help
for more.
Otherwise returns a PSGI
-compliant coderef. This will also happen if you application is require
'd, meaning that it returns a true value and actually serves nothing until run()
is called again.
Running under mod_perl requires setting a handler with MVC::Neaf::Request::Apache2.
EXPORTED HELPER FUNCTIONS
Neaf tries hard to keep user's namespace clean, however, some helper functions are needed.
neaf_err $error
Rethrow Neaf's internal exceptions immediately, do nothing otherwise.
If no argument if given, acts on current $@
value.
Currently Neaf uses exception mechanism for internal signalling, so this function may be of use if there's a lot of eval
blocks in the controller. E.g.
use MVC::Neaf qw(neaf_err);
# somewhere in controller
eval {
check_permissions()
or $req->error(403);
do_something()
and $req->redirect("/success");
};
if (my $err = $@) {
neaf_err;
# do the rest of error handling
};
Or alternatively with Try::Tiny:
try {
...
} catch {
neaf_err $_;
# proceed with normal error handling
}
See also MVC::Neaf::Exception.
neaf action => @options;
Forward @options
to the underlying method of the default instance. Possible actions include:
view -
load_view
session -
set_session_handler
default -
set_path_defaults
alias -
alias
static -
static
route -
route
Don't do this, use
any
orget + post + ...
instead.hook -
add_hook
Don't do this, use phase name instead.
error -
set_error_handler
Don't do this, use 3-digit error code instead.
DEVELOPMENT AND DEBUGGING METHODS
No more prototyped/exported functions below here.
run_test()
$neaf->run_test( \%PSGI_ENV, %options )
$neaf->run_test( "/path?parameter=value", %options )
Run a PSGI request and return a list of ($status, HTTP::Headers::Fast, $whole_content )
.
Returns just the content in scalar context.
Just as the name suggests, useful for testing only (it reduces boilerplate).
Continuation responses are supported, but will be returned in one chunk.
%options may include:
method - set method (default is GET)
cookie = \%hash - force HTTP_COOKIE header
header = \%hash - override some headers This gets overridden by type, cookie etc. in case of conflict
body = 'DATA' - force body in request
type - content-type of body
uploads - a hash of MVC::Neaf::Upload objects.
secure = 0|1 -
http
vshttps
override = \%hash - force certain data in
ENV
Gets overridden by all of the above.
get_routes()
$neaf->get_routes( $callback->(\%route_spec, $path, $method) )
Returns a 2-level hashref with ALL routes for inspection.
So $hash{'/path'}{'GET'} = { handler, expected params, description etc }
If callback is present, run it against route definition and append to hash its return value, but ONLY if it's true.
As of 0.20, route definitions are only protected by shallow copy, so be careful with them.
This SHOULD NOT be used by application itself.
set_forced_view()
$neaf->set_forced_view( $view )
If set, this view object will be user instead of ANY other view.
See load_view
.
Returns self.
INTERNAL METHODS
CAVEAT EMPTOR.
The following methods are generally not to be used, unless you want something very strange.
new()
MVC::Neaf->new(%options)
Constructor. Usually, instantiating Neaf is not required. But it's possible.
Options are not checked whatsoever.
handle_request()
$neaf->handle_request( MVC::Neaf::Request->new )
This is the CORE of this module. Should not be called directly - use run()
instead.
handle_request
really boils down to
my ($self, $req) = @_;
my $req->path =~ /($self->{GIANT_ROUTING_RE})/
or die 404;
my $route = $self->{ROUTES}{$1}{ $req->method }
or die 405;
my $reply_hash = $route->{CODE}->($req);
my $content = $reply_hash->{-view}->render( $reply_hash );
return [ $reply_hash->{-status}, [...], [ $content ] ];
The rest 200+ lines of it, split into 4 separate private functions, are running callbacks, handling corner cases, and substituting sane defaults.
get_view()
$neaf->get_view( "name", $lazy )
Fetch view object by name.
Uses load_view
( name => name ) if needed, unless $lazy flag is on.
This is for internal usage, mostly.
If set_forced_view
was called, return its argument instead.
get_form()
$neaf->get_form( "name" )
Fetch form named "name". No magic here. See "add_form".
REQUEST PROCESSING PHASES AND HOOKS
Hooks are subroutines executed during various phases of request processing. Each hook is characterized by phase, code to be executed, path, and method. Multiple hooks MAY be added for the same phase/path/method combination. ALL hooks matching a given route will be executed, either short to long or long to short (aka "event bubbling"), depending on the phase.
[CAUTION] Don't overuse hooks. This may lead to a convoluted, hard to follow application. Use hooks for repeated auxiliary tasks such as checking permissions or writing down statistics, NOT for primary application logic.
Hook return values are discarded, and deliberately so. In absence of an explicit return, Perl will interpret the last statement in the code as such. Therefore writers of hooks would have to be extremely careful to avoid breaking the execution chain. On the other hand, proper exception handling is required anyway for implementing any kind of callbacks.
As a rule of thumb, the following primitives should be used to maintain state across hooks and the main controller:
Use
session
if you intend to share data between requests.Use
reply
if you intend to render the data for the user.Use
stash
as a last resort for temporary, private data.
The following list of phases MAY change in the future. Current request processing diagram looks as follows:
[*] request created
. <- pre_route [no path] [can die]
|
* route - select handler
|
. <- pre_logic [can die]
[*] execute main handler
* apply path-based defaults - reply() is populated now
|
. <- pre_content
? checking whether content already generated
|\
| . <- pre_render [can die - template error produced]
| [*] render - -content is present now
|/
* generate default headers (content type & length, cookies, etc)
. <- pre_reply [path traversal long to short]
|
[*] headers sent out, no way back!
* output the rest of reply (if -continue specified)
* execute postponed actions (if any)
|
. <- pre_cleanup [path traversal long to short] [no effect on headers]
[*] request destroyed
pre_route
Executed AFTER the request has been received, but BEFORE the path has been resolved and handler found.
Dying in this phase stops both further hook processing and controller execution. Instead, the corresponding error handler is executed right away.
Options path
and exclude
are not available on this stage.
May be useful for mangling path. Use $request->set_path($new_path)
if you need to.
pre_logic
Executed AFTER finding the correct route, but BEFORE processing the main handler code (one that returns \%hash
, see route
above).
Hooks are executed in order, shorted paths to longer. reply
is not available at this stage, as the controller has not been executed yet.
Dying in this phase stops both further hook processing and controller execution. Instead, the corresponding error handler is executed right away.
[EXAMPLE] use this hook to produce a 403 error if the user is not logged in and looking for a restricted area of the site:
neaf pre_logic => sub {
my $request = shift;
$request->session->{user_id} or die 403;
}, path => '/admin', exclude => '/admin/static';
pre_content
This hook is run AFTER the main handler has returned or died, but BEFORE content rendering/serialization is performed.
reply()
hash is available at this stage.
Dying is ignored, only producing a warning.
pre_render
This hook is run BEFORE content rendering is performed, and ONLY IF the content is going to be rendered, i.e. no -content
key set in response hash on previous stages.
Dying will stop rendering, resulting in a template error instead.
pre_reply
This hook is run AFTER the headers have been generated, but BEFORE the reply is actually sent to client. This is the last chance to amend something.
Hooks are executed in REVERSE order, from longer to shorter paths.
reply()
hash is available at this stage.
Dying is ignored, only producing a warning.
pre_cleanup
This hook is run AFTER all postponed actions set up in controller (via -continue
etc), but BEFORE the request object is actually destroyed. This can be useful to free some resource or write statistics.
The client connection MAY be closed at this point and SHOULD NOT be relied upon.
Hooks are executed in REVERSE order, from longer to shorter paths.
Dying is ignored, only producing a warning.
MORE EXAMPLES
See the examples directory in this distro or at https://github.com/dallaylaen/perl-mvc-neaf/tree/master/example for complete working examples. These below are just code snippets.
All of them are supposed to start and end with:
use strict;
use warnings;
use MVC::Neaf qw(:sugar);
# ... snippet here
neaf->run;
Static content
neaf->static( '/images' => "/local/images" );
neaf->static( '/favicon.ico' => "/local/images/icon_32x32.png" );
neaf->static( '/robots.txt' => [ "Disallow: *\n", "text/plain "] );
Form submission
# You're still encouraged to use LIVR for more detailed validation
my %profile = (
name => [ required => '\w+' ],
age => '\d+',
);
neaf form my_form => \%profile;
get+post '/submit' => sub {
my $req = shift;
my $form = $req->form( "my_form" );
if ($req->is_post and $form->is_valid) {
my $id = do_something( $form->data );
$req->redirect( "/result/$id" );
};
return {
-template => 'form.tt',
errors => $form->error,
fill_values => $form->raw,
};
};
Adding JSONP callbacks
neaf pre_render => sub {
my $req = shift;
$req->reply->{-jsonp} = $req->param("callback" => '.*');
# Even if you put no restriction here, no XSS comes through
# as JS View has its own default filter
}, path => '/js/api';
More examples to follow as usage (hopefully) accumulates.
FOUNDATIONS OF NEAF
Data in, data out.
A function should receive an argument and return a value or die. Everything else should be confined within the function. This applies to both Neaf's own methods and the user code.
A notable exception is the session mechanism which is naturally stateful and thus hard to implement in functional style.
Sane defaults.
Everything can be configured, nothing needs to be.
TT
view needs work in this respect.It's not software unless you can run it.
Don't rely on a specific server environment. Be ready to run as a standalone program or inside a test script.
Trust nobody.
Validate incoming data. This is not yet enforced for HTTP headers and body.
Unicode inside the perimeter.
This is not yet implemented (but planned) for body and file uploads because these may well be binary data.
DEPRECATED METHODS
Some methods become obsolete during Neaf development. Anything that is considered deprecated will continue to be supported for at least three minor versions after official deprecation and a corresponding warning being added.
Please keep an eye on Changes
though.
Here is the list of such methods, for the sake of completeness.
$neaf->pre_route( sub { my $req = shift; ... } )
Use
$neaf->add_hook( pre_route => \&hook )
instead. Hook signature & meaning is exactly the same.$neaf->error_template( { param => value } )
Use "set_error_handler" aka
neaf \d\d\d => sub { ... }
instead.$neaf->set_default ( key => value, ... )
Use
MVC::Neaf->set_path_defaults( '/', { key => value, ... } );
as a drop-in replacement.$neaf->server_stat ( MVC::Neaf::X::ServerStat->new( ... ) )
Record server performance statistics during run.
The interface of
MVC::Neaf::X::ServerStat
is as follows:my $stat = MVC::Neaf::X::ServerStat->new ( write_threshold_count => 100, write_threshold_time => 1, on_write => sub { my $array_of_arrays = shift; foreach (@$array_of_arrays) { # @$_ = (script_name, http_status, # controller_duration, total_duration, start_time) # do something with this data warn "$_->[0] returned $_->[1] in $_->[3] sec\n"; }; }, );
on_write will be executed as soon as either count data points are accumulated, or time is exceeded by difference between first and last request in batch.
Returns self.
[DEPRECATED] Just use pre_route/pre_reply/pre_cleanup hooks if you need to gather performance statistics.
BUGS
This software is still in BETA stage.
Test coverage is maintained at >80% currently, but who knows what lurks in the other 20%.
See the TODO
file in this distribution for a vague roadmap.
Please report any bugs or feature requests to https://github.com/dallaylaen/perl-mvc-neaf/issues.
Alternatively, email them to bug-mvc-neaf at rt.cpan.org
, or report through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=MVC-Neaf.
Feedback and/or critique are welcome.
SUPPORT
Feel free to email the author to get instant help!
You can find documentation for this module with the perldoc
command:
perldoc MVC::Neaf
perldoc MVC::Neaf::Request
You can also look for information at:
MetaCPAN: https://metacpan.org/pod/MVC::Neaf
RT
: CPAN's request tracker (report bugs here)AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
SEE ALSO
The Kelp framework has very similar concept.
Neaf has a lot of similarities to Mojolicious::Lite, initially unintentional.
ACKNOWLEDGEMENTS
Ideas were shamelessly stolen from Catalyst, Dancer, PSGI, and sinatra.rb.
CGI was used heavily in the beginning of development, though Neaf was PSGI
-ready from the start.
Thanks to Eugene Ponizovsky for introducing me to the MVC concept.
Thanks to Alexander Kuklev for early feedback and great insights about pure functions and side effects.
Thanks to Akzhan Abdullin for driving me towards proper hooks model.
Thanks to Cono for early feedback and feature proposals.
Thanks to Alexey Kuznetsov for requesting REST support and thus adding of multiple methods for the same path.
LICENSE AND COPYRIGHT
Copyright 2016-2017 Konstantin S. Uvarin khedin@cpan.org
.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.