speedup find_handler
authorDietmar Maurer <dietmar@proxmox.com>
Thu, 24 Apr 2014 11:49:43 +0000 (13:49 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 24 Apr 2014 12:03:49 +0000 (14:03 +0200)
Avoid to scan the same path multiple times. That way we can speedup things
by factor 5 (depends on patch length and method count).

data/PVE/RESTHandler.pm

index 08c1015..fbe5df6 100644 (file)
@@ -13,6 +13,7 @@ use Storable qw(dclone);
 
 my $method_registry = {};
 my $method_by_name = {};
+my $method_path_lookup = {};
 
 our $AUTOLOAD;  # it's a package global
 
@@ -156,25 +157,76 @@ sub register_method {
     my $match_re = [];
     my $match_name = [];
 
+    my $errprefix;
+
+    my $method;
+    if ($info->{subclass}) {
+       $errprefix = "register subclass $info->{subclass} at ${self}/$info->{path} -";
+       $method = 'SUBCLASS';
+    } else {
+       $errprefix = "register method ${self}/$info->{path} -";
+       $info->{method} = 'GET' if !$info->{method};
+       $method = $info->{method};
+    }
+
+    $method_path_lookup->{$self} = {} if !defined($method_path_lookup->{$self});
+    my $path_lookup = $method_path_lookup->{$self};
+
+    die "$errprefix no path" if !defined($info->{path});
+    
     foreach my $comp (split(/\/+/, $info->{path})) {
-       die "path compoment has zero length" if $comp eq '';
+       die "$errprefix path compoment has zero length\n" if $comp eq '';
+       my ($name, $regex);
        if ($comp =~ m/^\{(\w+)(:(.*))?\}$/) {
-           my $name = $1;
-           push @$match_re, $3 ? $3 : '\S+';
-           push @$match_name,  $1;
+           $name = $1;
+           $regex = $3 ? $3 : '\S+';
+           push @$match_re, $regex;
+           push @$match_name, $name;
+       } else {
+           $name = $comp;
+           push @$match_re, $name;
+           push @$match_name, undef;
+       }
+
+       if ($regex) {
+           $path_lookup->{regex} = {} if !defined($path_lookup->{regex});      
+
+           my $old_name = $path_lookup->{regex}->{match_name};
+           die "$errprefix found changed regex match name\n"
+               if defined($old_name) && ($old_name ne $name);
+           my $old_re = $path_lookup->{regex}->{match_re};
+           die "$errprefix found changed regex\n"
+               if defined($old_re) && ($old_re ne $regex);
+           $path_lookup->{regex}->{match_name} = $name;
+           $path_lookup->{regex}->{match_re} = $regex;
+           
+           die "$errprefix path match error - regex and fixed items\n"
+               if defined($path_lookup->{folders});
+
+           $path_lookup = $path_lookup->{regex};
+           
        } else {
-           push @$match_re, $comp;
-           push @$match_name,  undef;
+           $path_lookup->{folders}->{$name} = {} if !defined($path_lookup->{folders}->{$name});        
+
+           die "$errprefix path match error - regex and fixed items\n"
+               if defined($path_lookup->{regex});
+
+           $path_lookup = $path_lookup->{folders}->{$name};
        }
     }
 
+    die "$errprefix duplicate method definition\n" 
+       if defined($path_lookup->{$method});
+
+    $path_lookup->{$method} = $info;
+
     $info->{match_re} = $match_re;
     $info->{match_name} = $match_name;
 
     $method_by_name->{$self} = {} if !defined($method_by_name->{$self});
 
     if ($info->{name}) {
-       die "method '${self}::$info->{name}' already defined\n"
+       die "$errprefix method name already defined\n"
            if defined($method_by_name->{$self}->{$info->{name}});
 
        $method_by_name->{$self}->{$info->{name}} = $info;
@@ -217,97 +269,68 @@ sub map_method_by_name {
     return $info;
 }
 
-sub map_method {
-    my ($self, $stack, $method, $uri_param) = @_;
+sub map_path_to_methods {
+    my ($class, $stack, $uri_param) = @_;
 
-    my $ma = $method_registry->{$self};
+    my $path_lookup = $method_path_lookup->{$class};
 
-    my $stacklen = scalar(@$stack);
+    while (my $comp = shift @$stack) {
+       return undef if !$path_lookup; # not registerd?
+       if ($path_lookup->{regex}) {
+           my $name = $path_lookup->{regex}->{match_name};
+           my $regex = $path_lookup->{regex}->{match_re};
 
-    #syslog ('info', "MAPTEST:$method:$self: " . join ('/', @$stack));
-
-    foreach my $info (@$ma) {
-       #syslog ('info', "TEST0 " . Dumper($info));
-       next if !($info->{subclass} || ($info->{method} eq $method));
-       my $regexlen = scalar(@{$info->{match_re}});
-       if ($info->{subclass}) {
-           next if $stacklen < $regexlen;
+           return undef if $comp !~ m/^($regex)$/;
+           $uri_param->{$name} = $1;
+           $path_lookup = $path_lookup->{regex};
+       } elsif ($path_lookup->{folders}) {
+           $path_lookup = $path_lookup->{folders}->{$comp};
        } else {
-           next if $stacklen != $regexlen;
-       }
-
-       #syslog ('info', "TEST1 " . Dumper($info));
-
-       my $param = {};
-       my $i = 0;
-       for (; $i < $regexlen; $i++) {
-           my $comp = $stack->[$i];
-           my $re = $info->{match_re}->[$i];
-           #print "COMPARE $comp $info->{match_re}->[$i]\n";
-           my ($match) = $stack->[$i] =~ m/^($re)$/;
-           last if !defined($match);
-           if (my $name = $info->{match_name}->[$i]) {
-               $param->{$name} = $match; 
-           }
+           die "internal error";
        }
+       return undef if !$path_lookup;
 
-       next if $i != $regexlen;
-
-       #print "MATCH $info->{name}\n";
-       
-       foreach my $p (keys %$param) {
-           $uri_param->{$p} = $param->{$p};
-       }
-
-       return $info;
-    }
-}
-
-sub __find_handler_full {
-    my ($class, $method, $stack, $uri_param, $pathmatchref) = @_;
-
-    my $info;
-    eval {
-       $info = $class->map_method($stack, $method, $uri_param);
-    };
-    syslog('err', $@) if $@;
-
-    return undef if !$info;
-
-    $$pathmatchref .= '/' . $info->{path};
+       if (my $info = $path_lookup->{SUBCLASS}) {
+           $class = $info->{subclass};
 
-    if (my $subh = $info->{subclass}) {
+           my $fd = $info->{fragmentDelimiter};
 
-       my $matchlen = scalar(@{$info->{match_re}});
+           if (defined($fd)) {
+               # we only support the empty string '' (match whole URI)
+               die "unsupported fragmentDelimiter '$fd'" 
+                   if $fd ne '';
 
-       for (my $i = 0; $i < $matchlen; $i++) {
-           shift @$stack; # pop from stack
+               $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1;
+           }
+           $path_lookup = $method_path_lookup->{$class};
        }
+    }
 
-       my $fd = $info->{fragmentDelimiter};
+    return undef if !$path_lookup;
 
-       if (defined($fd)) {
+    return ($class, $path_lookup);
+}
 
-           # we only support the empty string '' (match whole URI)
-           die "unsupported fragmentDelimiter '$fd'" 
-               if $fd ne '';
+sub find_handler {
+    my ($class, $method, $path, $uri_param) = @_;
 
-           $stack = [ join ('/', @$stack) ] if scalar(@$stack) > 1;
-       }
+    my $stack = [ grep { length($_) > 0 }  split('\/+' , $path)]; # skip empty fragments
 
-       return $subh->__find_handler_full($method, $stack, $uri_param, $pathmatchref);
-    }
+    my ($handler_class, $path_info);
+    eval {
+       ($handler_class, $path_info) = $class->map_path_to_methods($stack, $uri_param);
+    };
+    my $err = $@;
+    syslog('err', $err) if $err;
 
-    return ($class, $info, $$pathmatchref);
-};
+    return undef if !($handler_class && $path_info);
 
-sub find_handler {
-    my ($class, $method, $path, $uri_param) = @_;
+    my $method_info = $path_info->{$method};
 
-    my $stack = [ grep { length($_) > 0 }  split('\/+' , $path)]; # skip empty fragments
+    return undef if !$method_info;
 
-    my $pathmatch = '';
-    return $class->__find_handler_full($method, $stack, $uri_param, \$pathmatch);
+    return ($handler_class, $method_info);
 }
 
 sub handle {