How do you set up an object oriented jsonrpc Mojolicious application? - perl

I'm trying to set up a jsonrpc application using Mojo. I built some controllers
and related modules using REST but then decided I needed to use JSONRPC.
Using the following code from http://metacpan.org/pod/Mojolicious::Plugin::JsonRpcDispatcher
# 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
}
);
I was able to replicate one of my Controller => Model relationships where the controller
was ported to this format in one file.
I'd like to set it up so that the application file basically can load the each of the files
as a service similar to what this page displays http://metacpan.org/pod/MojoX::JSON::RPC::Service
$svc->register(
'sum',
sub {
my #params = #_;
my $sum = 0;
$sum += $_ for #params;
return $sum;
}
);
In other words, what I'm envisioning is reworking each controller to act similar to
the snippet above but have them all loaded in the application module. Like this:
## Then in Mojolicious application
$self->plugin(
'json_rpc_dispatcher',
services => {
'/jsonrpc' => $svc,
}
);
Is it possible to create a file with functions set up like the code above and then load
them as servers in the application module? MojoX::JSON::RPC::Service appears to offer that. I've been unable to get it to work.

Related

Mojolicious set HTTP params for controller artificially

I use Mojolicious command for testing a controller function. This function looks like this:
sub process_order {
# controller object
my $c = shift;
my $id = $c->param('id');
# process data
}
Is it possible to artificially set the params for the controller inside a command:
sub run {
my ($self, #args) = #_;
$self->app->controller_class('Base::Controller');
my $c = $self->app->build_controller;
set_http_param('id', 1); # ?
$c->process_order();
}
I am using a different database for testing and not all the tables are present there, so it would be nice if I could setup some artificial environment for the controller.

Delegate Rerouting to Controller

Mojolicious + Hypnotoad.
I want my controllers to reroute the remaining portion of the request, so that I don't have to declare all routes at the level of the main script.
So for example, '/foo/bar/baz' should route to Controller 'FOO', which will then decide routing for 'bar/baz', internal to itself.
Main script :
package MyApp;
use Mojo::Base 'Mojolicious';
use Mojolicious::Plugin::Config;
sub startup {
my $self = shift;
$self->moniker('MyApp');
$self->plugin('Config');
my $r = $self->routes;
$r->any('/foo/*remainder')->to('FOO#rerouter')->name('TEST_NAME');
}
I've tried one way, by dynamically adding routes, but running it many times shows inconsistency - probably caused by a race condition or something :
package MyApp::Controller::FOO;
use Mojo::Base 'Mojolicious::Controller';
sub rerouter {
my $self = shift;
my $r = Mojolicious::Routes->new;
$r->get('bar/:baz')->to('myInternalControllerAction');
my $current_route = $self->current_route; # 'TEST_NAME'
$self->app->routes->find($current_route)->add_child($r);
}
sub myInternalControllerAction { #stuff }
That seems consistent with this other answer that :
"The router is dynamic until the first request has been served, after
that, the router cannot change routes"
https://stackoverflow.com/a/22291864/2304437
Even if that did work though, the execution would exit the controller FOO before re-entering it on a different action via the dynamically added route(s).
The other way I can do it is just create a dispatch table in the "re-router" of the controller, since I have access to the remainder of the request anyway :
sub rerouter {
my $self = shift;
my $remainder = $self->stash->{remainder};
# ...
}
But then I lose benefits of Mojolicious' dispatcher - I would have to parse the remaining request URL / path myself. Feels dirty.
Hooks don't seem appropriate here either.
How can I elegantly let the controller route its own child portion ?
I solved this as follows :
use MyApp::Controller::FOO;
my $r = $self->routes;
my $r_root = $r->under('/' => sub { return 1; });
my $baz = $r_root->under('/baz' => sub { return 1; });
$baz->add_child($MyApp::Controller::FOO::routes);
Then in the module :
package MyApp::Controller::FOO;
use Mojo::Base 'Mojolicious::Controller';
our $routes = Mojolicious::Routes->new;
my $r = $routes->under('/')->to('FOO#bar');
sub bar { ... }
Maybe not as elegant as it could be, but it works very well for me.

how to pass arguments from an plack app to an mojolicious app enabled in builder?

given the example plack app using lots of middleware components and an mojolicious app
enabled in builder (see below), how can i pass parameters from the app.psgi to Mojolicious
without using the ugly %ENV hack shown? of cause passing an config is just an example, this could be any scalar/object.
app.psgi
use Plack::Builder;
$ENV{CONFIG} = {...};
builder {
...
Mojolicious::Commands->start_app('MyApp');
};
MyApp.pm
package MyApp;
use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
my $r = $self->routes;
$self->config( $ENV{CONFIG} );
$r->route('/')->to('home#');
}
This is an interesting question and is most easily tackled by looking at the source. In your example you rightly use
Mojolicious::Commands->start_app('MyApp');
Looking at the source shows that start_app is a rather simple wrapper:
sub start_app {
my $self = shift;
return Mojo::Server->new->build_app(shift)->start(#_);
}
It turns out that build_app is as well:
sub build_app {
my ($self, $app) = #_;
local $ENV{MOJO_EXE};
return $app->new unless my $e = Mojo::Loader->new->load($app);
die ref $e ? $e : qq{Couldn't find application class "$app".\n};
}
returning a new instance of your app's class. The Mojolicious class's new function is more involved, but in the end, it just calls the familiar startup method and returns the instance.
This means that you cannot easily pass arguments to the startup method from your middleware wrapper, used in the standard way. I can think of two mechanisms to accomplish what you want to do: 1) write your own build_app function to replace the server's method but which passes arguments to $app->new (which would get passed to startup in turn) or 2) write your own start_app function which could call another startup-like function.
# in MyApp.pm
sub startup {
... # as before
}
sub after_startup {
... # your new code here,
# or even most of what was in `startup` before
}
and
# app.psgi
builder {
...
my $app = Mojo::Server->new->build_app(shift);
$app->after_startup(#your_args_here);
$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;

How can I create a new CGI script programmatically and run it immediately?

I have written a Perl script to read the configuration file and create CGI scripts. This works fine and I get the output of CGI script on terminal and I can execute that CGI scripts on web pages. Below is my sample script.
#!/usr/bin/perl -w
use strict;
use Text::Template;
my $conf = "test.cfg";
open CFG, $conf or die "Could not open config file";
my #rawConfig = <CFG>;
my $config = eval "{".join("",#rawConfig)."}";
my $template = Text::Template->new(TYPE => 'FILE', SOURCE => 'test.cgi.tmpl');
my $result = $template->fill_in(HASH => $config);
print $result;
By using this I have to save each of the CGI and execute separately. I need help to modify this code so that I can directly execute this script on web and display the output on webpage.
This is a very, very scary thing to do and I would generally advise against doing anything that creates new programs and lets the world run them without a human in there somewhere.
I suspect, however, that you might be doing this because the CGI scripts you are creating merely need different configuration data, and you've stored that right in the script itself. It's just a guess, but it's the usual motivator for this sort of thing. The answer is to not store configuration data in the script. I have an entire chapter in Mastering Perl about not storing configuration in code.
I'd work really hard to avoid the situation that you are describing.
(Does SO have a spoiler tag?)
If you really needed to do this for a good reason (and that's almost never the case), you'd create the file from the original process then issue an internal redirect to the new CGI script. How you do that is your homework assignment since I'm not going to put the bullets in the gun for you.
Multiple template based scripts is (almost) never the right answer.
Use data structures from a config file and control structures to get the behaviors you need.
Instead of using a template to create code like:
sub foo {
my $thing = shift;
return blarg( $thing ) * feemb( $thing );
}
sub bar {
my $thing = shift;
return crag( $thing ) * forg( $thing );
}
sub baz {
my $thing = shift;
return chomb( $thing ) * veezle( $thing );
}
Do this:
# Get this from a config file. YAML perhaps
my $cfg = {
foo => [ \&blarg, \&feemb ],
bar => [ \&crag, \&forg ],
baz => [ \&chomb, \&veezle ],
};
sub calculate_product {
my $cfg = shift;
my $type = shift;
my $thing = shift;
my #subs_to_call = #{ $cfg->{$type} || [] };
my $result = {shift #subs_to_call}->($thing};
$result *= $_->($thing) for #subs_to_call;
return $result;
}
# Call like so:
my $foo_prod = calculate_product($cfg, 'foo', 15);
You can bind config info to a subroutine (that is 'curry your functions') by generating closures with config info:
# Get this from a config file. YAML perhaps
my $cfg = {
foo => [ \&blarg, \&feemb ],
bar => [ \&crag, \&forg ],
baz => [ \&chomb, \&veezle ],
};
my %calculate_product;
for my $product ( keys %$cfg ) {
my #stored_subs_to_call = #{$cfg->{$product} || [] };
$calculate_product{$prod} = sub {
my $thing = shift;
my #subs_to_call = #stored_subs_to_call;
my $result = {shift #subs_to_call}->($thing};
$result *= $_->($thing) for #subs_to_call;
return $result;
}
}
# Call generated code like so:
my $foo_prod = $calculate_product{foo}->(15);