X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FWeb%2FSimple%2FApplication.pm;h=e11eef71eead64b746279de0475dd272cd4f4a7b;hb=795c4698606dcaac5f660d9c759cda2474038eae;hp=d5cc731241bcd9477b93f84a50f139f164c3f0dd;hpb=3d5e4d2d72a343354f0fc95795442b42d4b6c729;p=catagits%2FWeb-Simple.git diff --git a/lib/Web/Simple/Application.pm b/lib/Web/Simple/Application.pm index d5cc731..e11eef7 100644 --- a/lib/Web/Simple/Application.pm +++ b/lib/Web/Simple/Application.pm @@ -3,12 +3,82 @@ package Web::Simple::Application; use strict; use warnings FATAL => 'all'; +{ + package Web::Simple::Dispatcher; + + sub _is_dispatcher { + ref($_[1]) + and "$_[1]" =~ /\w+=[A-Z]/ + and $_[1]->isa(__PACKAGE__); + } + + sub next { + @_ > 1 + ? $_[0]->{next} = $_[1] + : shift->{next} + } + + sub set_next { + $_[0]->{next} = $_[1]; + $_[0] + } + + sub dispatch { + my ($self, $env, @args) = @_; + 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)) { + $next = $result->set_next($next); + $env = { %$env, %$env_delta }; + } else { + return $result; + } + } + } + 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]); + } + + sub _execute_with { + $_[0]->{call}->(@_); + } +} + sub new { my ($class, $data) = @_; my $config = { $class->_default_config, %{($data||{})->{config}||{}} }; bless({ config => $config }, $class); } +sub _setup_default_config { + my $class = shift; + { + no strict 'refs'; + if (${"${class}::_default_config"}{CODE}) { + $class->_cannot_call_twice('_setup_default_config', 'default_config'); + } + } + my @defaults = (@_, $class->_default_config); + { + no strict 'refs'; + *{"${class}::_default_config"} = sub { @defaults }; + } +} + sub _default_config { () } sub config { @@ -16,80 +86,132 @@ sub config { } sub _construct_response_filter { - bless($_[1], 'Web::Simple::ResponseFilter'); -} - -sub _is_response_filter { - # simple blessed() hack - "$_[1]" =~ /\w+=[A-Z]/ - and $_[1]->isa('Web::Simple::ResponseFilter'); + my $code = $_[1]; + $_[0]->_build_dispatcher({ + call => sub { + my ($d, $self, $env) = (shift, shift, shift); + my @next = $d->next->dispatch($env, $self, @_); + return unless @next; + $self->_run_with_self($code, @next); + }, + }); } sub _construct_redispatch { - bless(\$_[1], 'Web::Simple::Redispatch'); -} - -sub _is_redispatch { - return unless - "$_[1]" =~ /\w+=[A-Z]/ - and $_[1]->isa('Web::Simple::Redispatch'); - return ${$_[1]}; + my ($self, $new_path) = @_; + $self->_build_dispatcher({ + call => sub { + shift; + my ($self, $env) = @_; + $self->_dispatch({ %{$env}, PATH_INFO => $new_path }) + } + }) } -sub _dispatch_parser { +sub _build_dispatch_parser { require Web::Simple::DispatchParser; return Web::Simple::DispatchParser->new; } -sub _setup_dispatchables { - my ($class, $dispatch_subs) = @_; - my $parser = $class->_dispatch_parser; - my @dispatchables; - foreach my $dispatch_sub (@$dispatch_subs) { - my $proto = prototype $dispatch_sub; - my $matcher = ( - defined($proto) - ? $parser->parse_dispatch_specification($proto) - : sub { ({}) } - ); - push @dispatchables, [ $matcher, $dispatch_sub ]; +sub _cannot_call_twice { + my ($class, $method, $sub) = @_; + my $error = "Cannot call ${method} twice for ${class}"; + if ($sub) { + $error .= " - did you call Web::Simple's ${sub} export twice?"; } + die $error; +} + +sub _setup_dispatcher { + my ($class, $dispatch_specs) = @_; { no strict 'refs'; - *{"${class}::_dispatchables"} = sub { @dispatchables }; + 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 handle_request { - my ($self, $env) = @_; - $self->_run_dispatch_for($env, [ $self->_dispatchables ]); -} - -sub _run_dispatch_for { - my ($self, $env, $dispatchables) = @_; - my @disp = @$dispatchables; - while (my $disp = shift @disp) { - my ($match, $run) = @{$disp}; - if (my ($env_delta, @args) = $match->($env)) { - my $new_env = { %$env, %$env_delta }; - if (my ($result) = $self->_run_with_self($run, @args)) { - if ($self->_is_response_filter($result)) { - return $self->_run_with_self( - $result, - $self->_run_dispatch_for($new_env, \@disp) - ); - } elsif (my $path = $self->_is_redispatch($result)) { - $new_env->{PATH_INFO} = $path; - return $self->_run_dispatch_for($new_env, $dispatchables); - } - return $result; +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_spec (@$dispatch_specs) { + my $new = $class->_build_dispatcher_from_spec($dispatch_spec); + $root ||= $new; + $last = $last ? $last->next($new) : $new; } - return [ - 500, [ 'Content-type', 'text/plain' ], - [ 'The management apologises but we have no idea how to handle that' ] - ]; + return $root; +} + +sub _build_dispatcher { + bless($_[1], 'Web::Simple::Dispatcher'); +} + +sub _build_final_dispatcher { + shift->_build_dispatcher({ + call => sub { + [ + 500, [ 'Content-type', 'text/plain' ], + [ 'The management apologises but we have no idea how to handle that' ] + ] + } + }) +} + +sub _dispatch { + my ($self, $env) = @_; + $self->_dispatcher->dispatch($env, $self); } sub _run_with_self { @@ -110,15 +232,15 @@ sub run_if_script { sub _run_cgi { my $self = shift; require Web::Simple::HackedPlack; - Plack::Server::CGI->run(sub { $self->handle_request(@_) }); + Plack::Server::CGI->run(sub { $self->_dispatch(@_) }); } sub run { my $self = shift; if ($ENV{GATEWAY_INTERFACE}) { - $self->_run_cgi; + return $self->_run_cgi; } - my $path = shift(@ARGV); + my $path = shift(@ARGV) or die "No path passed - use $0 / for root"; require HTTP::Request::AsCGI; require HTTP::Request::Common;