What is the difference between two routes in Mojolicious? - perl

I just create two routes:
$r->any ( '/api/v:api', [ api => qr/\d+/ ], { api => 1 } )->partial( 1 );
$r->under( '/api/v:api', [ api => qr/\d+/ ], { api => 1 } );
It seems both work same.
What is the difference under hood between them?
UPD
I have noticed that for first case (->any) link to static files are moved to /api/v1. This is noticeable when exception happen. mojo/debug template tries to load static files from /api/v1/... path and not /.... Why?

The under is called before anything else. You can place authentication code inside it.
use strict;
use warnings;
use Mojolicious::Lite;
under sub {
my $self = shift;
my $api_key = $self->req->url->to_abs->username;
my $api_secret = $self->req->url->to_abs->password;
if($api_key ne "Admin" && $api_secret ne "Password123") {
$self->res->headers->www_authenticate('Basic');
$self->render(text => 'Error: API-Credentials are wrong', status => 401);
# No routes will be executed
return undef;
}
# Authentication with success, execute other routes
return 1;
}
# Only executed if the url is http://Admin:Password123#127.0.0.1:3000
any '/' => sub {
my $self = shift;
my $date = Mojo::Date->new;
$self->render(text => $date, status => 200);
};
It's very important that you have to place under above the routines you want to protect.

Related

Pass value from one router to another using Mojolicious::Lite

from an ajax form this router foundname gets called, I need to process the value and pass it to another router, I can't figured it out how to do it, here is a sample of how I am trying:
#!/usr/bin/perl
use Mojolicious::Lite;
get '/foundname' => sub {
my $c = shift;
# Here I get the value from the form
my $name_on = $c->req->query_params->param('name');
if($name_on) {
# call another router and pass the name value to it
# It gives an error "Can't locate object method "get" ", I might not need to use "get", just don't know how to pass the value.
$c->get('/process_name')->to( searched => $name_on);
}
};
get '/process_name' => sub {
my $c = shift;
my $got_name = $c->req->query_params->param('searched');
...
};
Thank you!
You need to look up the routes through your Mojolicious::Routes object inside of your app. The name for the lookup is auto-generated by Mojolicious::Lite from the path-part of the URI, so /process_name has the name process_name.
You get back a Mojolicious::Routes::Route, which has a render method and you can pass your parameters along there.
use Mojolicious::Lite;
get '/foundname' => sub {
my $c = shift;
my $name_on = $c->req->query_params->param('name');
if( $name_on ) {
my $process_name = app->routes->lookup('process_name')->render( { searched => $name_on } );
$c->render( text => $process_name );
}
};
get '/process_name' => sub {
my $c = shift;
my $got_name = $c->req->query_params->param('searched');
$c->render( text => $got_name );
};
app->start;
When you curl this you get the parameter back as a response.
$ curl localhost:3000/foundname?name=foo
/process_name
However, this is probably not the right approach. If you want to implement business logic, you should not use internal or hidden routes for that. Remember that your application is still just Perl. You can write a sub and call that.
use Mojolicious::Lite;
get '/foundname' => sub {
my $c = shift;
my $name_on = $c->req->query_params->param('name');
if( $name_on ) {
my $got_name = process_name( $name_on );
$c->render( text => $got_name );
}
};
sub process_name {
my ( $got_name ) = #_;
# do stuff with $got_name
return uc $got_name;
};
app->start;
This will output
$ curl localhost:3000/foundname?name=foo
FOO
It's the more portable approach, as you can easily unit-test these functions. If you want to have $c, you have to pass it along. You also have the app keyword available in any sub you define.
For the original question, I would use
$c->redirect_to()
See this question for details on passing the variable over:
Passing arguments to redirect_to in mojolicious and using them in the target controller
======
But, I would look more into writing subs (as others have said).
If you have existing logic then you can wrap it in a helper or just toss the logic in a helper and call that.
helper('process_name'=> sub{
my $self,$args = #_;
# Do some logic with $args->{'name'}
return $something;
});
get '/foundname' => sub {
my $c = shift;
my $name_on = $c->req->query_params->param('name');
if( $name_on ) {
my $process_name = $c->process_name({name => $name_on});
$c->render( text => $process_name );
}else{
$c->redner(text => 'Error',status=>500);
}
};

What's the best practice for error handling with Catalyst::Controller::REST

I'm having a hard time trying to figure out a way to handle unexpected errors in my Catalyst::Controller::REST based API.
BEGIN { extends 'Catalyst::Controller::REST' }
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
### something happens here and it dies
}
If default_GET dies unexpectedly, the application standard status 500 error page is shown. I'd expect the REST library behind the controller to take control over it and show a JSON error (or whatever serialization response the REST request accepts).
Adding error control (with ie Try::Tiny) action by action is not an option. I'm looking to centralize all error handling. I've tried using an sub end action but that did not work.
sub error :Private {
my ( $self, $c, $code, $reason ) = #_;
$reason ||= 'Unknown Error';
$code ||= 500;
$c->res->status($code);
$c->stash->{data} = { error => $reason };
}
This is not a best practice. It's just how I would do it.
You can use Try::Tiny to catch the errors in your controller, and the helpers that Catalyst::Action::REST brings to send the appropriate response codes. It will take care of converting the response to the right format (i.e. JSON) for you.
But that still requires you to do it for each type of error. Basically it boils down to this:
use Try::Tiny;
BEGIN { extends 'Catalyst::Controller::REST' }
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
try {
# ... (there might be a $c->detach in here)
} catch {
# this is thrown by $c->detach(), so don't 400 in this case
return if $_->$_isa('Catalyst::Exception::Detach');
$self->status_bad_request( $c, message => q{Boom!} );
}
}
The methods for those kinds of responses are listed in Catalyst::Controller::REST under STATUS HELPERS. They are:
status_ok
status_created
status_accepted
status_no_content
status_multiple_choices
status_found
status_bad_request
status_forbidden
status_not_found
gone
status_see_other
status_moved
You can implement your own for the missing status1 by subclassing Catalyst::Controller::REST or by adding into its namespace. Refer to one of them for how they are constructed. Here's an example.
*Catalyst::Controller::REST::status_teapot = sub {
my $self = shift;
my $c = shift;
my %p = Params::Validate::validate( #_, { message => { type => SCALAR }, }, );
$c->response->status(418);
$c->log->debug( "Status I'm A Teapot: " . $p{'message'} ) if $c->debug;
$self->_set_entity( $c, { error => $p{'message'} } );
return 1;
}
If that is too tedious because you have a lot of actions, I suggest you make use of the end action like you intended. More to how that works a bit further below.
In that case, don't add the Try::Tiny construct to your actions. Instead, make sure that all of your models or other modules you are using throw good exceptions. Create exception classes for each of the cases, and hand the control of what is supposed to happen in which case to them.
A good way to do all this is to use Catalyst::ControllerRole::CatchErrors. It lets you define a catch_error method that will handle errors for you. In that method, you build a dispatch table that knows what exception should cause which kind of response. Also look at the documentation of $c->error as it has some valuable information here.
package MyApp::Controller::Root;
use Moose;
use Safe::Isa;
BEGIN { extends 'Catalyst::Controller::REST' }
with 'Catalyst::ControllerRole::CatchErrors';
__PACKAGE__->config(
json_options => { relaxed => 1, allow_nonref => 1 },
default => 'application/json',
map => { 'application/json' => [qw(View JSON)] },
);
sub default : Path : ActionClass('REST') { }
sub default_GET {
my ( $self, $c, $mid ) = #_;
$c->model('Foo')->frobnicate;
}
sub catch_errors : Private {
my ($self, $c, #errors) = #_;
# Build a callback for each of the exceptions.
# This might go as an attribute on $c in MyApp::Catalyst as well.
my %dispatch = (
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
);
# #errors is like $c->error
my $e = shift #errors;
# this might be a bit more elaborate
if (ref $e =~ /^MyAPP::Exception/) {
$dispatch{ref $e}->($e) if exists $dispatch{ref $e};
$c->detach;
}
# if not, rethrow or re-die (simplified)
die $e;
}
The above is a crude, untested example. It might not work like this exactly, but it's a good start. It would make sense to move the dispatching into an attribute of your main Catalyst application object (the context, $c). Place it in MyApp::Catalyst to do that.
package MyApp::Catalyst;
# ...
has error_dispatch_table => (
is => 'ro',
isa => 'HashRef',
traits => 'Hash',
handles => {
can_dispatch_error => 'exists',
dispatch_error => 'get',
},
builder => '_build_error_dispatch_table',
);
sub _build_error_dispatch_table {
return {
'MyApp::Exception::BadRequest' => sub {
$c->status_bad_request(message => $_[0]->message);
},
'MyApp::Exception::Teapot' => sub {
$c->status_teapot;
},
};
}
And then do the dispatching like this:
$c->dispatch_error(ref $e)->($e) if $c->can_dispatch_error(ref $e);
Now all you need is good exceptions. There are different ways to do those. I like Exception::Class or Throwable::Factory.
package MyApp::Model::Foo;
use Moose;
BEGIN { extends 'Catalyst::Model' };
# this would go in its own file for reusability
use Exception::Class (
'MyApp::Exception::Base',
'MyApp::Exception::BadRequest' => {
isa => 'MyApp::Exception::Base',
description => 'This is a 400',
fields => [ 'message' ],
},
'MyApp::Exception::Teapot' => {
isa => 'MyApp::Exception::Base',
description => 'I do not like coffee',
},
);
sub frobnicate {
my ($self) = #_;
MyApp::Exception::Teapot->throw;
}
Again, it would make sense to move the exceptions into their own module so you can reuse them everywhere.
This can be nicely extended I believe.
Also keep in mind that coupling the business logic or models too strongly to the fact that it's a web app is be bad design. I chose very speaking exception names because it's easy to explain that way. You might want to just more generic or rather less web centric names, and your dispatch thingy should take of actually mapping them. Otherwise it's too much tied to the web layer.
1) Yes, this is the plural. See here.

Rendering errors in proper format inside under on Mojolicious::Lite

I have an under statement that may generate and error (for instance, authentication error). I use content negotiation all over, and I'd like to return the error inside the under in a proper format. An example code:
under sub {
my $self = shift;
# Authenticated
my $token = $self->param('token') || '';
return 1 if $token eq '123456';
# Not authenticated
$self->respond_to(
json => {
json => { error => 'Invalid authentication token.' },
status => 401
},
text => {
text => 'Unauthorized.',
status => 401
}
);
return undef;
}
I can use render inside under, but respond_to won't work. Probably under work for that. But in that case, what should I do?
In Galileo I have an auth_fail helper which does something like this. While I work out an example, see that code (and the if_author and if_admin unders).
Ok here is an example, the trick turned out to be (at least for this mechanism) before you can redirect to your failure handler, you need to flash the format, which make it available to the next handler.
#!/usr/bin/env perl
use Mojolicious::Lite;
any '/fail' => sub {
my $self = shift;
$self->respond_to(
json => {
json => { error => 'Invalid authentication token.' },
status => 401
},
text => {
text => 'Unauthorized.',
status => 401
}
);
};
under sub {
my $self = shift;
# Authenticated
my $token = $self->param('token') || '';
return 1 if $token eq '123456';
# Not authenticated
$self->flash( format => $self->param('format') );
$self->redirect_to('fail');
return undef;
};
any '/private' => sub {
my $self = shift;
$self->respond_to(
json => {
json => { launch_codes => '9999999' },
},
text => {
text => 'Launch Code: 9999999',
}
);
};
app->start;

Is there a way to incorporate a Mojolicious::Controller with a MojoX::JSON::RPC::Service?

I'm working with zabbix and writing an interface to interact with the zabbix api. Since zabbix exposes a jsonrpc interface I decided to use MojoX::JSON::RPC::Service. The problem I'm running into is that I'm now faced with interacting with other services written using Mojolicious::Controllers where they're expecting a Mojolicious::Controller objects. There is no Mojolicious::Controller object available when using MojoX::JSON::RPC::Service.
my $obj = $rpc_obj->register(
'retrieve',
sub {
# do stuff
},
{ with_mojo_tx => 1 }
);
That registers a route called 'retrieve'. When the route is accessed and the anonymous
subroutine is run, the subroutine has access only to the Mojo::Transaction::HTTP object.
So, I don't have access to the app for using plugins and the stash and other things that Mojolicious offers. Is there a way to incorporate Mojolicious::Controller with MojoX::JSON::RPC::Service?
I could rewrite it to use a Mojolicious::Controller but I'm trying to avoid that if possible.
You should consider to use MojoX::JSON::RPC::Dispatcher, as it inherits all attributes from Mojolicious::Controller
SYNOPSIS:
# lib/your-application.pm
use base 'Mojolicious';
use MojoX::JSON::RPC::Service;
sub startup {
my $self = shift;
my $svc = MojoX::JSON::RPC::Service->new;
$svc->register(
'sum',
sub {
my #params = #_;
my $sum = 0;
$sum += $_ for #params;
return $sum;
}
);
$self->plugin(
'json_rpc_dispatcher',
services => {
'/jsonrpc' => $svc
}
);
}
[UPDATE]
Hook Example:
package Application;
use Mojo::Base 'Mojolicious';
use Application::Firewall;
# This method will run once at server start
sub startup {
my $app = shift;
# Routes
my $r = $app->routes;
# Validation Middleware
$app->hook(
before_dispatch => sub {
my $self = shift;
my $data = $self->req->params->to_hash;
my $vald = Application::Firewall->new($data);
# mojolicious bug at the time of coding
delete $data->{""} if defined $data->{""};
$app->{input} = {};
if ( keys %{$data} ) {
# validation the submitted data
unless ( $vald->validate( keys %{$data} ) ) {
$self->render(
text => join( "", #{ $vald->errors } ),
status => 500
);
return 0;
}
# Helper (optional)
# create a helper to get access to the transformed data
# if your validation rules had/has filters
# Note! due to a bug in the params function we must do this
# (... i know, so what)
$app->{input} = {
map { $_ => $vald->{fields}->{$_}->{value} }
keys %{ $vald->{fields} }
};
}
return 1;
}
);
# Normal route to controller * with auto-matic input validation *
$r->route('/')->to(
cb => sub {
my $self = shift;
$self->render(
text => 'Hello ' . ( $app->{input}->{foobar} || 'World' ),
status => 200
);
}
);
}
1;

Is it possible to overwrite the response data in a Mojo::Message::Response?

In a Mojolicious app I have a route in my Controller code like the following:
/account/:id/users
The /account/:id part of the route has the following data in it when I get to the
users part of the chain:
$VAR1 = {
'signup_ip' => '172.17.5.146',
'z_id' => '382C58D8-529E-11E1-BDFB-A44585CCC763',
'signup_date' => '2012-03-12T12:11:10Z',
'name' => 'Some Cool Account Name',
'users' => [
{
'user_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763'
}
],
'account_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763',
};
In the users part of the chain I'm getting the above hash using
$self->tx->res->content->get_body_chunk(0)
sub users {
my $self = shift;
my $user_list = from_json( $self->tx->res->content->get_body_chunk(0) );
$self->respond_to( json => $user_list->{users} );
}
The problem I'm having is that I want to overwrite the response with only
the users arrayref. The code above in sub users(){} doesn't do that. That is,
when I dump the result in the test, I still getting the entire hash.
The $user_list is the arrayref I'm looking for in users() but I'm unable to overwrite it.
Anyone have an idea how to do that?
Hrm I think I put my previous answer in the wrong place. So here it is:
In the application I added the following routes:
my $base = $r->bridge('/account/:id')->to('account#read');
$base->route('/')->via('get')->to('account#index');
$base->route('/users')->via('get')->to('account#users');
In Acount.pm
sub read {
my $self = shift;
# do stuff
$self->stash->{account} = $data; # set the stash
return 1; #return 1. Don't render.
}
sub index {
my $self = shift;
my $data = $self->stash('account'); #get the stash
$self->render_json( $data );
}
sub users {
my $self = shift;
# do stuff
my $data = $self->stash('account');
$self->render_json( $data );
}
Doing this sets the result of /account/:id into the stash in the read sub.
Setting a route to $base->route('/')->via('get')->to('account#index');
causes calls to /account/:id to be rendered from the index sub.
The route $base->route('/users')->via('get')->to('account#users') causes
the calls to /account/:id/users to be rendered from the users sub.
I think you have to provide different parameters to respond_to method. I would expect this to work:
$self->respond_to(json => { json => $user_list->{users} });
Or just call render_json:
$self->render_json($user_list->{users});
Edit: I made simple testing script that works for me (using latter option above):
use Mojolicious::Lite;
get '/account/users' => sub {
my $self = shift;
my $user_list = {
'signup_ip' => '172.17.5.146',
'z_id' => '382C58D8-529E-11E1-BDFB-A44585CCC763',
'signup_date' => '2012-03-12T12:11:10Z',
'name' => 'Some Cool Account Name',
'users' => [{'user_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763'}],
'account_id' => '382C67EC-529E-11E1-BDFB-A44585CCC763',
};
$self->render_json($user_list->{users});
};
app->start;
the request to http://localhost:3000/account/users returned this:
[{"user_id":"382C67EC-529E-11E1-BDFB-A44585CCC763"}]