From: Matt S Trout Date: Sun, 22 Nov 2009 08:07:00 +0000 (-0500) Subject: subdispatch X-Git-Tag: v0.003~46 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=795c4698606dcaac5f660d9c759cda2474038eae;hp=da8429c93d5e013a72456da945681cb2a84aaf80;p=catagits%2FWeb-Simple.git subdispatch --- diff --git a/examples/dispatchex/dispatchex.cgi b/examples/dispatchex/dispatchex.cgi new file mode 100644 index 0000000..9c357a5 --- /dev/null +++ b/examples/dispatchex/dispatchex.cgi @@ -0,0 +1,29 @@ +use Web::Simple 'DispatchEx'; + +package DispatchEx; + +dispatch [ + filter_response { + [ 200, [ 'Content-type' => 'text/plain' ], $_[1] ]; + }, + subdispatch sub (.html) { + [ + filter_response { [ @{$_[1]}, '.html' ] }, + sub (/foo) { [ '/foo' ] }, + ] + }, + subdispatch sub (/domain/*/...) { + return unless (my $domain_id = $_[1]) =~ /^\d+$/; + [ + sub (/) { + [ "Domain ${domain_id}" ] + }, + sub (/user/*) { + return unless (my $user_id = $_[1]) =~ /^\d+$/; + [ "Domain ${domain_id} user ${user_id}" ] + } + ] + } +]; + +DispatchEx->run_if_script; diff --git a/lib/Web/Simple.pm b/lib/Web/Simple.pm index 5307bbb..f4b28f0 100644 --- a/lib/Web/Simple.pm +++ b/lib/Web/Simple.pm @@ -36,6 +36,9 @@ sub _export_into { *{"${app_package}::redispatch_to"} = sub { $app_package->_construct_redispatch($_[0]); }; + *{"${app_package}::subdispatch"} = sub ($) { + $app_package->_construct_subdispatch($_[0]); + }; *{"${app_package}::default_config"} = sub { $app_package->_setup_default_config(@_); }; @@ -149,6 +152,8 @@ It also exports the following subroutines: redispatch_to '/somewhere'; + subdispatch sub (...) { ... } + and creates a $self global variable in your application package, so you can use $self in dispatch subs without violating strict (Web::Simple::Application arranges for dispatch subroutines to have the correct $self in scope when @@ -251,6 +256,26 @@ Thus if you receive a POST to '/some/url' and return a redipstch to '/other/url', the dispatch behaviour will be exactly as if the same POST request had been made to '/other/url' instead. +=head2 subdispatch + + subdispatch sub (/user/*/) { + my $u = $self->user($_[1]); + [ + sub (GET) { $u }, + sub (DELETE) { $u->delete }, + ] + } + +The subdispatch subroutine is designed for use in dispatcher construction. + +It creates a dispatcher which, if it matches, treats its return value not +as a final value but an arrayref of dispatch specifications such as could +be passed to the dispatch subroutine itself. These are turned into a dispatcher +which is then invoked. Any changes the match makes to the request are in +scope for this inner dispatcher only - so if the initial match is a +destructive one like .html the full path will be restored if the +subdispatch fails. + =head1 DISPATCH STRATEGY =head2 Description of the dispatcher object diff --git a/lib/Web/Simple/Application.pm b/lib/Web/Simple/Application.pm index 2a5831b..e11eef7 100644 --- a/lib/Web/Simple/Application.pm +++ b/lib/Web/Simple/Application.pm @@ -25,7 +25,7 @@ use warnings FATAL => 'all'; sub dispatch { my ($self, $env, @args) = @_; - my $next = $self->next; + my $next = $self->_has_match ? $self->next : undef; if (my ($env_delta, @match) = $self->_match_against($env)) { if (my ($result) = $self->_execute_with(@args, @match)) { if ($self->_is_dispatcher($result)) { @@ -36,9 +36,18 @@ use warnings FATAL => 'all'; } } } + return () unless $next; return $next->dispatch($env, @args); } + sub call { + @_ > 1 + ? $_[0]->{call} = $_[1] + : shift->{call} + } + + sub _has_match { $_[0]->{match} } + sub _match_against { return ({}, $_[1]) unless $_[0]->{match}; $_[0]->{match}->($_[1]); @@ -81,7 +90,9 @@ sub _construct_response_filter { $_[0]->_build_dispatcher({ call => sub { my ($d, $self, $env) = (shift, shift, shift); - $self->_run_with_self($code, $d->next->dispatch($env, $self, @_)); + my @next = $d->next->dispatch($env, $self, @_); + return unless @next; + $self->_run_with_self($code, @next); }, }); } @@ -112,36 +123,75 @@ sub _cannot_call_twice { } sub _setup_dispatcher { - my ($class, $dispatch_subs) = @_; + my ($class, $dispatch_specs) = @_; { no strict 'refs'; if (${"${class}::_dispatcher"}{CODE}) { $class->_cannot_call_twice('_setup_dispatcher', 'dispatch'); } } + my $chain = $class->_build_dispatch_chain( + [ @$dispatch_specs, $class->_build_final_dispatcher ] + ); + { + no strict 'refs'; + *{"${class}::_dispatcher"} = sub { $chain }; + } +} + +sub _construct_subdispatch { + my ($class, $dispatch_spec) = @_; + my $disp = $class->_build_dispatcher_from_spec($dispatch_spec); + my $call = $disp->call; + $disp->call(sub { + my @res = $call->(@_); + return unless @res; + my $chain = $class->_build_dispatch_chain(@res); + return $class->_build_dispatcher({ + call => sub { + my ($d, $self, $env) = (shift, shift, shift); + return $chain->dispatch($env, $self, @_); + } + }); + }); + return $class->_build_dispatcher({ + call => sub { + my ($d, $self, $env) = (shift, shift, shift); + my @sub = $disp->dispatch($env, $self, @_); + return @sub if @sub; + return unless (my $next = $d->next); + return $next->dispatch($env, $self, @_); + }, + }); +} + +sub _build_dispatcher_from_spec { + my ($class, $spec) = @_; + return $spec unless ref($spec) eq 'CODE'; + my $proto = prototype $spec; my $parser = $class->_build_dispatch_parser; + my $matcher = ( + defined($proto) + ? $parser->parse_dispatch_specification($proto) + : undef + ); + return $class->_build_dispatcher({ + match => $matcher, + call => sub { shift; + shift->_run_with_self($spec, @_) + }, + }); +} + +sub _build_dispatch_chain { + my ($class, $dispatch_specs) = @_; my ($root, $last); - foreach my $dispatch_sub (@$dispatch_subs) { - my $proto = prototype $dispatch_sub; - my $matcher = ( - defined($proto) - ? $parser->parse_dispatch_specification($proto) - : undef - ); - my $new = $class->_build_dispatcher({ - match => $matcher, - call => sub { shift; - shift->_run_with_self($dispatch_sub, @_) - }, - }); + foreach my $dispatch_spec (@$dispatch_specs) { + my $new = $class->_build_dispatcher_from_spec($dispatch_spec); $root ||= $new; $last = $last ? $last->next($new) : $new; } - $last->next($class->_build_final_dispatcher); - { - no strict 'refs'; - *{"${class}::_dispatcher"} = sub { $root }; - } + return $root; } sub _build_dispatcher {