Skip to content

Commit

Permalink
added support for non-blocking operations in bridges to Mojolicious::…
Browse files Browse the repository at this point in the history
…Routes
  • Loading branch information
kraih committed Aug 27, 2013
1 parent 84188c0 commit d279cfb
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 21 deletions.
5 changes: 4 additions & 1 deletion Changes
@@ -1,5 +1,8 @@

4.28 2013-08-27
4.28 2013-08-28
- Added support for non-blocking operations in bridges to
Mojolicious::Routes.
- Fixed automatic rendering bug in Mojolicious::Routes.

4.27 2013-08-26
- Added acceptors attribute to Mojo::Server::Daemon.
Expand Down
28 changes: 28 additions & 0 deletions lib/Mojolicious/Guides/Routing.pod
Expand Up @@ -573,6 +573,34 @@ be broken, this makes bridges a very powerful tool for authentication.
});
$foo->route('/bar')->to(controller => 'foo', action => 'bar');

To allow non-blocking operations to finish before continuing the dispatch
chain, you can suspend it by returning a C<Scalar> reference. A callback
leading to the next dispatch cycle will be automatically assigned to it.

# /foo -> undef
# /foo/bar -> {cb => sub {...}}
# -> {controller => 'foo', action => 'bar'}
my $foo = $r->bridge('/foo')->to(cb => sub {
my $self = shift;

# Wait 3 seconds and then give visitors a 50% chance to continue
my $continue;
Mojo::IOLoop->timer(3 => sub {

# Loser
return $self->render(text => 'No luck.') unless int rand 2;

# Winner
$continue->();
});

return \$continue;
});
$foo->route('/bar')->to(controller => 'foo', action => 'bar');

Automatic rendering will be disabled after the dispatch chain has been
suspended.

=head2 More convenient routes

From the tutorial you should already know L<Mojolicious::Lite> routes, which
Expand Down
43 changes: 23 additions & 20 deletions lib/Mojolicious/Routes.pm
Expand Up @@ -59,7 +59,8 @@ sub dispatch {
}
}

return undef unless $self->_walk($c);
my @copy = @{$c->match->stack};
return undef unless @copy && $self->_next($c, \@copy);
$self->auto_render($c);
return 1;
}
Expand Down Expand Up @@ -94,7 +95,7 @@ sub _add {

sub _callback {
my ($self, $c, $field, $last) = @_;
$c->stash->{'mojo.routed'}++;
$c->stash->{'mojo.routed'}++ if $last;
my $app = $c->app;
$app->log->debug('Routing to a callback.');
return _action($app, $c, $field->{cb}, $last);
Expand Down Expand Up @@ -170,7 +171,8 @@ sub _controller {

if (my $sub = $new->can($method)) {
$old->stash->{'mojo.routed'}++ if $last;
return 1 if _action($app, $new, $sub, $last);
my $continue = _action($app, $new, $sub, $last);
return $continue if $continue;
}

else { $log->debug('Action not found in controller.') }
Expand All @@ -193,29 +195,30 @@ sub _load {
return ++$self->{loaded}{$app};
}

sub _walk {
my ($self, $c) = @_;
sub _next {
my ($self, $c, $stack) = @_;

return 1 unless my $field = shift @$stack;

my $stack = $c->match->stack;
return undef unless my $nested = @$stack;
# Merge captures into stash
my @keys = keys %$field;
my $stash = $c->stash;
$stash->{'mojo.captures'} ||= {};
for my $field (@$stack) {
$nested--;
@{$stash}{@keys} = @{$stash->{'mojo.captures'}}{@keys} = values %$field;

# Merge captures into stash
my @keys = keys %$field;
@{$stash}{@keys} = @{$stash->{'mojo.captures'}}{@keys} = values %$field;
my $continue
= $field->{cb}
? $self->_callback($c, $field, !@$stack)
: $self->_controller($c, $field, !@$stack);

my $continue
= $field->{cb}
? $self->_callback($c, $field, !$nested)
: $self->_controller($c, $field, !$nested);
# Break the chain
return undef if !!@$stack && !$continue;

# Break the chain
return undef if $nested && !$continue;
}
# Next
return $self->_next($c, $stack) unless ref $continue eq 'SCALAR';

# Delay
$c->render_later;
$$continue = sub { $self->_next($c, $stack) };
return 1;
}

Expand Down
5 changes: 5 additions & 0 deletions t/mojolicious/app.t
Expand Up @@ -453,6 +453,11 @@ $t->get_ok('/staged' => {'X-Pass' => 1})->status_is(200)
$t->get_ok('/staged')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('Go away!');

# MojoliciousTestController::Foo::delayed
$t->get_ok('/delayed')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Delayed' => '1, 1')->content_is('Have fun!');

# MojoliciousTest::Foo::config
$t->get_ok('/stash_config')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('123');
Expand Down
30 changes: 30 additions & 0 deletions t/mojolicious/group_lite_app.t
Expand Up @@ -20,6 +20,25 @@ get '/expiration' => sub {
$self->render(text => $self->session('expiration'));
};

under('/missing' => sub {1})->route->to('does_not_exist#not_at_all');

under '/delayed' => sub {
my $self = shift;

my $continue;
Mojo::IOLoop->timer(
0 => sub {
return $self->render(text => 'stopped!') unless $self->param('ok');
$self->stash(delayed => 'delayed!');
$continue->();
}
);

return \$continue;
};

get '/' => sub { shift->render(inline => '<%= $delayed %>\\') };

under sub {
my $self = shift;
return undef unless $self->req->headers->header('X-Bender');
Expand Down Expand Up @@ -184,6 +203,17 @@ $t->get_ok('/expiration?redirect=1')->status_is(200)
ok !$t->tx->res->cookie('mojolicious')->expires, 'no expiration';
$t->reset_session;

# Missing action behind bridge
$t->get_ok('/missing')->status_is(404);

# Delayed bridge
$t->get_ok('/delayed?ok=1')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('delayed!');

# Delayed bridge (stopped)
$t->get_ok('/delayed?ok=0')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('stopped!');

# Authenticated with header
$t->get_ok('/with_under' => {'X-Bender' => 'Rodriguez'})->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
Expand Down
4 changes: 4 additions & 0 deletions t/mojolicious/lib/MojoliciousTest.pm
Expand Up @@ -144,6 +144,10 @@ sub startup {
my $b = $r->bridge('/staged')->to('foo#stage1', return => 1);
$b->route->to(action => 'stage2');

# /delayed (delayed bridge)
$r->bridge('/delayed')->to('foo#delayed')->bridge->to('foo#delayed')
->route->to('foo#fun');

# /shortcut/act
# /shortcut/ctrl
# /shortcut/ctrl-act (shortcuts to controller#action)
Expand Down
14 changes: 14 additions & 0 deletions t/mojolicious/lib/MojoliciousTest/Foo.pm
Expand Up @@ -11,6 +11,20 @@ sub config {
$self->render(text => $self->stash('config')->{test});
}

sub delayed {
my $self = shift;

my $continue;
Mojo::IOLoop->timer(
0 => sub {
$self->res->headers->append('X-Delayed' => 1);
$continue->();
}
);

return \$continue;
}

sub fun { shift->render(text => 'Have fun!') }

sub index {
Expand Down

0 comments on commit d279cfb

Please sign in to comment.