The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MVC::Neaf - Not Even A Framework for very simple web apps.

OVERVIEW

Neaf [ni:f] stands for Not Even An (MVC) Framework.

It is made for lazy people without an IDE.

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 reduced to just one function which receives a <MVC::Neaf::Request> object containing all it needs to know and returns a simple \%hashref which is forwarded to View.

Multiple controllers can be set up in the same application under different paths.

Please see the examples directory in this distribution which 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 MVC::Neaf;

    MVC::Neaf->route( "/app" => sub {
        my $req = shift;

        my $name = $req->param( name => qr/[\w\s]+/, "Yet another perl hacker" );

        return {
            -template => \"Hello, [% name %]",
            -type     => "text/plain",
            name      => $name,
        };
    });
    MVC::Neaf->run;

CREATING AN APPLICATION

THE CONTROLLER

The Controller sub receives an MVC::Neaf::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 "Not Found" right away.

Much like in Dancer or Kelp, multiple controllers may be configured under different paths using the route( path => CODEREF ); method discussed below.

THE REQUEST

The Request object is similar to the OO interface 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 qr/.*/ if you know better.

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 proverties 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 which control app's behaviour.

-Note -that -dash-prefixed -options -look -antique even to the author of this writing. They smell like Tk and CGI. They feel so 90's! However, it looks like a meaningful 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 which receives the Request object. It will be executed after the headers and pre-generated content are served to the client, and may use $req->write( $data ) and $req->close to write more data.

  • -jsonp - Used by JS view module as a callback name to produce a jsonp response. Callback is ignored unless it is a set of identifiers separated by dots, for security reasons.

  • -location - HTTP Location: header.

  • -status - HTTP status (200, 404, 500 etc). Default is 200 if the app managed to live through, and 500 if it died.

  • -template - Set template name for TT (Template-based view).

  • -type - Content-type HTTP header. View module may set this parameter if unset. Default: "text/html".

  • -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 by MVC::Neaf->load_view( "name" = $your_view );>. (See below).

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.

APPLICATION API

These methods are generally called during the setup phase of the application. They have nothing to do with serving the request.

route( path => CODEREF, %options )

Creates a new route in the application. Any incoming request to uri starting with /path (/path/something/else too, but NOT /pathology) will now be directed to CODEREF.

Longer paths are GUARANTEED to be checked first.

Dies if same route is given twice.

Exactly one leading slash will be prepended no matter what you do. (path, /path and /////path are all the same).

%options may include:

  • description - just for information, has no action on execution.

  • method - list of allowed HTTP methods - GET, POST & so on.

  • view - default View object for this Controller. Must be an object with a render methods, or a CODEREF receiving hash and returning a list of two scalars.

alias( $newpath => $oldpath )

Create a new name for already registered route. The handler will be executed as is, but new name will be reflected in Request->path.

Returns self.

static( $req_path => $file_path, %options )

Serve static content located under $file_path.

File type detection is based on extention. This MAY change in the future. Known file types are listed in %MVC::Neaf::ExtType hash. Patches welcome.

%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 for cache_ttl seconds. EXPERIMENTAL. Cache API is not yet established.

Generally it is probably a bad idea to serve files in production using a web application framework. Use a real web server instead.

However, this method may come in handy when testing the application in standalone mode, e.g. under plack web server. This is the intended usage.

pre_route( sub { ... } )

Mangle request before serving it. E.g. canonize uri or read session cookie.

If the sub returns a MVC::Neaf::Request object in scalar context, it is considered to replace the original one. It looks like it's hard to return an unrelated Request by chance, but beware!

load_view( $view_name, [ $object || $module_name, %options ] )

Return a cached View object named $view_name. This will be called whenever the app returns { -view => "foo" }.

If no such object was found, attempts to add new object to cache:

  • if object is given, just saves it.

  • if module name + parameters is given, tries to load module and create new() instance.

  • as a last resort, loads stock view: TT, JS, or Dumper. Those are prefixed with MVC::Neaf::View::.

If set_forced_view was called with an argument, return THAT view instance instead of all above.

So the intended usage is as follows:

    # in the app initialisation section
    # this can be omitted when using TT, JS, or Dumper as view.
    MVC::Neaf->load_view( foo => TT, INCLUDE_PATH => "/foo/bar" );
        # Short alias for MVC::Neaf::View::TT

    # in the app itself
    return {
        ...
        -view => "foo", # triggers a second call with 1 parameter "foo"
    };

NOTE Some convoluted logic here, needs rework.

set_default ( key => value, ... )

Set some default values that would be appended to data hash returned from any controller on successful operation. Controller return always overrides these values.

Returns self.

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) - 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);

server_stat ( MVC::Neaf::X::ServerStat->new( ... ) )

Record server performance statistics during run.

The interface of 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.

on_error( sub { my ($req, $err) = @_ } )

Install custom error handler (e.g. write to log).

error_template ( status => { ... } )

Set custom error handler.

Status must be either a 3-digit number (as in HTTP), or "view". Other allowed keys MAY appear in the future.

The second argument follows the same rules as controller return.

The following fields are expected to exist if this handler fires:

  • -status - defaults to actual status (e.g. 500);

  • caller - array with the point where MVC::Neaf->route was set up;

  • error - in case of exception, this will be the error.

Returns self.

run()

Run the applicaton. This should be in the last statement in your appication main file.

If called in void context, assumes CGI is being used and instantiates MVC::Neaf::Request::CGI. If command line options are present at the time, enters debug mode via MVC::Neaf::CLI.

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 FUNCTIONS

Currently only one function is exportable:

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
    };

DEVELOPMENT AND DEBUGGING METHODS

get_routes

Returns a hash with ALL routes for inspection. This should NOT be used by application itself.

set_forced_view( $view )

If set, this view object will be user instead of ANY other view.

See load_view.

Returns self.

INTERNAL API

CAVEAT EMPTOR.

The following methods are generally not to be used, unless you want something very strange.

new(%options)

Constructor. Usually, instantiating Neaf is not required. But it's possible.

Options are not checked whatsoever.

Just in case you're curious, $MVC::Neaf::Inst is the default instance that handles MVC::Neaf->... requests.

handle_request( MVC::Neaf::request->new )

This is the CORE of this module. Should not be called directly - use run() instead.

BUGS

Lots of them, this software is still under heavy development.

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. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc MVC::Neaf

You can also look for information at:

ACKNOWLEDGEMENTS

LICENSE AND COPYRIGHT

Copyright 2016 Konstantin S. Uvarin.

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.