package Dancer::Route::Cache; use strict; use warnings; use Carp; use base 'Dancer::Object'; use vars '$VERSION'; use Dancer::Config 'setting'; use Dancer::Error; use Dancer::Exception qw(:all); $VERSION = '0.01'; Dancer::Route::Cache->attributes('size_limit', 'path_limit'); # static # singleton for the current cache object my $_cache; sub get {$_cache} sub reset { $_cache = Dancer::Route::Cache->new(); $_cache->{size_limit} = setting('route_cache_size_limit') if defined setting('route_cache_size_limit'); $_cache->{path_limit} = setting('route_cache_path_limit') if defined setting('route_cache_path_limit'); } # instance sub init { my ($self, %args) = @_; $self->build_size_limit($args{'size_limit'} || '10M'); $self->build_path_limit($args{'path_limit'} || 600); } sub build_path_limit { my ($self, $limit) = @_; if ($limit) { $self->{'path_limit'} = $limit; } return $self->{'path_limit'}; } sub build_size_limit { my ($self, $limit) = @_; if ($limit) { $self->{'size_limit'} = $self->parse_size($limit); } return $self->{'size_limit'}; } sub parse_size { my ($self, $size) = @_; if ($size =~ /^(\d+)(K|M|G)?$/i) { my $base = $1; if (my $ext = $2) { $ext eq 'K' and return $base * 1024**1; $ext eq 'M' and return $base * 1024**2; $ext eq 'G' and return $base * 1024**3; } return $base; } } sub route_from_path { my ($self, $method, $path, $app_name) = @_; $method && $path or raise core_route => "Missing method or path"; $app_name = 'main' unless defined $app_name; return $self->{'cache'}{$app_name}{$method}{$path} || undef; } sub store_path { my ($self, $method, $path, $route, $app_name) = @_; $method && $path && $route or raise core_route => "Missing method, path or route"; $app_name = 'main' unless defined $app_name; $self->{'cache'}{$app_name}{$method}{$path} = $route; push @{$self->{'cache_array'}}, [$method, $path, $app_name]; if (my $limit = $self->size_limit) { while ($self->route_cache_size() > $limit) { my ($method, $path, $app_name) = @{shift @{$self->{'cache_array'}}}; delete $self->{'cache'}{$app_name}{$method}{$path}; } } if (my $limit = $self->path_limit) { while ($self->route_cache_paths() > $limit) { my ($method, $path, $app_name) = @{shift @{$self->{'cache_array'}}}; delete $self->{'cache'}{$app_name}{$method}{$path}; } } } sub route_cache_size { my $self = shift; my %cache = %{$self->{'cache'}}; my $size = 0; use bytes; foreach my $app_name (keys %cache) { $size += length $app_name; foreach my $method (keys %{$cache{$app_name}}) { $size += length $method; foreach my $path (keys %{$cache{$app_name}{$method}}) { $size += length $path; $size += length $cache{$app_name}{$method}{$path}; } } } no bytes; return $size; } sub route_cache_paths { my $self = shift; my %cache = $self->{'cache'} ? %{$self->{'cache'}} : (); return scalar map { keys %{$_} } map { values %{$cache{$_}} } keys %cache; } 1; __END__ =head1 NAME Dancer::Route::Cache - route caching mechanism for L =head1 SYNOPSIS my $cache = Dancer::Route::Cache->new( path_limit => 300, # optional ); # storing a path # /new/item/ is the path, $route is a compiled route $cache->store_path( 'get', '/new/item/', $route ); my $cached_route = $cache->route_from_path('/new/item/'); =head1 DESCRIPTION When L first starts, it has to compile a regexp list of all the routes. Then, on each request it goes over the compiled routes list and tries to compare the requested path to a route. A major drawback is that L has to go over the matching on every request, which (especially on CGI-based applications) can be very time consuming. The caching mechanism allows one to cache some requests to specific routes (but B specific results) and run those routes on a specific path. This allows us to speed up L quite a lot. =head1 METHODS/SUBROUTINES =head2 new(@args) Creates a new route cache object. my $cache = Dancer::Route::Cache->new( path_limit => 100, # only 100 paths will be cached size_limit => '30M', # max size for cache is 30MB ); Please check the C section below to learn about the arguments for C. =head2 route_from_path($path) Fetches the route from the path in the cache. =head2 store_path( $method, $path => $route ) Stores the route in the cache according to the path and $method. For developers: the reason we're using an object for this and not directly using the registry hash is because we need to enforce the limits. =head2 parse_size($size) Parses the size wanted to bytes. It can handle Kilobytes, Megabytes or Gigabytes. B handles bytes, not bits! my $bytes = $cache->parse_size('30M'); # doesn't need an existing object $bytes = Dancer::Route::Cache->parse_size('300G'); # works this way too =head2 route_cache_size Returns a rough calculation the size of the cache. This is used to enforce the size limit. =head2 route_cache_paths Returns all the paths in the cache. This is used to enforce the path limit. Please be carefull if you use L and some applications - routes are linked with applications and same path may be in some applications but with different handlers! =head1 ATTRIBUTES =head2 size_limit($limit) Allows one to set a size limit of the cache. Returns the limit (post-set). $cache->size_limit('10K'); # sets limit my $limit = $cache->size_limit; # gets limit =head2 path_limit($limit) A path limit. That is, the amount of paths that whose routes will be cached. Returns the limit (post-set). $cache->path_limit('100'); # sets limit my $limit = $cache->path_limit; # gets limit =head1 AUTHOR Sawyer X =head1 LICENSE AND COPYRIGHT Copyright 2010 Sawyer X. This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License. See http://dev.perl.org/licenses/ for more information.