view release on metacpan or search on metacpan
devdata/https_mojolicious.io_blog_2018_12_04_testing-hooks-and-helpers_ view on Meta::CPAN
<p>What if I have a hook to log exceptions to a special log file, like so:</p>
<pre><code>#!/usr/bin/env perl
use Mojolicious::Lite;
# Log exceptions to a separate log file
hook after_dispatch => sub {
my ( $c ) = @_;
return unless my $e = $c->stash( 'exception' );
state $path = $c->app->home->child("exception.log");
state $log = Mojo::Log->new( path => $path );
$log->error( $e );
};
app->start;
</code></pre>
<p>To test this, once I've loaded my app and created a Test::Mojo object,
I'm free to add more configuration to my app, including new routes!
These routes can set up exactly the right conditions for my test.</p>
<pre><code># test.pl
use Test::More;
devdata/https_mojolicious.io_blog_2018_12_05_compound-selectors_ view on Meta::CPAN
use warnings;
use open qw(:std :utf8);
use charnames qw();
use Mojo::UserAgent;
my $ua = Mojo::UserAgent->new;
my $url = 'https://blog.emojipedia.org/new-unicode-9-emojis/';
my $tx = $ua->get( $url );
die "That didn't work!\n" if $tx->error;
say $tx->result
->dom
->find( 'ul:not( [class] ) li a' )
->map( 'text' )
->map( sub {
my $c = substr $_, 0, 1;
[ $c, ord($c), charnames::viacode( ord($c) ) ]
})
->sort( sub { $a->[1] <=> $b->[1] } )
devdata/https_mojolicious.io_blog_2018_12_07_openapi_ view on Meta::CPAN
- name: collapsed
in: query
description: |
Force a collapsed even when searching for a particular
distribution or module name.
type: boolean
</code></pre>
<h4>Defining the Response</h4>
<p>The OpenAPI specification allows you to define each response to a method call, this includes both specific and generic error handling. Definitions are defined per HTTP status code.</p>
<pre><code>responses:
# HTTP 200 response
200:
description: Search response
# The schema defines what the result will look like
schema:
type: object
properties:
total:
devdata/https_mojolicious.io_blog_2018_12_10_minion-stands-alone_ view on Meta::CPAN
};
app->minion->add_task(
check_url => sub( $job, $url ) {
my $start = time;
my $tx = $job->app->ua->head( $url );
my $elapsed = time - $start;
$job->app->log->info(
sprintf 'Fetching %s took %.3f seconds', $url, $elapsed
);
# If there's an error loading the web page, fail the job
if ( $tx->error ) {
$job->app->log->error(
sprintf 'Error loading URL (%s): %s (%s)',
$url, @{ $tx->error }{qw( code message )},
);
return $job->fail(
sprintf '%s: %s', @{ $tx->error }{qw( code message )}
);
}
$job->finish( $elapsed );
},
);
app->start;
</code></pre>
<h2>Enqueuing Jobs</h2>
devdata/https_mojolicious.io_blog_2018_12_11_who-watches-the-minions_ view on Meta::CPAN
<!-- theme suggests 1300x500 -->
<img alt="Minion logo in the middle of binocular circles, original artwork by Doug Bell" src="/blog/2018/12/11/who-watches-the-minions/banner.jpg">
</div>
<div class="post-content">
<section id="section-1">
<p>Now that I have a <a href="https://mojolicious.org/perldoc/Minion">Minion job
queue</a>, I need to take care of
it properly. Are the workers working (have they seized the means of
production)? Are jobs completing successfully? Are there any errors?
What are they?</p>
</section>
<section id="section-2">
<h2>Minion Jobs Command</h2>
<p>Minion comes with a <a href="https://mojolicious.org/perldoc/Minion/Command/minion/job"><code>job</code>
command</a> that
lists the jobs and their statuses.</p>
devdata/https_mojolicious.io_blog_2018_12_12_dancer-and-minion_ view on Meta::CPAN
}
sub get_invalid_queues( $self, @queues ) {
my %queue_map;
@queue_map{ @QUEUE_TYPES } = ();
my @invalid_queues = grep !exists $queue_map{ $_ }, @queues;
return @invalid_queues;
}
</code></pre>
<p>With that in place, it was easy for our <code>queue_job()</code> method to throw an error if the developer tried to add a job to an invalid queue:</p>
<pre><code>sub queue_job( $self, $args ) {
my $job_name = $args->{ name } or die "queue_job(): must define job name!";
my $guid = $args->{ guid } or die "queue_job(): must have GUID to process!";
my $title = $args->{ title } // $job_name;
my $queue = $args->{ queue } // 'default';
my $job_args = $args->{ job_args };
die "queue_job(): Invalid job queue '$queue' specified" if $self->has_invalid_queues( $queue );
devdata/https_mojolicious.io_blog_2018_12_12_dancer-and-minion_ view on Meta::CPAN
is => 'ro',
isa => 'MyJob::JobQueue',
lazy => 1,
default => sub( $self ) { return MyJob::JobQueue->new->runner; },
);
</code></pre>
<p>And in the models themselves, creating a new queueable task was as easy as:</p>
<pre><code>$self->runner->add_task( InstantXML =>
sub( $job, $request_path, $guid, $company_db, $force, $die_on_error = 0 ) {
$job->note(
request_path => $request_path,
feed_id => 2098,
group => $company_db,
);
MyJob::Models::FooBar->new( request_path => $request_path )->generate_xml({
pdf_guid => $guid,
group => $company_db,
force => $force,
die_on_error => $die_on_error,
});
});
</code></pre>
<h2>Running Jobs</h2>
<p>Starting a job from Dancer was super easy:</p>
<pre><code>use Dancer2;
use MyJob::JobQueue;
devdata/https_mojolicious.io_blog_2018_12_12_dancer-and-minion_ view on Meta::CPAN
my $id = $job->id;
my $notes = $job->info->{ notes };
my $title = $notes->{ title };
my $guid = $notes->{ guid };
$job->on( spawn => sub( $job, $pid ) {
$0 = "$title $guid";
$logger->info( "$title: Created child process $pid for job $id by parent $$ - $guid");
});
$job->on( failed => sub( $job, $error ) {
chomp $error;
$logger->error( $error );
});
});
</code></pre>
<p>To help us for future capacity planning, we want our workers to tell us if they are running at peak capacity, so log when this event occurs:</p>
<pre><code>$worker->on( busy => sub( $worker ) {
my $max = $worker->status->{ jobs };
$logger->log( "$0: Running at capacity (performing $max jobs)." );
});
devdata/https_mojolicious.io_blog_2018_12_23_mojolicious-and-angular_ view on Meta::CPAN
title => 'Higher Order Promises',
day => 3,
author => 'brain d foy',
},
],
status => 200,
);
});
</code></pre>
<p>As a sidenote, you may have to allow <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">Cross Origin Resource Sharing (CORS)</a> if the Angular app throws an error while accessing the mojo API endpoint.
In that case you may want to make use of the <a href="https://mojolicious.org/perldoc/Mojolicious/#before_dispatch"><code>before_dispatch</code></a> app hook:</p>
<pre><code>$self->hook(before_dispatch => sub {
my $c = shift;
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
});
</code></pre>
<p>But please note, in a real-world application <code>*</code> is defeating the security feature.</p>
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
$promise->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>At first glance this isn't too much better than the callback.
However, a few nice features emerge.
The most important of which is that the promise object can be returned to the caller and the caller can choose what to do with it.</p>
<p>In useful code you would also want to attach error handling, though I've omitted it here for bevity.
Mojolicious' promise implementation also gives us the <code>wait</code> method to start the ioloop if necessary.</p>
<p>Although it is interesting to see how a user can create a promise object to convert a callback api to promises, many libraries, including Mojolicious, now have promise variants built-in.
Rather than depending on the user to create a promise to resolve in the callback, these libraries will just return a promise of their own.
In the Mojolicious project, by convention methods that return promises end in <code>_p</code>.</p>
<p>With that we can write similar code to the one above</p>
<pre><code>my $promise = $ua->get_p($url);
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
}
get_title_p($url)->then(sub ($title) {
say $title;
})->wait;
</code></pre>
<p>All told, this is a step in the right direction, but it still involves a mental shift in style.
Even if this is easier than using pure callbacks, you still have to keep track of promises, consider the implications of chaining.
You still have to attach callbacks using <code>then</code>.
And don't forget error handling callbacks too!</p>
<p><em>Editor's note: to this point in the article, it is similar to the Perl Advent Calendar entry <a href="http://www.perladvent.org/2018/2018-12-19.html">posted just a few days before this one on 2018-12-19</a>, humorously presented by Mark Fo...
If you'd like to see another take on promises and Mojo::Promise specifically, give it a read.
Everything in it is applicable even as this article takes it one step further below ...</em></p>
<h2>Async/Await</h2>
<p>What we really wish we could tell the Perl interpreter to do is</p>
<ul>
devdata/https_mojolicious.io_blog_2018_12_24_async-await-the-mojo-way_ view on Meta::CPAN
<p>Where the example above would be at</p>
<pre><code>localhost:3000?url=https%3A%2F%2Fmojolicious.org&url=https%3A%2F%2Fmojolicious.io&url=https%3A%2F%2Fmetacpan.org
</code></pre>
<p>That code is almost exactly what you'd write for a blocking implementation except that it would block the server and it have to fetch the urls sequentially.
Instead, since it is written nonblocking, the requests are all made concurrently and the server is still free to respond to new clients.
And yet the code is still very easy to follow.</p>
<p>Note: the <a href="https://metacpan.org/pod/Mojolicious::Plugin::PromiseActions">PromiseActions</a> plugin automatically attaches error handlers to the controller action when it returns a promise; it is highly recommended when using async actions....
<h2>A Word on Implementation</h2>
<p>As stated before Mojo::AsyncAwait requires some mechanism to suspend the interpreter and resume it at that point later on.
Currently, the module uses the somewhat controversial module <a href="https://metacpan.org/pod/Coro">Coro</a> to do it.
As a bulwark against future implimentation changes, it comes with a pluggable backend system, not unlike how Mojo::IOLoop's pluggable reactor system works.
The default implementation may change and users may choose to use any available backend if they have a preference (once new ones come along, and others <strong>are</strong> in the works).</p>
<h2>Conclusion</h2>