Chouette

 view release on metacpan or  search on metacpan

lib/Chouette.pm  view on Meta::CPAN


    my $name = $c->req->parameters->{name};

=item C<res>

One would think this would return a L<Plack::Response> object. Unfortunately this isn't yet implemented and will instead throw an error.

=item C<generate_token>

Generates a random string using a default-config L<Session::Token> generator. The generator is created when the first token is needed so as to avoid a "cold" entropy pool immediately after a reboot (see the L<Session::Token> docs).

=item C<task>

Returns an L<AnyEvent::Task> checkout object for the task with the given name:

    $c->task('db')->selectrow_hashref(q{ SELECT * FROM sometable WHERE id = ? },
                                      undef, $id, sub {
        my ($dbh, $row) = @_;

        die $c->respond($row);
    });

Checkout options can be passed after the task name:

    $c->task('db', timeout => 5)->selectrow_hashref(...);

See L<AnyEvent::Task> for more details.

=back



=head1 EXCEPTIONS

Assuming you are familiar with asynchronous programming, most of L<Chouette> should feel straightforward. The only thing that might be unfamiliar is how exceptions are used.

=head2 ERROR HANDLING

The first unusual thing about how Chouette uses exceptions is that it uses them for error conditions, in contrast to many other asynchronous frameworks.

Most asynchronous frameworks are unable to use exceptions to signal errors since an error may occur in a callback being run from the event loop. If this callback throws an exception, there will be nothing to catch it, except perhaps a catch block ins...

Consider the L<AnyEvent::DBI> library. This is how its error handling works:

    $dbh->exec("SELECT * FROM no_such_table", sub {
        my ($dbh, $rows, $rv) = @_;

        if ($#_) {
            # success
        } else {
            # failure. error message is in $@
        }
    });

Even if C<exec> failed, the callback still gets called. Whether or not it succeeded is indicated by its parameters. You can think of this as a sort of "in-band" signalling. The fact that there was an error, and what exactly that error was, needs to b...

But with both of these methods, what should the callback do when it is notified of an error? It can't just C<die> because nothing will catch the exception. With the L<EV> event loop you will see this:

    EV: error in callback (ignoring): failure: ERROR:  relation "no_such_table" does not exist

Even if you wrap an C<eval> or a L<Try::Tiny> C<try {} catch {}> around the code the same thing happens. The try/catch is in effect while installing the callback, but not when the callback is called.

As a consequence of all this, asynchronous web frameworks usually cannot indicate errors with exceptions. Instead, they require you to respond to the client from inside the callback:

    $dbh->exec("SELECT * FROM no_such_table", sub {
        my ($dbh, $rows, $rv) = @_;

        if (!$#_) {
            $context->respond_500_error("DB error: $@");
            return;
        }

        # success
    });

There are several down-sides to this approach:

=over

=item *

The error must be handled locally in each callback, rather than once in a catch-all error handler.

=item *

Everywhere an error might occur needs to have access to the context object. This often requires passing it as an argument around everywhere.

=item *

You might forget to handle an error (or it might be too inconvenient so you don't bother) and your success-case code will run on garbage data.

=item *

Perhaps most importantly, if some unexpected exception is thrown by your callback (or something that it calls) then the event loop will receive an exception and nothing will get logged or replied to.

=back

For these reasons, Chouette uses L<Callback::Frame> to deal with exceptions. The idea is that the exception handling code is carried around with your callbacks. For instance, this is how you would accomplish the same thing with Chouette:

    my $dbh = $c->task('db');

    $dbh->selectrow_arrayref("SELECT * FROM no_such_table", undef, sub {
        my ($dbh, $rows) = @_;

        # success

        # Even if I can die here and it will get routed to the right request!
    });

The callback will only be invoked in the success case. If a failure occurs, an exception will be raised in the dynamic scope that was in effect when the callback was installed. Because Chouette installs a C<catch> handler for each request, an appropr...

Important note: Libraries like L<AnyEvent::Task> (which is what C<task> in the above example uses) are L<Callback::Frame>-aware. This means that you can pass C<sub {}> callbacks into them and they will automatically convert them to C<fub {}> callback...

When using 3rd-party libraries, you must pass C<fub {}> instead. Also, you'll need to figure out how the library handles error cases, and throw exceptions as appropriate. For example, if you really wanted to use L<AnyEvent::DBI> (even though the L<An...

    $dbh->exec("SELECT * FROM no_such_table", fub {
        my ($dbh, $rows, $rv) = @_;

        if (!$#_) {
            die "DB error: $@";
        }



( run in 1.946 second using v1.01-cache-2.11-cpan-5a3173703d6 )