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

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.

Related

Getting "A service address has not been specified..." when using Perl SOAP::Lite to do call using wsdl

I'm trying to do a SOAP call to http://www.webservicex.net/ConvertSpeed.asmx using the WSDL.
My code is as follows
use SOAP::Lite;
my $client = SOAP::Lite->new;
$client = $client->service( "http://www.webservicex.net/ConvertSpeed.asmx?wsdl" );
my $soapResp = $client->call( "ConvertSpeed", 100, "kilometersPerhour", "milesPerhour" );
if ( $soapResp->fault ) {
print $soapResp->faultstring;
}
else {
print $soapResp->result;
}
This gives me the following error
A service address has not been specified either by using SOAP::Lite->proxy() or a service description)
I believe that the WSDL is supposed to provide the service address, but it does not seem to be doing that here.
I have tried setting the service address to http://www.webservicex.net/ConvertSpeed.asmx via $client->proxy("http://www.webservicex.net/ConvertSpeed.asmx") after $client->service(), but that just gives me an error saying:
Server did not recognize the value of HTTP header SOAPAction: #ConvertSpeed
I assumed the WSDL was supposed to provide the service address and so omitted the $client->proxy() bit and tried other stuff.
This ranged from chaining methods differently and eventually chaining as little as possible (the code above) to changing the way the call is made.
From this answer I tried
$client->ConvertSpeed(100, "kilometersPerhour", "milesPerhour")
which just seems to return nothing.
This is running on Perl 5.10 and version 0.714 of SOAP::Lite.
I'm not sure what else to try, but I have confirmed that the same SOAP call works from Python(see edit).
EDIT:
from zeep import Client
client = Client('http://www.webservicex.net/ConvertSpeed.asmx?wsdl')
result = client.service.ConvertSpeed(100, 'kilometersPerhour', 'milesPerhour')
print(result)
I posted this question to PerlMonks as well since it didn't seem to be doing well here. According to the answer I received there SOAP::Lite isn't ideal for a complex service using the WSDL. Instead everything should be specified using the uri() and proxy() methods. If the WSDL is to be used then XML::Compile::WSDL11 is the module to look at.
The answer supplies working code for the call using SOAP::Lite without the WSDL as follows :
$client = SOAP::Lite->new(
uri => 'http://www.webserviceX.NET/',
proxy => 'http://www.webservicex.net/ConvertSpeed.asmx',
);
$client->on_action(sub {"http://www.webserviceX.NET/ConvertSpeed"});
$client->autotype(0);
$sheader = SOAP::Header->new('');
$sheader->type(xml => '');
$client->ns('http://www.webserviceX.NET/','web');
$client->envprefix('soapenv');
push #request, SOAP::Data->name(speed => 100)->prefix('web');
push #request, SOAP::Data->name(FromUnit => 'kilometersPerhour')->prefix('web');
push #request, SOAP::Data->name(ToUnit => 'milesPerhour')->prefix('web');
$soapResp = $client->ConvertSpeed($sheader,#request);
print $soapResp->result,"\n";
If I get a working example using XML::Compile::WSDL11 I will post it here. As I am unsure if I am going to look into it soon I decided to at least post this since the question is more focused on using a WSDL with SOAP::Lite

How to receive json in Dancer?

I am very new to Perl framework Dancer. As of now I have a get http listener working. I have an Angular framework trying to post a json string to Dancer. How can I retreive the json and perhaps assign it to a scalar variable ($json).
get '/games' => sub {
header 'Access-Control-Allow-Origin' => '*';
&loadgames();
return $games;
};
post '/newgame' => sub {
header 'Access-Control-Allow-Origin' => '*';
#what should i put here to retrieve the json string
#I plan to pass the json string to a sub to convert to XML
};
I am not sure If I chose Dancer as backend framework that will get and post data.
Thanks for the help!
If your HTTP request has a JSON body (Content-type: application/json) rather than being an HTML form post, then you probably want something like this:
post '/url-path' => {
my $post = from_json( request->body );
# do something with the POSTed data structure
# which would typically be a hashref (or an arrayref)
# e.g.: schema->resultset('Widget')->create($post);
}
The from_json routine is one of the DSL Keywords provided by Dancer.
Dancer provides the params keyword for accessing route, body, and query parameters. You want a body parameter. Exactly which body parameter you want will depend on the name of the field you posted it to the route with (look at your form or your ajax request).
my $json_string = params('body')->{$field_name}
You can also use param, if you don't have any conflicting parameter names in the route or query parameters.
Once you have the json, remember it's just a string at the moment. You might want to read it into a perl data structure: Dancer provides from_json for this purpose.
As an aside: I notice in your get route, you call a function loadgames in void context, and then return a variable you haven't declared (or perhaps you have set it as a global - but do you need it to be a global?). I recommend beginning each perl file with use strict; to pick up issues like this. I suspect you probably just want to use the return value of loadgames as your return value.

Sending an unbuffered response in Plack

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

Perl WebService::Soundcloud - how to pass track parameters while uploading to Soundcloud

I'm trying to upload a sound to soundcloud using WebService::Soundcloud. I've so far been able to make a couple of GET/POST requests following the examples provided by the WebService::Soundcloud documentation.
However, I can't find a decent example anywhere on how to do an upload while passing the required parameters i.e. track, and within it, asset_data, title e.t.c. I'm wondering if I should be sending out a multipart message.
Any examples will be appreciated!
Also, here's what I have tried so far: After authenticating and getting a valid WebService::Soundcloud instance.
GET my $response = $scloud->get( '/me/tracks' );
PUT my $response = $scloud->put( '/me/tracks/91576621', JSON::to_json({track=>{title=>"My test title",description=>"My test description"}}) );
POST my $file = '/home/ski/track1.mp3';
my $asset_data = File::Slurp::read_file( $file, binmode => ':raw' );
my $response = $scloud->post('/me/tracks', '{"track":{"title":"My test title","asset_data":"'.$asset_data.'"}}' );
This fails with "Request entity contains invalid byte sequence. Please transmit valid UTF-8"
The example you provide manually constructs the JSON but doesn't take into account the binary nature of the file content which means it's unlikely to work. It's also vulnerable to abusive content changing your JSON due to lack of escaping/proper encoding.
The documentation you cite has a put example which demonstrate the content needs to be encoded into JSON and then passed to the library. I've not used this api but it's probably a simple case of using encode_json as per the examples. I'll just show an example that's equivalent to your manual encoding:
use JSON qw(encode_json);
my $asset_data = "ascii, quotes (\"'), non-ascii: \000\001\002\003";
my $content = encode_json({ track => { title => "My test title",
asset_data => $asset_data}});
print $content . "\n"; ### inspection of encoding
And this shows that JSON uses a UTF-8 representation to deal with binary data:
{"track":{"asset_data":"ascii, quotes (\"'), non-ascii: \u0000\u0001\u0002\u0003","title":"My test title"}}
The key/values are being re-ordered there but it's equivalent JSON.

Testing a JSON PUT request in mojolicious

I am creating test cases for my app developed using Mojolicious framework. I am testing the routes for appropriate responses for the REST calls that are made. I use JSON as a means of communication between the server and client. To test GET, POST and DELETE requests I use the following functions
GET : get_ok()POST: post_json_ok() DELETE: delete_ok()
but for PUT I am not able to use put_ok(). And example of my usage of put_ok() would be my $t = Test::Mojo->new;$t->put_ok('this/is/url/to/resource'=>{ "element" => "modified value"})->status_is(204)
I get a bad request status every time(400) and so the test fails. I use Test::Mojo for testing my application.
Additional information:
$t->put_ok('http://localhost:3000/application/instances/system/test'=>{"model" => "testing put"})->status_is(204);
This is the exact test for PUT request that is attempted. model is a property of resource test that is being attempted to be changed to testing put and the expected status response is 204.
The mapping to the controller is done by
$r->route('/application/instances/system/:id, id => qr/[A-Za-z0-9 ]+/ -> via('put')
->to(controller => 'system', action => 'update_sys');
This route calls the update_sys method in the system controller and that is how the resource is located.
Actually, after discussions with SRI on IRC, he pointed me to an example almost identical you your needs in the documentation.
# Test custom transaction
my $tx = $t->ua->build_json_tx('/user/99' => {name => 'sri'});
$tx->req->method('PUT');
$t->tx($t->ua->start($tx))
->status_is(200)
->json_is('/message' => 'User has been replaced.');
So this should work for you!
Postscript:
You might be interested to know that this discussion has brought some progress: the next release of Mojolicious (version 3.66) will now have a cleaner syntax for this purpose, the new request_ok method. The above example can then be rewritten as
my $tx = $t->ua->build_json_tx('/user/99' => {name => 'sri'});
$tx->req->method('PUT');
$t->request_ok($tx)
->status_is(200)
->json_is('/message' => 'User has been replaced.');
Can you share the definition of your route for 'this/is/url/to/resource' ? the server is returning 400, so it appears Mojo::Controller in your app does not understand what you are sending ...