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.
Related
I have two routes in Mojolicious app as follows:
my $route = $r->any('/api')->to('API#');
$route->get('/get_data')->to('#process_forms');
$route->get('/get_data/?file=:file&name=:name')->to('#submit_forms');
if I go to /api/get_data I get redirected to process_forms function. I want the app to take me to submit_forms function if I pass additional arguments to that same route. for example, url /api/get_data/?file=myfile&name=myname should call submit_forms function, but that's not the case here.
In both scenarios, process_forms is called.
What option Mojolicious routing provides to help me with this?
Mojo's router connects URL and HTTP request methods to controllers. The GET and POST parameters are not used in routing. This makes sense, because a URL is typically supposed to target a resource by itself.
You have a path /get_data you need to send that to one controller. From there you want it sounds like you want to do is to go to another controller if you have GET parameters (passed in the URL). You can do this, but it's not normally what you want.
Just putting a conditional in a controller,
What you normally want when handling get parameters is simply to put them inside a block in the controller, IE,
my $query = $c->req->query_params
my $file = $query->param('foo');
my $name = $query->param('name');
if ( defined $file && defined $name) {
# we have file and name
}
else {
# or we do not
}
Redispatch
But you can always redispatch to a different controller (manually)
MyApp::Controller::submit_forms($self);
Or, through Mojo,
$self->routes->continue("#submit_forms")
As a last idea, you can also make it that the route doesn't match if the post variables aren't there. But, I've never needed to do this.
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.
I'm working in a section of a Perl module that creates a large CSV response. The server runs on Plack, on which I'm far from expert.
Currently I'm using something like this to send the response:
$res->content_type('text/csv');
my $body = '';
query_data (
parameters => \%query_parameters,
callback => sub {
my $row_object = shift;
$body .= $row_object->to_csv;
},
);
$res->body($body);
return $res->finalize;
However, that query_data function is not a fast one and retrieves a lot of records. In there, I'm just concatenating each row into $body and, after all rows are processed, sending the whole response.
I don't like this for two obvious reasons: First, it takes a lot of RAM until $body is destroyed. Second, the user sees no response activity until that method has finished working and actually sends the response with $res->body($body).
I tried to find an answer to this in the documentation without finding what I need.
I also tried calling $res->body($row_object->to_csv) on my callback section, but seems like that ends up sending only the last call I made to $res->body, overriding all previous ones.
Is there a way to send a Plack response that flushes the content on each row, so the user starts receiving content in real time as the data is gathered and without having to accumulate all data into a veriable first?
Thanks in advance for any comments!
You can't use Plack::Response because that class is intended for representing a complete response, and you'll never have a complete response in memory at one time. What you're trying to do is called streaming, and PSGI supports it even if Plack::Response doesn't.
Here's how you might go about implementing it (adapted from your sample code):
my $env = shift;
if (!$env->{'psgi.streaming'}) {
# do something else...
}
# Immediately start the response and stream the content.
return sub {
my $responder = shift;
my $writer = $responder->([200, ['Content-Type' => 'text/csv']]);
query_data(
parameters => \%query_parameters,
callback => sub {
my $row_object = shift;
$writer->write($row_object->to_csv);
# TODO: Need to call $writer->close() when there is no more data.
},
);
};
Some interesting things about this code:
Instead of returning a Plack::Response object, you can return a sub. This subroutine will be called some time later to get the actual response. PSGI supports this to allow for so-called "delayed" responses.
The subroutine we return gets an argument that is a coderef (in this case, $responder) that should be called and passed the real response. If the real response does not include the "body" (i.e. what is normally the 3rd element of the arrayref), then $responder will return an object that we can write the body to. PSGI supports this to allow for streaming responses.
The $writer object has two methods, write and close which both do exactly as their names suggest. Don't forget to call the close method to complete the response; the above code doesn't show this because how it should be called is dependent on how query_data and your other code works.
Most servers support streaming like this. You can check $env->{'psgi.streaming'} to be sure that yours does.
Plack is middleware. Are you using a web application framework on top of it, like Mojolicious or Dancer2, or something like Apache or Starman server below it? That would affect how the buffering works.
The link above shows an example by Plack's author:
https://metacpan.org/source/MIYAGAWA/Plack-1.0037/eg/dot-psgi/echo-stream-sync.psgi
Or you can do it easily by using Dancer2 on top of Plack and Starman or Apache:
https://metacpan.org/pod/distribution/Dancer2/lib/Dancer2/Manual.pod#Delayed-responses-Async-Streaming
Regards, Peter
Some reading material for you :)
https://metacpan.org/pod/PSGI#Delayed-Response-and-Streaming-Body
https://metacpan.org/pod/Plack::Middleware::BufferedStreaming
https://metacpan.org/source/MIYAGAWA/Plack-1.0037/eg/dot-psgi/echo-stream.psgi
https://metacpan.org/source/MIYAGAWA/Plack-1.0037/eg/dot-psgi/nonblock-hello.psgi
So copy/paste/adapt and report back please
In Catalyst framework there is a global init sub(executed before any controller). i want the set some config variable from DB(like supper admin id or admin email for GPG configuration ).
i tried to use auto and 'begin' actions but those actions are just executed for its corresponding controller.
in Root.pm file:
sub auto :Private {
my ($self, $c) = #_;
my $config = $c->model('DB')->resultset('Config')->single();
$c->config->{var1} = $config->var1;
};
When i request another controller like Controller2 and begin , the $c->config->{var1} was empty..
That sort of configuration belongs in the main program, i.e. MyApp.pm, so it is set before you even think about accepting a request. You don't normally want to be configuring database connectivity during every request cycle.
Your question isn't entirely clear, but if you do need to configure database settings on every request, then the auto sub in the root controller Root.pm would be the place to ensure it happened at the top of every request.
But I can't help feeling there's an X-Y problem in here...
I'm working with SOAP::WSDL and another company's custom WSDL file. Every time they make a change for me and I recreate my modules, something breaks. Finding the problem is rather tedious because I don't find a proper way to access the actual request that is sent to the SOAP server.
The only way to get to the request so far has been to use tcpdump in conjunction with wireshark to extract the request and result. That works, but since I don't have root privileges on the dev machine I have to get an admin over every time I want to do that. I feel there must be another way to get to the HTTP::Request object inside the SOAP::WSDL thing. But if the server returns a fault, I don't even have a response object, but rather a SOAP::WSDL::SOAP::Typelib::Fault11 object that has no visible relation to the request.
I've also tried using the debugger but I'm having trouble finding the actual request part. I've not yet understood how to tell the debuger to skip to a specific part deep inside a complex number of packages.
I stumbled across this, having the same problem myself. I found the answer is using both options that raina77ow listed.
$service->outputxml(1);
returns the whole SOAP envelope xml, but this needs to be combined with
$service->no_dispatch(1);
With no_dispatch set, the SOAP request is printed, instead of the reply from the request. Hopefully this can help others.
Have you tried to use SOAP::WSDL::Client tracing methods - and outputxml in particular? It returns the raw SOAP envelope which is to be sent to the server.
You can also use no_dispatch configuration method of SOAP::WSDL package:
When set, call() returns the plain request XML instead of dispatching
the SOAP call to the SOAP service. Handy for testing/debugging.
I found a way to at least print out the generated XML code.
First, I looked at SOAP::WSDL::Client as raina77ow suggested. That wasn't what I needed, though. But then I came across SOAP::WSDL::Factory::Serializer. There, it says:
Serializer objects may also be passed directly to SOAP::WSDL::Client
by using the set_serializer method.
A little fidgeting and I came up with a wrapper class for SOAP::WSDL::Serializer::XSD which is the default serializer used by SOAP::WSDL. A look at the code helped, too.
Here's the module I wrote. It uses SOAP::WSDL::Serializer::XSD as a base class and overloads the new and serialize methods. While it only passes arguments to new, it grabs the returned XML from serialize and prints it, which suffices for debugging. I'm not sure if there's a way to put it somewhere I can easily get it from.
package MySerializer;
use strict;
use warnings;
use base qw(SOAP::WSDL::Serializer::XSD);
sub new {
my $self = shift;
my $class = ref($self) || $self;
return $self if ref $self;
# Create the base object and return it
my $base_object = $class->SUPER::new(#_);
return bless ($base_object, $class);
}
sub serialize {
my ($self, $args_of_ref) = #_;
# This is basically a wrapper function that calls the real Serializer's
# serialize-method and grabs and prints the returned XML before it
# giving it back to the caller
my $xml = ref($self)->SUPER::serialize($args_of_ref);
print "\n\n$xml\n\n"; # here we go
return $xml;
}
1;
And here's how I call it:
my $serializer = MySerializer->new();
$self->{'_interface'} = Lib::Interfaces::MyInterface->new();
$self->{'_interface'}->set_serializer($serializer); # comment out to deactivate
It's easy to deactivate. Only put a comment in the set_serializer line.
Of course printing a block of XML to the command line is not very pretty, but it gets the job done. I only need it once in a while why coding/testing, so this is fine I guess.