Triggering 404 error from a hook
Hi all, My application uses different databases depending on the client, which is always the first parameter in each route. Example: /:client/home /:client/list ... I’m using the following ‘before’ hook to set the database handle dbh: # Figure out client, if any my $client = param('client') || ''; unless ($client =~ /^[a-z0-9]{2,20}$/) { $client = ''; } # If $client, get client dbh if ($client) { if (!vars->{'dbh'}) { eval { var dbh => database({ driver => "mysql", host => "localhost", dbi_params => ... database => "s_".$client, ... }); } } if (!vars->{'dbh'}) { warning “Could not open client database”; # Warning is present pass; # Internal Server Error thrown } } I’d like Dancer to gracefully fall through to a 404 error in case s1_$client is not found or cannot be opened. I’ve tried “pass” and “forward”, but I receive a cryptic “Internal Server Error” which I assume is because these keywords are not valid within hooks, but I’m not sure. Any ideas on how to implement this without having to repeat this code over and over and over within each route? I’m also open to other ideas/approaches to implement this general scheme as long as I get a separate database for each :client (i.e., “create one huge database” is not a valid solution given the application requirements). Thanks in advance for any thoughts! Hermann
On Nov 9, 2015, at 6:35 PM, Hermann Calabria <hermann@ivouch.com> wrote:
# Figure out client, if any my $client = param('client') || ''; unless ($client =~ /^[a-z0-9]{2,20}$/) { $client = ''; }
# If $client, get client dbh if ($client) {
Two lines replaces all that: my $client = param('client') || ‘'; if ($client =~ /^[a-z0-9]{2,20}$/) {
eval { var dbh => database({ driver => "mysql", host => "localhost", dbi_params => ... database => "s_".$client, ... }); }
You should be caching database connections. Re-opening it on each and every hit is a bit expensive, even for a localhost connection.
I’d like Dancer to gracefully fall through to a 404 error
Why 404? That means the URI names a nonexistent resource, which is more appropriate for something like SELECT … = 0 records. This feels more like a 500 error. Unless, that is, “missing customer DB” is something you expect your users to run into.
I’ve tried “pass” and “forward”, but I receive a cryptic “Internal Server Error” which I assume is because these keywords are not valid within hooks, but I’m not sure.
Try send_error() instead. If it works, it’s more sensible anyway.
Thanks for the response!
Try send_error() instead. If it works, it’s more sensible anyway.
It doesn't appear to work. Same error as 'pass' and 'forward'. It seems maybe send_error() is not allowed within a before_hook.
You should be caching database connections. Re-opening it on each and every hit is a bit expensive, even for a localhost connection.
Hmmm does this code not do that? I'm using the Dancer Database plugin (and not the old plain DBI), just setting the database dynamically (based on $client) instead of from the typical ./config.yml. I assumed each database would be cached automatically by the plugin.
Why 404?
Because the routes are named /:client /:client/foo /:client/bar /:client/foo/bar/foobar etc... so, if a database for /:client doesn't exist, it means (within the context of my app) the URI doesn't exist. -----Original Message----- From: Warren Young Sent: Monday, November 09, 2015 9:57 PM To: Perl Dancer users mailing list Subject: Re: [dancer-users] Triggering 404 error from a hook On Nov 9, 2015, at 6:35 PM, Hermann Calabria <hermann@ivouch.com> wrote:
# Figure out client, if any my $client = param('client') || ''; unless ($client =~ /^[a-z0-9]{2,20}$/) { $client = ''; }
# If $client, get client dbh if ($client) {
Two lines replaces all that: my $client = param('client') || ‘'; if ($client =~ /^[a-z0-9]{2,20}$/) {
eval { var dbh => database({ driver => "mysql", host => "localhost", dbi_params => ... database => "s_".$client, ... }); }
You should be caching database connections. Re-opening it on each and every hit is a bit expensive, even for a localhost connection.
I’d like Dancer to gracefully fall through to a 404 error
Why 404? That means the URI names a nonexistent resource, which is more appropriate for something like SELECT … = 0 records. This feels more like a 500 error. Unless, that is, “missing customer DB” is something you expect your users to run into.
I’ve tried “pass” and “forward”, but I receive a cryptic “Internal Server Error” which I assume is because these keywords are not valid within hooks, but I’m not sure.
Try send_error() instead. If it works, it’s more sensible anyway. _______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
On Nov 9, 2015, at 11:16 PM, Hermann Calabria <hermann@ivouch.com> wrote:
It seems maybe send_error() is not allowed within a before_hook.
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
You should be caching database connections.
Hmmm does this code not do that? I'm using the Dancer Database plugin (and not the old plain DBI),
I had no idea where database() came from when I wrote that, and I don’t use that plugin anyway. If you force it to use a TCP connection by giving a host name, you can probably find out if it creates a new connection for each page load with: netstat -na | grep 3306.*ESTA | wc -l or sudo lsof -n | grep mysql.sock | wc -l (The first for TCP, the second for the Unix domain socket.)
Why 404?
Because the routes are named /:client /:client/foo /:client/bar /:client/foo/bar/foobar etc...
so, if a database for /:client doesn't exist, it means (within the context of my app) the URI doesn't exist.
Redirecting to a login or home page is probably a more common response to that sort of error, since a 404 page doesn’t help the user fix the problem. 404 would be appropriate if a logged-in user tried to go to /:client/foo/qux, for example: i.e. a clear case of a dead bookmark, or bogus hand-hacking of the URL.
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
Tried it, still got the same error. We're also using D1. However, I think I cracked it. This appears to work: if (!$client) { request->path_info('/error/notfound'); return; } Then I can either define '/error/notfound' or not, in which case no routes match which gets treated like a 404 error anyway. Perfect!
netstat -na | grep 3306.*ESTA | wc -l or sudo lsof -n | grep mysql.sock | wc -l
Thank you. Very useful trick to keep in the toolbox!
Redirecting to a login or home page is probably a more common response to that sort of error, since a 404 page doesn’t help the user fix the problem.
We have a bunch of different "clients" and they're all completely separate companies. Think "https://payroll.com/microsoft", "https://payroll.com/ibm", "https://payroll.com/apple", etc. We don't really want anyone knowing who the clients are, etc. So if they can't find their particular '/client' page, a 404 is perfect. Again, just the nuances of my particular business logic. Thanks for your help! -----Original Message----- From: Warren Young Sent: Tuesday, November 10, 2015 12:05 PM To: Perl Dancer users mailing list Subject: Re: [dancer-users] Triggering 404 error from a hook On Nov 9, 2015, at 11:16 PM, Hermann Calabria <hermann@ivouch.com> wrote:
It seems maybe send_error() is not allowed within a before_hook.
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
You should be caching database connections.
Hmmm does this code not do that? I'm using the Dancer Database plugin (and not the old plain DBI),
I had no idea where database() came from when I wrote that, and I don’t use that plugin anyway. If you force it to use a TCP connection by giving a host name, you can probably find out if it creates a new connection for each page load with: netstat -na | grep 3306.*ESTA | wc -l or sudo lsof -n | grep mysql.sock | wc -l (The first for TCP, the second for the Unix domain socket.)
Why 404?
Because the routes are named /:client /:client/foo /:client/bar /:client/foo/bar/foobar etc...
so, if a database for /:client doesn't exist, it means (within the context of my app) the URI doesn't exist.
Redirecting to a login or home page is probably a more common response to that sort of error, since a 404 page doesn’t help the user fix the problem. 404 would be appropriate if a logged-in user tried to go to /:client/foo/qux, for example: i.e. a clear case of a dead bookmark, or bogus hand-hacking of the URL. _______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
A followup question... is there a way to pass parameters by modifying the request within a hook? I'm using this to change the route when the user is not signed in: if (!$signed_in) { request->path_info('/signin'); } But I would love to pass the original route as a parameter to the /signin form, so the user can jump right back to wherever they tried to go. Something along the lines of: if (!$signed_in) { request->path_info('/signin', { next_url => request->path() }); } And just as a reminder, this is D1, and 'forward' does not appear to work within hooks... Thanks -----Original Message----- From: Hermann Calabria Sent: Tuesday, November 10, 2015 5:31 PM To: Perl Dancer users mailing list Subject: Re: [dancer-users] Triggering 404 error from a hook
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
Tried it, still got the same error. We're also using D1. However, I think I cracked it. This appears to work: if (!$client) { request->path_info('/error/notfound'); return; } Then I can either define '/error/notfound' or not, in which case no routes match which gets treated like a 404 error anyway. Perfect!
netstat -na | grep 3306.*ESTA | wc -l or sudo lsof -n | grep mysql.sock | wc -l
Thank you. Very useful trick to keep in the toolbox!
Redirecting to a login or home page is probably a more common response to that sort of error, since a 404 page doesn’t help the user fix the problem.
We have a bunch of different "clients" and they're all completely separate companies. Think "https://payroll.com/microsoft", "https://payroll.com/ibm", "https://payroll.com/apple", etc. We don't really want anyone knowing who the clients are, etc. So if they can't find their particular '/client' page, a 404 is perfect. Again, just the nuances of my particular business logic. Thanks for your help! -----Original Message----- From: Warren Young Sent: Tuesday, November 10, 2015 12:05 PM To: Perl Dancer users mailing list Subject: Re: [dancer-users] Triggering 404 error from a hook On Nov 9, 2015, at 11:16 PM, Hermann Calabria <hermann@ivouch.com> wrote:
It seems maybe send_error() is not allowed within a before_hook.
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
You should be caching database connections.
Hmmm does this code not do that? I'm using the Dancer Database plugin (and not the old plain DBI),
I had no idea where database() came from when I wrote that, and I don’t use that plugin anyway. If you force it to use a TCP connection by giving a host name, you can probably find out if it creates a new connection for each page load with: netstat -na | grep 3306.*ESTA | wc -l or sudo lsof -n | grep mysql.sock | wc -l (The first for TCP, the second for the Unix domain socket.)
Why 404?
Because the routes are named /:client /:client/foo /:client/bar /:client/foo/bar/foobar etc...
so, if a database for /:client doesn't exist, it means (within the context of my app) the URI doesn't exist.
Redirecting to a login or home page is probably a more common response to that sort of error, since a 404 page doesn’t help the user fix the problem. 404 would be appropriate if a logged-in user tried to go to /:client/foo/qux, for example: i.e. a clear case of a dead bookmark, or bogus hand-hacking of the URL. _______________________________________________ dancer-users mailing list dancer-users@dancer.pm http://lists.preshweb.co.uk/mailman/listinfo/dancer-users
On Nov 11, 2015, at 6:31 PM, Hermann Calabria <hermann@ivouch.com> wrote:
is there a way to pass parameters by modifying the request within a hook?
It sounds like you don’t yet know about the Dancer DSL “var” keyword. It will let you pass a value determined in the “before” hook down to the route handler.
And just as a reminder, this is D1, and 'forward' does not appear to work within hooks…
I don’t think you want to use “forward” here anyway. That’s for chaining route handlers, so that the route the client went to is actually handled by some other route, without redirecting the client. So, /client/lsi could actually be handled by /client/dell, and your users wouldn’t know it. I did some testing, and if this cryptic “Internal Server Error” you’re getting is just that, three words and nothing else, then never mind my previous comment. (I thought you were getting the detailed development-mode Dancer error page.) I see it here, too, but only on D1. In D2, “forward” from the “before” hook works! Replace the “get” handler in a “dancer2 -a fwdtest” app’s main module with this: hook before => sub { if (request->path =~ m{^/client}) { forward '/login' if request->path !~ m{/initech}; } }; get '/' => sub { forward '/login'; }; get '/login' => sub { template 'index'; }; get '/client/:client' => sub { return join(' ', "I am a client:", param 'client'); }; This is the sort of scheme I’m recommending. The “before” hook verifies that the client is one of the known clients, and if so, it redirects/forwards the caller to /login if it isn’t. The current “forward” code means that if you go to /client/bob, you get the login page (really, the default index page) but the URL in the browser’s Location bar remains /client/bob. If you change it to a redirect call, the browser loads /login itself, which I think makes more sense; plus, it works on D1. By the way, I had to move :client underneath /client to avoid a redirect loop here, since a variable at the top level basically matches everything. You need to give Dancer’s route handler some unique bit to avoid this.
On Nov 10, 2015, at 6:31 PM, Hermann Calabria <hermann@ivouch.com> wrote:
We use redirect() for similar purposes here on D1, and it works. Maybe that will suffice for you.
Tried it, still got the same error. We're also using D1.
Well, our web app’s “before” hook has a “redirect ‘/login’” line in it that gets executed frequently. I assure you, it does work. Maybe you should post the HTML of that “cryptic internal server error” page here. That page’s contents are intended to be meaningful to the Dancer app’s developer, but if you can’t make any sense of it, maybe someone will be willing to analyze it for you.
However, I think I cracked it. This appears to work:
if (!$client) { request->path_info('/error/notfound'); return; }
That appears to be an undocumented feature, which may not work in D2. I do see that there are a few places in the D1 tutorial material that use this mechanism without really explaining it. The D1 Request class reference doesn’t document it, and the corresponding D2 page describes it only as a copy of an environment variable, not as a property that, when set, causes some definite action. Also, the D2 tutorial material doesn’t seem to include these uses of path_info.
"https://payroll.com/apple", etc. We don't really want anyone knowing who the clients are, etc.
That sounds like security through obscurity. An attacker just has to guess a bunch of plausible URLs, then start down the list of less plausible ones until he finds one that works. It’s no different from password guessing, a technology that’s been developed to a fine art. Meanwhile, when your clients fat-finger their URL, they get an ugly 404 error, instead of being sent back to a page that could actually help them.
participants (2)
-
Hermann Calabria -
Warren Young