Perl Mojolicious routes call sub only once - perl

Iam using Mojolicious::Lite to declare routes for a webservice.
Maybe i missunderstood the routes behaviour but if iam calling a subroutine in the routes definition it gets only called once.
I thougt the sub should get triggered every time the webservice route is called... which isn't the case.
For example i wrote a test route:
use Mojolicious::Lite;
get '/test' => {
text => get_test()
};
sub get_test {
say 'Hello iam only showing up once';
return 'test';
};
This is the console output on starting the server and visiting the localhost:3000/test route:
Hello iam only showing up once
[2020-04-04 22:07:21.09011] [69050] [info] Listening at "http://*:3000"
Server available at http://127.0.0.1:3000
[2020-04-04 22:07:28.26033] [69050] [debug] [78278b87] GET "/test"
[2020-04-04 22:07:28.26097] [69050] [debug] [78278b87] 200 OK
(0.000626s, 1597.444/s)
The "Hello iam only showing up once" is outputted once as the server starts. Visiting the route is not triggering the sub again.
If this is the wanted behaviour how can i get my routes to triggering the sub everytime the route is visited?
I need this because i use this webservice in my application to scan the network and return the result. And i want to rescan the network everytime i call the webservice GET route. Otherwise the data would be useless if its not up to date.
Thanks for your help and explanations.

Your code isn't actually rendering. Your get sub needs to call the render method from Mojolicious::Controller. Inside the get sub, $_[0] is an instance of the controller. In a Mojolicious::Lite app, the route and controller are combined, so you do need to render. If you change your code to this, it will do what you might expect.
use Mojolicious::Lite;
get '/test' => sub {
shift()->render(text => get_test());
};
sub get_test {
warn "Hello I show up on every hit.\n";
return 'test';
};
The key difference is the shift()->render(...) call, which could also be written like this:
get '/test' => sub {
my $c = shift;
$c->render(text => get_text());
}

The above answer is good, but one of the key problems in your code is that you seem to have incorrectly defined your route code ref:
get '/test' => {
should be:
get '/test' => sub {
This is why your get_test only gets called once - you are defining a hash and populating it with the result of get_test, instead of defining a subroutine. But see #DavidO's answer for the full detail on how to approach this.

Related

How to change Mojolicious Lite default error not found to a custom json response

I'm creating a json web service using Mojolicious Lite.
By default Mojolicious returns a HTML response for a server error or not found error.
Is there a way to overwrite this to a custom JSON response?
Here are two approaches:
Use json as the app's default format and use a not_found.*.json.ep template
use Mojolicious::Lite;
app->renderer->default_format('json');
app->start;
__DATA__
## not_found.development.json.ep
{"not":"found","code":404,"data":{"key1":"value1","key2":[42,19,"value3"]}}
Override json payload with a before_render hook.
use Mojolicious::Lite;
hook before_render => sub {
my ($c,$args) = #_;
if ($args->{template} && $args->{template} eq 'not_found') {
$args->{json} = { "too bad" => "so sad" };
}
};
app->start;
It's been a minute, but in Mojo 9 in a full app I've just been returning JSON and returning the status:
$c->render( json => $json, status => 404 );
But, I also have a catch-all route at the end of my setup:
$self->routes->any('/*')->to( ... );
Note, however, that there are some decisions to make about HTTP codes and application-level messaging. For example, accessing a defined and valid endpoint that returns zero search results could easily return 200 and an empty JSON array. The endpoint was there, the server knew how to handle it, and zero list items can be seen as valid as any other number. See I've been abusing HTTP Status Codes in my APIs for years, for example.
The Rendering guide discusses how to customize these responses.

How to add and remove routes dynamically in Mojolicious?

I am trying to put together a maintenance page in my Mojolicious app which all of my users will be shown whenever a file or DB entry is present on the server.
I know I can check for this file or entry on startup and if its there add in my 'catch all' route. However I'm not sure how to do this dynamically? I don't want to have to restart the backend whenever I want to go into maintenance.
Is there a way to add and remove routes from a hook? for example use the before dispatch hook to monitor for the file/db entry and if it exists modify the routes?
I tried this but I didn't seem to be able to access the routes from the hooked function, only in the startup function.
Thank you.
The router is dynamic until the first request has been served, after that, the router cannot change routes (source). That said, can you not declare the route generally and just prohibit any access until that condition exists?
#!/usr/bin/env perl
use Mojolicious::Lite;
any '/' => sub { shift->render( text => 'Hello World' ) };
under sub {
unless (-e 'myfile') {
shift->render_not_found;
return 0;
}
return 1;
};
any '/protected' => sub { shift->render( text => 'I am safe' ) };
app->start;

How can I see all active sessions in a Mojolicious Lite app?

I'm building an app with Mojolicious Lite and I'm looking to give myself a way to watch any and all data about the active sessions. I'm mostly doing this because this is my first foray into using sessions with Mojolicious Lite, and I want to watch what's going on under the hood.
A couple notes:
I'm pretty new to Mojolicious, as you might guess by the fact that I'm using Mojolicious Lite. Any Mojolicious Lite apps I've written before have been pretty trivial, so my familiarity with it is not deep.
For that matter, I'm still 'early intermediate, at best' with perl, so poking around the inner workings of anything OO in perl is largely foreign territory for me.
That said, I made myself a few little routes like so:
get '/firstpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'first_user');
$self->render(text => $usr);
};
get '/secondpage' => sub{
my $self = shift;
my $usr = $self->session(user => 'second_user');
$self->render(text => $usr);
};
get '/sessions' => sub{
my $self = shift;
$self->render(text => Dumper(app->sessions));
};
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understanding of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Sadly, /sessions just shows me the contents of the Mojolicious::Sessions object:
$VAR1 = bless( {
'cookie_path' => '/',
'secure' => 0,
'cookie_name' => 'mojolicious',
'default_expiration' => 3600
}, 'Mojolicious::Sessions' );
But I'm assuming that, somewhere, I can get to a hash of all of the session-related data that Mojolicious has. I've been poking around the documentation for a while but I have yet to find any leads.
Any insight?
I'm working off the assumption that, after I visit the first two urls, Mojolicious will have 'some' data somewhere that would confirm that it knows about first_user and second_user. (I could also be totally off base in my understand of how to use Mojolicious Lite sessions...honestly, from the documentation, I'm not really sure.)
Yeah, I think you're missing the point of sessions. The server/app doesn't remember the state of every user who visits. To allow it to look as if it did, we have cookies. A session is a per-client persistence thing.
Session information is just a hashreference, encoded as JSON and stored in a cookie on the client side. This is useful for remembering that you are logged in, as what username, perhaps an arrayref of things in your shopping cart. When you request a page, this cookie is sent back to the server, which can access the data and prepare the response for you knowing the state of your session.
As such there is no record of "active sessions". All that information is distributed amongst all the clients.
If you would like to get a better idea of what's going on, may I recommend tempire's plugin Mojolicious::Plugin::ConsoleLogger which for the current request shows all of the relevant information (session, stash etc) in your browser's javascript console.
Here is an example.
#!/usr/bin/env perl
use Mojolicious::Lite;
#plugin 'ConsoleLogger'; # if desired
any '/' => sub {
my $self = shift;
my $name = $self->session('name') || 'Unknown'; # get the name from the session
$self->render( text => "Hello $name" );
};
any '/name/:name' => sub {
my $self = shift;
my $name = $self->stash('name'); # get name from path
$self->session( name => $name ); # and store it in the session
$self->redirect_to('/');
};
any '/logout' => sub {
my $self = shift;
$self->session( expires => 1 );
$self->redirect_to('/');
};
app->start;
If you visit /name/ghorahn it will store your name in a cookie. From then on, every time you visit / it will say hello to you until:
Your session expires (default 1 hour from your last visit)
You change your name via /name/whatever
You visit /logout to manually expire the session
You will notice that another user (either on another computer or even a different browser on your same computer) may have a different name, but both are persistent. That is what a session is for. :-)

How to call action in some other controller in Mojolicious?

I have an application that uses the Mojolicious framework. I have a table in the database that has a list of error response and additional details associated with it. I have created corresponding Result and Resultset to work with the DB table. There is also a controller to get details about the Error by interacting with the Resultset.
My idea is to Call an action in this controller that would get the details of the error that is passed to it (by another controller) by querying the database, add-in runtime information about the environment that requested for the resource that resulted in the error, create a response and return to the controller that called it.
I am struggling with the call from one controller to another. How do I do it in Mojolicious? I can pass the controller object ($self) to accomplish this, but is there a better way to do it, so that I separate entirely my error handling response from the calling controller?
In Mojolicious, you would probably want to pass that object around with a helper without creating a Mojolicious::Controller out of it:
In your main class:
sub startup {
my $app = shift;
# ...
my $thing = Thing->new(foo => 42);
$app->helper(thing => sub {$thing});
}
In your controller:
sub cool_action {
my $c = shift;
# ...
my $foo = $c->thing->gimmeh_foo('bar');
# ...
}
However, if you would like to prepare something (e.g. databases) for some actions, maybe under is helpful for you:
To share code with multiple nested routes you can [...]
PS: This feature of Mojolicious was previously named Bridges. Answer updated accordingly.

Mojolicious lite how to redirect for not found and server error pages to user defined error page

How to redirect user defined error page for not found and server error pages to user define page Mojolicious lite
You can add a template for your custom page named exception.html.ep or not_found.html.ep at the end of your liteapp.
For example:
use Mojolicious::Lite;
get '/' => sub {
my $self = shift;
$self->render(text => "Hello.");
};
app->start;
__DATA__
## not_found.html.ep
<!DOCTYPE html>
<html>
<head><title>Page not found</title></head>
<body>Page not found <%= $status %></body>
</html>
For a reference, see the Mojolicious rendering guide.
The renderer will always try to find exception.$mode.$format.* or
not_found.$mode.$format.* before falling back to the built-in default
templates.
I wanted to run some code in my 404 page so borrowing from here
https://groups.google.com/forum/#!topic/mojolicious/0wzBRnetiHo
I made a route that catches everything and placed it after all my other routes, so urls that don't match routes fall through to this:
any '/(*)' => sub {
my $self = shift;
$self->res->code(404);
$self->res->message('Not Found');
# 404
$self->stash( {
# ... my stuff in the stash ...
} );
$self->render('mytemplate', status => 404);
};
I had an API that wanted to send back 404 errors as it would any other error—same JSON format and whatnot.
I had this snippet at the end of startup (full app, not lite). Since this is the last defined route, it only picks up anything not already handled. And, since this handles all that, Mojo never gets the chance to use its own 404 handling by looking for templates:
$self->routes->any('/*')->to(
controller => 'Base',
action => 'not_found'
);