[dancer-users] Dancer crashing

Hermann Calabria hermann at ivouch.com
Fri May 17 01:42:27 BST 2019


Hi all,

Still (3 months later) struggling with this one.  I’m still seeing the occasional crashes, and in addition – and this is new -- tests using Dancer::Test are failing consistency which may be yet another clue.

I’ve narrowed the potential culprit to fancy route handling being done by my app.  I’m posting here functioning and well-commented code (i.e. works most of the time except when it crashes as previously described) for any comments or thoughts.  

As an alternative way to fix the problem, I also welcome alternative ways to accomplish what we’re trying to accomplish (as described in comments):

use strict;
use warnings;

use Dancer;
 
=item

Each "instance" of the app is accessed via a URL prefix representing each "client".  Examples:
  
  Various paths within instance for client #1, called "mary":
    http://www.myapp.com/mary
    http://www.myapp.com/mary/foo
    http://www.myapp.com/mary/foo/bar

  Various paths within instance for client #2, called "susan":
    http://www.myapp.com/susan
    http://www.myapp.com/susan/foo
    http://www.myapp.com/susan/foo/bar

  The various paths are implemented as Dancer Routes:
    GET '/instance/...'
  Examples:
    POST '/instance/bar/api/users'
    GET '/instance/foo/bar'

We trick Dancer by substituting the instance URL with "/instance". All such trickery is contained within this 
one file. Here's how it works:

1. Dancer will attempt to match path to anything in ./public, and return file. However, since instance-specific
paths will be along the lines of http://www.myapp.com/mary/my.css, no match will occur, since all 
instance-specific assets are under ./public/instance.

2. Dancer attempts to match by route. Very first route is qr{.*} route (see below). However, before this route
runs, hook 'before' will be triggered...

3a. hook 'before' looks for instance URL prefixes (such as "/mary", "/susan" above). If match:
   - change path to '/instance/..' 
   - if path points to a ./public resource [/instance/(static|cached)], return to qr{.*} immediately
   - set vars->{'client'} to client id, such as "mary" or "susan" above
   - return to qr{.*} route

3b. If no match under 3a, return to qr{.*}

4. Back in qr{.*}:
   - if route path is an instance-specific public asset [/instance/(static|cached)], send it manually using Dancer's send_file()
   - otherwise pass(), which leads to remaining default Dancer route handling:
      - if route path matches a remaining Dancer route, that route will be executed
      - if no matches, Dancer creates 404/not_found error (unless final route is another catch-all for special handling of 404s)

=cut


any qr{.*} => sub {

  my $request_path = request->path_info;

  # Manually send files in ./public/instance/(static|cached)
  if ( $request_path =~ m{^/(instance/(static|cached)(/.*|\z))} ) {
    send_file( $1 );
    return;   # per Dancer docs, route exits after send_file(). return is unnecessary; added for readability.
  }

  # causes Dancer to attempt to match to all remaining routes, with path as modified by hook
  pass();
};



hook 'before' => sub {

  my $request_path = request->path_info();

  #
  # /instance
  # (already did all hook_before operations when first entered as /[instance_url], so skip now)
  #
  if ( $request_path =~ m{^/instance(/|\z)} ) {
    return;
  }


  #
  # /[instance_url]
  #
  # If match,
  #   1. converts $request_path & request->path_info to /instance/...
  #   2. sets $instance
  #
  # If no match, returns
  #
  my $instance;
  if ( $request_path =~ m{^/([^/]+)(/.*|\z)} ) {
    my $url = $1;
    my $therest = $2;

    if ( $url =~ /^(mary|susan)$/ ) {  # simplified; actual code uses a database
      $instance = $url;
      $request_path = "/instance" . $therest;
      request->path_info( $request_path );
    }

  }
  if ( !defined($instance) ) {
    return;
  }


  #
  # /instance (converted from /[instance_url])
  # with $instance
  #
  
  # hole for ./instance/(static|cached)
  if ( $request_path =~ m{^/instance/(static|cached)(/|\z)} ) {
    return;
  }

  
  # set instance-specific var
  var instance => $instance;


  #
  # Only allow access to specific /instance areas, and in some cases require authentication
  #

  # /instance - welcome
  if ( $request_path =~ m{^/instance/?\z} ) {
    return;
  }

  # /instance/register - Register
  if ( $request_path =~ m{^/instance/register(/|\z)} ) {
    return; 
  }

  # /instance/signedin - Signed-in area (requires authentication)
  if ( $request_path =~ m{^/instance/signedin(/|\z)} ) {
    if (!vars->{'not_signed_in'}) {
      request->path_info( "/instance/register");
    }
    return;
  }

  # /instance/(everything else)
  request->path_info('/instance/error/notfound');
  return;

};


#
# /instance routes
#

get '/instance' => sub {
  return "Welcome, ".vars->{'instance'};
};

get '/instance/register' => sub {
  return "Please register, ".vars->{'instance'};
};

get '/instance/signedin' => sub {
  return "This is the signed-in area, ".vars->{'instance'}.". You are authenticated";
};

get '/instance/error/not/found' => sub {
  return "OOPS! 404 Error";
};


dance;


Test results:

use Dancer::Test;
…
response_status_is [GET => "/instance"], 200;   # ok
response_status_is [GET => "/mary"], 200;       # fails. Gets 404.
response_status_is [GET => "/susan"], 200;      # fails. Gets 404.


Thoughts/ideas?

Thank you!!
Hermann


From: David Precious
Sent: Tuesday, March 19, 2019 6:17 AM
To: dancer-users at dancer.pm
Subject: Re: [dancer-users] Dancer crashing

On Wed, 13 Mar 2019 09:58:54 -0700
Chad Wallace <cwallace at lodgingcompany.com> wrote:
> Here are lines 396 and 397 of HTTP/Server/Simple.pm (version 0.52):
> 
>         my $remote_sockaddr = getpeername( $self->stdio_handle );
>         my $family = sockaddr_family($remote_sockaddr);
> 
> So $remote_sockaddr is undef.  It should avoid calling
> sockaddr_family() in that case.

It looks like Chad is right - it would appear to be a problem with
HTTP::Server::Simple.

That means (a) it should be reported there, (b) it should only affect
you while running Dancer standalone.

You could, if you're keen, patch your local HTTP::Server::Simple with a
check that the call to getpeername( $self->stdio_handle ) did indeed
return something useful, before then passing that to sockaddr_family(),
and if not, emit as much potentially-useful debug info as you can, to
try to isolate the conditions under which this could happen.

Some form of stress-testing might help, too - maybe it's a timing
issue, a race condition where another request arrives while a previous
one is in a certain state, where the client (or another/previous
client) drops its connection right whne the server isn't expecting it,
or similar?

Cheers

Dave P
_______________________________________________
dancer-users mailing list
dancer-users at dancer.pm
http://lists.preshweb.co.uk/mailman/listinfo/dancer-users

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.preshweb.co.uk/pipermail/dancer-users/attachments/20190516/1342c29d/attachment.html>


More information about the dancer-users mailing list