summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2017-12-10 13:39:53 +0100
committerYorhel <git@yorhel.nl>2017-12-10 13:39:56 +0100
commit5a4c27dcb9f66aebe8d4912b1f2126276577fb5e (patch)
tree53b81511a2b449a822a58a272158ad66866848d6
parentf7db78e4a0fdd5580f6ddc8aadbcdca8abf6d9b6 (diff)
Add TUWF::hook() + fix some error handling cases
The primary motivation behind TUWF::hook() is to allow individual modules to register their own hooks, without requiring some centralized coordination. While I was at it, I also noticed some odd behaviour around the old post_request_handler and error_*_handlers, in which the response was not even initialized in some cases. These changes should guarantee that a response has always been initialized whenever any handler is called, and that a dbCommit or dbRollback is always performed as appropriate.
-rw-r--r--lib/TUWF.pm58
-rw-r--r--lib/TUWF.pod56
2 files changed, 90 insertions, 24 deletions
diff --git a/lib/TUWF.pm b/lib/TUWF.pm
index 9a0c221..49418ba 100644
--- a/lib/TUWF.pm
+++ b/lib/TUWF.pm
@@ -19,6 +19,10 @@ our $VERSION = '1.1';
our $OBJ = bless {
_TUWF => {
route_handlers => [],
+ hooks => {
+ before => [],
+ after => [],
+ },
# defaults
mail_from => '<noreply-yawf@blicky.net>',
mail_sendmail => '/usr/sbin/sendmail',
@@ -168,6 +172,14 @@ sub put ($&) { any ['put' ], @_ }
sub patch ($&) { any ['patch' ], @_ }
+sub hook ($&) {
+ my($hook, $sub) = @_;
+ croak "Unknown hook: $hook" if $hook ne 'before' && $hook ne 'after';
+ croak 'Hooks expect a subroutine as second argument' if ref $sub ne 'CODE';
+ push @{$OBJ->{_TUWF}{hooks}{$hook}}, $sub;
+}
+
+
# Load modules
sub load {
$OBJ->_load_module($_) for (@_);
@@ -282,6 +294,15 @@ sub _handle_request {
# put everything in an eval to catch any error, even
# those caused by a TUWF core module
my $eval = eval {
+ # initialze response
+ $self->resInit();
+
+ # initialize TUWF::XML
+ TUWF::XML->new(
+ write => sub { print { $self->resFd } $_ for @_ },
+ pretty => $self->{_TUWF}{xml_pretty},
+ default => 1,
+ );
# initialize request
my $err = $self->reqInit();
@@ -294,22 +315,17 @@ sub _handle_request {
return 1;
}
- # initialze response
- $self->resInit();
-
- # initialize TUWF::XML
- TUWF::XML->new(
- write => sub { print { $self->resFd } $_ for @_ },
- pretty => $self->{_TUWF}{xml_pretty},
- default => 1,
- );
-
# make sure our DB connection is still there and start a new transaction
$self->dbCheck() if $self->{_TUWF}{db_login};
# call pre request handler, if any
return 1 if $self->{_TUWF}{pre_request_handler} && !$self->{_TUWF}{pre_request_handler}->($self);
+ # run 'before' hooks
+ for (@{$self->{_TUWF}{hooks}{before}}) {
+ return 1 if !$_->();
+ }
+
# find the handler
my $loc = sprintf '%s %s', $self->reqMethod(), $self->reqPath();
study $loc;
@@ -333,15 +349,29 @@ sub _handle_request {
# execute post request handler, if any
$self->{_TUWF}{post_request_handler}->($self) if $self->{_TUWF}{post_request_handler};
+ 1;
+ };
+ my $evalerr = $@;
- # commit changes
- $self->dbCommit if $self->{_TUWF}{db_login};
+ # always run 'after' hooks
+ my $cleanup = eval {
+ $_->() for (@{$self->{_TUWF}{hooks}{after}});
+ 1;
+ };
+ my $cleanuperr = $@;
+
+ # commit changes if everything went okay
+ my $commit = eval {
+ if($eval && $cleanup && $self->{_TUWF}{db_login}) {
+ $self->dbCommit;
+ }
1;
};
+ my $commiterr = $@;
# error handling
- if(!$eval) {
- chomp( my $err = $@ );
+ if(!$eval || !$cleanup || !$commit) {
+ chomp( my $err = $evalerr || $cleanuperr || $commiterr );
# act as if the changes to the DB never happened
warn $@ if $self->{_TUWF}{db_login} && !eval { $self->dbRollBack; 1 };
diff --git a/lib/TUWF.pod b/lib/TUWF.pod
index 8116e3c..fb0ccd9 100644
--- a/lib/TUWF.pod
+++ b/lib/TUWF.pod
@@ -282,6 +282,35 @@ C<TUWF::any(['get','head'], @_)>.
my $gameid = tuwf->capture(1);
};
+=head2 TUWF::hook($hook, $sub)
+
+Add a hook. This allows you to run a piece of code before or after a request
+handler. Hooks are run in the same order as they are registered. The following
+hooks are supported:
+
+=over
+
+=item before
+
+Your subroutine will be called before the request handler. The subroutine
+B<must> return a true value to indicate that TUWF can continue processing the
+request as usual. If the subroutine returns false, TUWF will assume the
+subroutine has generated a response and will halt any further processing. This
+hook can be used used to initialize or reset request-specific data (such as
+authentication), or to perform some checks that should apply to every route.
+
+This replaces the L<pre_request_handler|/pre_request_handler> setting.
+
+=item after
+
+Called after the request handler has run, but before the result has been sent
+to the client. This hook is always called, even if a I<before> hook has
+returned false or if a route handler threw an exception.
+
+This replaces the L<post_request_handler|/post_request_handler> setting.
+
+=back
+
=head2 TUWF::load(@modules)
Loads the listed module names and imports their exported functions to the
@@ -425,6 +454,8 @@ data did not make sense to TUWF. In the current implementation, this only
happens when the request data contains non-UTF8-encoded text. A warning is
written to the log file when this happens.
+B<WARNING:> The warning of L</error_500_handler> applies here as well.
+
=item error_404_handler
Set this to a subroutine reference if you want to write your own 404 error
@@ -440,11 +471,15 @@ overriding the default 405 error page makes little sense in most situations. If
you do override it, do not forget to add an C<Allow> HTTP header to the
response, as required by the HTTP standard.
+B<WARNING:> The warning of L</error_500_handler> applies here as well.
+
=item error_413_handler
Similar to I<error_404_handler>, but is called when the POST body exceeds the
configured I<max_post_body>.
+B<WARNING:> The warning of L</error_500_handler> applies here as well.
+
=item error_500_handler
Set this to a subroutine reference if you want to write your own 500 error
@@ -454,6 +489,11 @@ report will be written to the log. It is recommended to ignore the error
message passed to your subroutine and to enable the log file, so you won't risk
sending sensitive information to your visitors.
+B<WARNING:> When this handler is called, the database and request objects may
+not be in a usable state. This handler may also be called before any of the
+hooks have been run. It's best to keep this handler as simple as possible, and
+only have it generate a friendly response.
+
=item http_server_port
Port to listen on when running the standalone HTTP server. This defaults to the
@@ -528,19 +568,15 @@ The default MIME type for extensions not covered in L<mime_types|/mime_types>.
=item pre_request_handler
-Set to a subroutine reference if you want to perform some actions before TUWF
-calls your URI handling function. The subroutine B<must> return a true value to
-indicate that TUWF can continue processing the request as usual. If the
-subroutine returns false, TUWF will assume the subroutine has generated a
-response and will halt any further processing. This callback is often used
-for initializing or resetting request-specific data, and parsing cookies or
-other request data to set preferences or session information.
+(Deprecated) Equivalent to a I<before> hook, see
+L<TUWF::hook()|/TUWF::hook($hook, $sub)>.
=item post_request_handler
-Similar to I<post_request_handler>, except it will be called after the request
-has been processed but before it has been sent to the client. This callback
-will not be called if any of the functions before threw an exception.
+(Deprecated) Equivalent to an I<after> hook, see
+L<TUWF::hook()|/TUWF::hook($hook, $sub)>. One notable difference is that this
+callback will B<not> run when a I<before> hook returned false or if a route
+handler threw an exception.
=item validate_templates