Chouette
view release on metacpan or search on metacpan
AnyEvent::Task
Allows us to perform blocking operations without holding up other
requests.
Callback::Frame
Makes exception handling simple and convenient. You can "die"
anywhere and it will only affect the request being currently
handled.
Important note: If you are using 3rd-party libraries that accept
callbacks, please understand how Callback::Frame works. You will
usually need to pass "fub {}" instead of "sub {}" to these
libraries. See the EXCEPTIONS section for more details.
Session::Token
For random identifiers such as session tokens (obviously).
Log::Defer
Structured logging, properly integrated with AnyEvent::Task so your
tasks can log messages into the proper request log contexts.
"config_file"
If you want a config file, this path is where it will be read from.
The file's format is YAML. The values in this file over-ride the
values in "config_defaults". If this parameter is not provided then
it will not attempt to load a config file and defaults will be used.
"routes"
Routes are specified as a hash-ref of route paths, mapping to
hash-refs of methods, mapping to package+function names or
callbacks. For example:
routes => {
'/myapi/resource' => {
POST => 'MyAPI::Resource::create',
GET => 'MyAPI::Resource::get_all',
},
'/myapi/resource/:resource_id' => {
GET => 'MyAPI::Resource::get_by_id',
POST => sub {
$c->done;
You will need to send a response later, usually from an async
callback. Note: If the last reference to the context is destroyed
without a response being sent, the message "no response was sent,
sending 500" will be logged and a 500 "internal server error"
response will be sent.
You don't ever need to call "done". You can just "return" from the
handler instead. "done" is only for convenience in case you are
deeply nested in callbacks and don't want to worry about writing a
bunch of nested returns.
"respond_raw"
Similar to "respond" except it doesn't assume JSON encoding:
$c->respond_raw(200, 'text/plain', 'here is some plain text');
"logger"
Returns the Log::Defer object associated with the request:
# failure. error message is in $@
}
});
Even if "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 be indicated by the callback's
parameters in some way. Unfortunately every library does this slightly
differently. Another alternative used by some libraries is to accept 2
callbacks, one of which is called in the success case, and the other in
the failure case.
But with both of these methods, what should the callback do when it is
notified of an error? It can't just "die" because nothing will catch the
exception. With the 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 "eval" or a Try::Tiny "try {} catch {}" around the
code the same thing happens. The try/catch is in effect while installing
* 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.
* 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.
For these reasons, Chouette uses 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
"catch" handler for each request, an appropriate error will be sent to
the client and added to the Chouette logs.
Important note: Libraries like AnyEvent::Task (which is what "task" in
the above example uses) are Callback::Frame-aware. This means that you
can pass "sub {}" callbacks into them and they will automatically
convert them to "fub {}" callbacks for you.
When using 3rd-party libraries, you must pass "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
AnyEvent::DBI (even though the AnyEvent::Task version is superior in
pretty much every way) this is what you would do:
$dbh->exec("SELECT * FROM no_such_table", fub {
my ($dbh, $rows, $rv) = @_;
if (!$#_) {
die "DB error: $@";
}
# success
});
Note that the "sub" has been changed to "fub" and an exception is thrown
for the error case.
In summary, when installing callbacks you must use "fub" except when the
library is Callback::Frame-aware.
Please see the Callback::Frame documentation for more specifics.
CONTROL FLOW
The other unusual thing about how Chouette uses exceptions is that it
uses them for control flow as well as errors.
As you can see in the "respond" method documentation of the "CHOUETTE
OBJECT" section, you can "die" with the result of the "respond" method:
lib/Chouette.pm view on Meta::CPAN
=over
=item L<AnyEvent::Task>
Allows us to perform blocking operations without holding up other requests.
=item L<Callback::Frame>
Makes exception handling simple and convenient. You can C<die> anywhere and it will only affect the request being currently handled.
Important note: If you are using 3rd-party libraries that accept callbacks, please understand how L<Callback::Frame> works. You will usually need to pass C<fub {}> instead of C<sub {}> to these libraries. See the L<EXCEPTIONS> section for more detail...
=item L<Session::Token>
For random identifiers such as session tokens (obviously).
=item L<Log::Defer>
Structured logging, properly integrated with L<AnyEvent::Task> so your tasks can log messages into the proper request log contexts.
Note that Chouette also depends on L<Log::Defer::Viz> so C<log-defer-viz> will be available for viewing logs.
lib/Chouette.pm view on Meta::CPAN
C<logging.timezone> - Either C<gmtime> or C<localtime> (C<gmtime> is default, see L<Log::File::Rolling>).
The only required config parameters are C<var_dir> and C<listen> (though these can be omitted from the defaults assuming they will be specified in the config file, see below).
=item C<config_file>
If you want a config file, this path is where it will be read from. The file's format is L<YAML>. The values in this file over-ride the values in C<config_defaults>. If this parameter is not provided then it will not attempt to load a config file and...
=item C<routes>
Routes are specified as a hash-ref of route paths, mapping to hash-refs of methods, mapping to package+function names or callbacks. For example:
routes => {
'/myapi/resource' => {
POST => 'MyAPI::Resource::create',
GET => 'MyAPI::Resource::get_all',
},
'/myapi/resource/:resource_id' => {
GET => 'MyAPI::Resource::get_by_id',
POST => sub {
lib/Chouette.pm view on Meta::CPAN
die 403;
=item C<done>
If you wish to stop processing but not send a response:
$c->done;
You will need to send a response later, usually from an async callback. Note: If the last reference to the context is destroyed without a response being sent, the message C<no response was sent, sending 500> will be logged and a 500 "internal server ...
You don't ever need to call C<done>. You can just C<return> from the handler instead. C<done> is only for convenience in case you are deeply nested in callbacks and don't want to worry about writing a bunch of nested returns.
=item C<respond_raw>
Similar to C<respond> except it doesn't assume JSON encoding:
$c->respond_raw(200, 'text/plain', 'here is some plain text');
=item C<logger>
Returns the L<Log::Defer> object associated with the request:
lib/Chouette.pm view on Meta::CPAN
$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 {
lib/Chouette.pm view on Meta::CPAN
=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: $@";
}
# success
});
Note that the C<sub> has been changed to C<fub> and an exception is thrown for the error case.
In summary, when installing callbacks you must use C<fub> except when the library is L<Callback::Frame>-aware.
Please see the L<Callback::Frame> documentation for more specifics.
=head2 CONTROL FLOW
The other unusual thing about how Chouette uses exceptions is that it uses them for control flow as well as errors.
As you can see in the C<respond> method documentation of the L<CHOUETTE OBJECT> section, you can C<die> with the result of the C<respond> method:
( run in 1.062 second using v1.01-cache-2.11-cpan-9b1e4054eb1 )