"Dynamic" routes in Mojolicious - perl

I would like to implement something like "dynamic" routes in my Mojolicious app. I have some pre-defined "static" routes and a DB table with URL aliases: '/alias' -> '/URL'. Now I'm defining routes on-the-fly and it looks like this:
before_dispatch => sub {
my ($self, $controller) = #_;
my $path = $controller->tx->req->url->path->to_string;
if ( my $alias = $controller->app->model->alias->find({ alias => $path }) ) {
my $match = Mojolicious::Routes::Match->new( get => $alias->{uri} );
my $routes = $controller->app->routes;
$match->match( $routes );
$routes->route( $path )->to( $match->captures );
}
But is there any better way?

You are adding routes at runtime which seems a good approach (although you should probably check if a route exists before overriding it). You could also do it as a catchall with a wildcard placeholder then handoff the request a bit later on.
http://mojolicio.us/perldoc/Mojolicious/Guides/Routing#Wildcard_Placeholders
$r->get('/(*everything)' )->to('mycontroller#aliases');

Related

perl - searching in list of objects which are an accessor of another object

I am a Perl-OO beginner and I am encountering a design-challenge. I hope you can give me some hints to get to an elegant solution. I am working with Mouse Object System here.
For a minimal example lets say I have a User-Object. A user has a name.
package User;
use Mouse;
has "name" => (
is => "rw",
isa => "Str|Undef",
);
Then I have a User-Cache-Object, which gets a list of all Users (from an LDAP-Server). You can say this is a "has-a" Relationship between the User Cache and the User.
package UserCache;
use Mouse;
has "users" => (
is => 'rw',
isa => 'ArrayRef|Undef',
default => sub { [] },
);
I store this list of Users as an Array of User-Objects in the accessor of the User-Cache.
my $cache = UserCache->new();
foreach my $entry ( $ldap->searchGetEntries() ) {
my $user = User->new();
$user->name($entry->get_value('userdn'));
push #{ $cache->users }, $user;
}
Now this is where my Problem comes in. If I want to find a User-Object with specific attributes (e.g. a User named John), I have to loop over this whole Array of User-Objects and query each object for its name. When given a list of names, this gets a really inefficient process.
foreach my $user ( #{ $cache->users } ) {
if ( $user->name eq 'John' ) {
#do something with John
}...
}
Is there a way of storing Lists of Objects in other Objects in a way, that I can efficently search? Like $cache->get_users->get_name('John') and that returns the object I need?
You don't really have to write the UserCache class yourself. Instead, use CHI to cache users you want to cache under the key you want to use for lookups. If you want, you can wrap your cache class to abstract away from the specific cache implementation.
Also, you have this:
push #{ $cache->users }, $user;
where you leak implementation details. Instead, your UserCache object needs something like a save_user method so the code it uses does not depend on the implementation details.
$cache->save_user( $user );
For Moose objects, you get Moose::Meta::Attribute::Native::Trait::Array; for Mouse, you get MouseX::NativeTraits::ArrayRef.
No. At least not universally. You can of course build indexes for common things. Or you could cache searches once you have done them.
Lookups are best implemented as hashes. Those could be attached to the UserCache object. Something like:
my #users = $cache->find( name => 'John' );
That would internally map to a hashref with search fields.
package UserCache;
#...
has _search_index => (
is => 'ro',
isa => 'HashRef',
default => sub { {} },
);
And the hash reference would look something like this:
{
name => {
John => [
User->new( name => 'John', last_name => 'Smith' ),
User->new( name => 'John', last_name => 'Wayne' ),
User->new( name => 'John', last_name => 'Bon Jovi' ),
],
James => [ ... ],
},
id => {
# ...
},
),
But again, you'd have to build those. So you need to do the lookup once. But I think the lookup should be done inside UserCache and stored there too.
sub find {
my ($self, $key, $value) = #_;
# get operation
return #{ $self->_search_index->{$key}->{$value} }
if exists $self->_search_index->{$key}->{$value};
# set operation
foreach my $user ( #{ $self->users } ) {
push #{ $self->_search_index->{$key}->{$value} }, $user
if $user->$key eq $value
}
return #{ $self->_search_index->{$key}->{$value} }
}
This is a very naive implementation and it doesn't support multiple lookups, but it's a start.
Note that if you have a lot of users and a lot of indexes, the data structure might become large.
To make it easier, Moose's built-in traits might be helpful. If you want a stronger cache behavior, look at CHI.

mojolicious helper storing an elasticsearch connection

i'm experimenting with elasticsearch within mojolicious.
I'm reasonably new at both.
I wanted to create a helper to store the ES connection and I was hoping to pass the helper configuration relating to ES (for example the node info, trace_on file etc).
If I write the following very simple helper, it works;
has elasticsearch => sub {
return Search::Elasticsearch->new( nodes => '192.168.56.21:9200', trace_to => ['File','/tmp/elasticsearch.log'] );
};
and then in startup
$self->helper(es => sub { $self->app->elasticsearch() });
however if I try to extend that to take config - like the following -
it fails. I get an error "cannot find index on package" when the application calls $self->es->index
has elasticsearch => sub {
my $config = shift;
my $params->{nodes} = '192.168.56.21:' . $config->{port};
$params->{trace_to} = $config->{trace_to} if $config->{trace_to};
my $es = Search::Elasticsearch->new( $params );
return $es;
};
and in startup
$self->helper(es => sub { $self->app->elasticsearch($self->config->{es}) });
I assume I'm simply misunderstanding helpers or config or both - can someone enlighten me?
Just fyi, in a separate controller file I use the helper as follows;
$self->es->index(
index => $self->_create_index_name($index),
type => 'crawl_data',
id => $esid,
body => {
content => encode_json $data,
}
);
that works fine if I create the helper using the simple (1st) form above.
I hope this is sufficient info? please let me know if anything else is required?
First of all, has and helper are not the same. has is a lazily built instance attribute. The only argument to an attribute constructor is the instance. For an app, it would look like:
package MyApp;
has elasticsearch => sub {
my $app = shift;
Search::ElasticSearch->new($app->config->{es});
};
sub startup {
my $app = shift;
...
}
This instance is then persistent for the life of the application after first use. I'm not sure if S::ES has any reconnect-on-drop logic, so you might need to think about it a permanent object is really what you want.
In contrast a helper is just a method, available to the app, all controllers and all templates (in the latter case, as a function). The first argument to a helper is a controller instance, whether the current one or a new one, depending on context. Therefore you need to build your helper like:
has (elasticsearch => sub {
my ($c, $config) = #_;
$config ||= $c->app->config->{es};
Search::ElasticSearch->new($config);
});
This mechanism will build the instance on demand and can accept pass-in arguments, perhaps for optional configuration override as I have shown in that example.
I hope this answers your questions.

Less verbose debug screen in Catalyst?

in my stage server I would like to activate the debug so the clients can find errors for themselves before the app goes to the production server.
BUT I only want the first part of the message, not the Request, or the Session Data.
For example: Couldn't render template "templates/home.tt2: file error - templates/inc/heater: not found".
The message is enough for me and for my client to see that the "header" call is misspelled.
The Request has a lot of irrelevant information for the client, but also has A LOT of internal developing information that should be hidden all the time!!
Regards
What you want is to override Catalyst's dump_these method. This returns a list of things to display on Catalyst's error debugging page.
The default implementation looks like:
sub dump_these {
my $c = shift;
[ Request => $c->req ],
[ Response => $c->res ],
[ Stash => $c->stash ],
[ Config => $c->config ];
}
but you can make it more restrictive, for example
sub dump_these {
my $c = shift;
return [ Apology => "We're sorry that you encountered a problem" ],
[ Response => substr($c->res->body, 0, 512) ];
}
You would define dump_these in your app's main module -- the one where you use Catalyst.
I had a similar problem that I solved by overriding the Catalyst method log_request_parameters.
Something like this (as #mob said, put it in your main module):
sub log_request_parameters {
my $c = shift;
my %all_params = #_;
my $copy = Clone::clone(\%all_params); # don't change the 'real' request params
# Then, do anything you want to only print what matters to you,
# for example, to hide some POST parameters:
my $body = $copy->{body} || {};
foreach my $key (keys %$body) {
$body->{$key} = '****' if $key =~ /password/;
}
return $c->SUPER::log_request_parameters( %$copy );
}
But you could also simply return at the beginning, if you don't want any GET/POST parameters displayed.
Well, I didn't think of the more obvious solution, in your case: you could simply set your log level to something higher than debug, which would prevent these debug logs from being displayed, but would keep the error logs:
# (or a similar condition to check you are not on the production server)
if ( !__PACKAGE__->config->{dev} ) {
__PACKAGE__->log->levels( 'warn', 'error', 'fatal' ) if ref __PACKAGE__->log;
}

Mojolicious Basic Authentication using "under" without Mojolicious::Lite

I am looking for a clean and simple example of how to use the "under" functionality in a "Mojolicious" application. All the examples I find are dealing with "Mojolicious::Lite" (which I don't use).
For example I listened to the screencast here http://mojocasts.com/e3 and I think I understand the concept of the under functionality. But I don't use "Mojolicious::Lite", so it seems that I can't follow the example directly. I keep on failing trying to adopt the Lite-example for non-Lite style. (That's probably also because I'm still kind of new to the framework)
The relevant code looks like this:
# Router
my $r = $self->routes;
# Normal route to controller
$r->get('/') ->to('x#a');
$r->get('/y')->to('y#b');
$r->any('/z')->to('z#c');
So all of this routes need to be protected by user/pass. I tried to do something like this:
$r->under = sub { return 1 if ($auth) };
But this does not compile and I just can't find an example matching this code-style...
Can anybody give me the right hint or link here?
And please forgive me if this is somewhere in the docs... they might be complete, but they lack understandable examples for simple minded guys like me :-P
The analogous code to the Lite-examples looks like this:
# Router
my $r = $self->routes;
# This route is public
$r->any('/login')->to('login#form');
# this sub does the auth-stuff
# you can use stuff like: $self->param('password')
# to check user/pw and return true if fine
my $auth = $r->under( sub { return 1 } );
# This routes are protected
$auth->get ('/') ->to('x#a');
$auth->post('/y')->to('y#b');
$auth->any ('/z')->to('z#c');
Hopes this helps anybody!
(Solution found here: http://mojolicio.us/perldoc/Mojolicious/Routes/Route#under)
I am doing it like this - in a full mojo (not lite) app:
in the startup method
$self->_add_routes_authorization();
# only users of type 'cashier' will have access to routes starting with /cashier
my $cashier_routes = $r->route('/cashier')->over( user_type => 'cashier' );
$cashier_routes->route('/bank')->to('cashier#bank');
# only users of type 'client' will have access to routes starting with /user
my $user_routes = $r->route('/user')->over( user_type => 'client' );
$user_routes->get('/orders')->to('user#orders');
below in the main app file:
sub _add_routes_authorization {
my $self = shift;
$self->routes->add_condition(
user_type => sub {
my ( $r, $c, $captures, $user_type ) = #_;
# Keep the weirdos out!
# $self->user is the current logged in user, as a DBIC instance
return
if ( !defined( $self->user )
|| $self->user->user_type()->type() ne $user_type );
# It's ok, we know him
return 1;
}
);
return;
}
I hope this helps
I use this scenario in my application:
my $guest = $r->under->to( "auth#check_level" );
my $user = $r->under->to( "auth#check_level", { required_level => 100 } );
my $admin = $r->under->to( "auth#check_level", { required_level => 200 } );
$guest->get ( '/login' )->to( 'auth#login' );
$user ->get ( '/users/profile' )->to( 'user#show' );
After this all children routes of $r will go over check_level subroutine:
sub check_level {
my( $self ) = #_;
# GRANT If we do not require any access privilege
my $rl = $self->stash->{ required_level };
return 1 if !$rl;
# GRANT If logged in user has required level OR we raise user level one time
my $sl = $self->session->{ user_level };
my $fl = $self->flash( 'user_level' );
return 1 if $sl >= $rl || $fl && $fl >= $rl;
# RESTRICT
$self->render( 'auth/login', status => 403 );
return 0;
}

Mojolicious wildcard placeholders and question mark

The wildcard placeholder (*) is said to match absolutely everything.
But I'm afraid that it doesn't...
I have a webservice with the following method:
get '/*param' => sub {
my $self = shift;
my $param = $self->stash('param');
$self->app->log->debug($param);
}
When i query my service with: http://localhost:3000/search
then the method logs "search" which is ok
but
when i query my service with: http://localhost:3000/search?page=1
then the method also logs "search" which is not ok IMO
I also tried replacing
get '/*param' => sub {
with
get '/:param' => [param => qr/.*/] => sub {
but the result is the same.
Does anybody know of a way around this?
Or should I file this as a bug?
Regards,
Lorenzo
UPDATE
for people with the same problem, I've worked around this issue like this:
get '/*path' => sub {
my $self = shift;
my $path = $self->stash('path');
my #params = $self->param;
if (scalar #params > 0) {
$path .= '?';
foreach my $param (#params) {
$path .= $param . '=' . $self->param($param) . '&';
}
$path = substr($path, 0, length($path) - 1);
}
$self->app->log->debug($path);
}
?page= its not url.
Its param.
So no any bugs here.
you have 'search' in $param.
And $page=1 in stash.
I think Korjavin is right, that's expected behavior. Looks like "page=1" as a parameter and should be in $stash->param('page'). See GET-POST-parameters in ::Lite
If it does not work, maybe renaming the "param" placeholder to something else helps? Maybe it's a name-clash.
The request parameters wouldn't be in the stash.
They're in
$self->req->params
So
my $params = $self->req->params->to_hash;
$self->app->log->debug(Dumper $params);
Should allow you to see the information you're after