CGI-Snapp-Dispatch

 view release on metacpan or  search on metacpan

lib/CGI/Snapp/Dispatch.pm  view on Meta::CPAN

package CGI::Snapp::Dispatch;

use strict;
use warnings;

use Carp;

use CGI::PSGI;

use Class::Load ':all';

use HTTP::Exception;

use Log::Handler;

use Moo;

use Try::Tiny;

has logger =>
(
	is       => 'rw',
	default  => sub{return ''},
	required => 0,
);

has return_type =>
(
	is       => 'rw',
	default  => sub{return 0},
	required => 0,
);

our $VERSION = '2.00';

# --------------------------------------------------

sub as_psgi
{
	my($self, @args)   = @_;

	$self -> log(debug => 'as_psgi(...)');

	croak "Parameter \@args to dispatch() must be a hashref or a hash\n" if ( ($#args > 0) && ($#args % 2 != 1) );

	my($options) = ref $args[0] eq 'HASH' ? $args[0] : {@args};
	my($args)    = $self -> _merge_args($options);

	croak "Missing dispatch table, or it's not an arrayref\n" if (! $$args{table} || ref $$args{table} ne 'ARRAY');

	my($output);

	return
		sub
		{
			my($env)         = shift @_;
			my($http_method) = $$env{REQUEST_METHOD};
			my($named_args)  = $self -> _parse_path($http_method, $self -> _clean_path($$env{PATH_INFO}, $args), $$args{table});

			HTTP::Exception -> throw(404, status_message => 'Not Found') if (! $$named_args{app});
			HTTP::Exception -> throw(400, status_message => "Invalid characters in run mode name '$$named_args{rm}'") if ($$named_args{rm} && ($$named_args{rm} !~ m/^([a-zA-Z_][\w\']+)$/) );

			# If _prepare() croaks, error number is 404.
			# If run() croaks, error number is 500,
			# because the error message will not match /^\d+$/.

			try
			{
				my($module, $rm, $args_to_new) = $self -> _prepare($http_method, $args, $named_args);
				$$args_to_new{_psgi}           = 1; # Required.
				$$args_to_new{QUERY}           = CGI::PSGI -> new($env) if (! $$args_to_new{QUERY});
				my($app)                       = $module -> new(%$args_to_new);

				$app -> mode_param(sub {return $rm}) if ($rm);

				$output = $app -> run;
			}
			catch

lib/CGI/Snapp/Dispatch.pm  view on Meta::CPAN

the new code behave in an identical fashion to the corresponding code in L<CGI::Application::Dispatch>.

Also, the re-write allowed me to support a version of L</dispatch(@args)> which accepts a hashref, not just a hash.
The same flexibility has been added to L</as_psgi(@args)>.

=head3 No special code for Apache, mod_perl or plugins

I suggest that sort of stuff is best put in sub-classes.

=head3 Unsupported features

=over 4

=item o dispatch_path()

Method dispatch_path() is not provided. For L<CGI> scripts, the code in dispatch() accesses $ENV{PATH_INFO} directly,
whereas for L<PSGI|http://plackperl.org/> scripts, as_psgi() accesses the L<PSGI|http://plackperl.org/> environment
hashref $$env{PATH_INFO}.

=back

=head3 Enhanced features

L</new()> can take extra parameters:

=over 4

=item o return_type

Note: I<return_type> is ignored by L</as_psgi(@args)>.

=back

=head3 This module uses Class::Load to try loading your application's module

L<CGI::Application::Dispatch> uses:

	eval "require $module";

whereas CGI::Snapp::Dispatch uses 2 methods from L<Class::Load>:

	try_load_class $module;
	croak 404 if (! is_class_loaded $module);

For L<CGI> scripts, the 404 (and all other error numbers) is handled by sub _http_error(), whereas for
L<PSGI|http://plackperl.org/> scripts, the code throws errors of type L<HTTP::Exception>.

=head3 Reading an error document from a file

L<CGI::Application::Dispatch> always prepends $ENV{DOCUMENT_ROOT} to the file name.
Unfortunately, this means that when $ENV{DOCUMENT_ROOT} is not set, File::Spec prepends a '/' to the file name.
So, an I<error_document> of '<x.html' becomes '/x.html'.

This module only prepends $ENV{DOCUMENT_ROOT} if it is not empty. Hence, with an empty $ENV{DOCUMENT_ROOT},
an I<error_document> of '<x.html' becomes 'x.html'.

See sub _parse_error_document() and t/args.t test_26().

=head3 Handling of exceptions

L<CGI::Application::Dispatch> uses a combination of eval and L<Try::Tiny>, together with L<Exception::Class>.
Likewise, L<CGI::Application::Dispatch::PSGI> uses the same combination, although without L<Exception::Class>.

CGI::Snapp::Dispatch just uses L<Try::Tiny>. This applies both to CGI scripts and PSGI scripts.
For L<CGI> scripts, errors are handled by sub _http_errror(). For L<PSGI|http://plackperl.org/> scripts, the code
throws errors of type L<HTTP::Exception>.

=head2 How does CGI::Snapp parse the path info?

Firstly, the path info is split on '/' chars. Hence /module_name/mode1 gives us ('', 'module_name', 'mode1').

The value 'module_name' is passed to L</translate_module_name($name)>. In this case, the result is 'Module::Name'.

You are free to override L</translate_module_name($name)> to customize it.

After that, the I<prefix> option's value, if any, is added to the front of 'Module::Name'. See L</dispatch_args($args)> for
more about I<prefix>.

FInally, 'mode1' becomes the name of the run mode.

Remember from the docs for L<CGI::Snapp>, that this is the I<name> of the run mode, but is not necessarily the name
of the method which will be run. The code in your sub-class of L<CGI::Snapp> can map run mode names to method
names.

For instance, a statement like:

	$self -> run_modes({rm_name_1 => 'rm_method_1', rm_name_2 => 'rm_method_2'});

in (probably) sub setup(), shows how to separate run mode names from method names.

=head2 What is the structure of the dispatch table?

Sometimes it's easiest to explain with an example, so here you go:

	CGI::Snapp::Dispatch -> new -> dispatch # Note the new()!
	(
		args_to_new =>
		{
			PARAMS => {big => 'small'},
		},
		default => '/app',
		prefix  => 'MyApp',
		table   =>
		[
			''                         => {app => 'Blog', rm => 'recent'},
			'posts/:category'          => {app => 'Blog', rm => 'posts'},
			':app/:rm/:id'             => {app => 'Blog'},
			'date/:year/:month?/:day?' =>
			{
				app         => 'Blog',
				rm          => 'by_date',
				args_to_new => {PARAMS => {small => 'big'} },
			},
		]
	);

Firstly note, that besides passing this structure into L</dispatch(@args)>, you could sub-class L<CGI::Snapp::Dispatch>
and design L</dispatch_args($args)> to return exactly the same structure.

OK. The components, all of which are optional, are:

=over 4

=item o args_to_new => $hashref



( run in 1.624 second using v1.01-cache-2.11-cpan-39bf76dae61 )