1package Jifty::Plugin::RequestInspector; 2use strict; 3use warnings; 4use base 'Jifty::Plugin'; 5use Time::HiRes 'time'; 6 7sub version { '0.0.2' } 8 9__PACKAGE__->mk_accessors(qw(url_filter on_cookie persistent)); 10 11my $current_inspection; 12my @requests; 13 14sub init { 15 my $self = shift; 16 return if $self->_pre_init; 17 18 my %opt = ( 19 url_filter => '.*', 20 on_cookie => undef, 21 persistent => 0, 22 @_ 23 ); 24 25 $self->url_filter(qr/$opt{url_filter}/); 26 $self->on_cookie($opt{on_cookie}); 27 $self->persistent($opt{persistent}); 28 29 Jifty::Handler->add_trigger(before_request => sub { 30 $self->before_request(@_); 31 }); 32 33 Jifty::Handler->add_trigger(after_request => sub { 34 $self->after_request(@_); 35 }); 36} 37 38sub requests { 39 my $self = shift; 40 my %args = ( 41 after => 0, 42 @_, 43 ); 44 45 if ($self->persistent) { 46 my $requests = Jifty::Plugin::RequestInspector::Model::RequestCollection->new( 47 current_user => Jifty->app_class('CurrentUser')->superuser 48 ); 49 $requests->unlimit; 50 $requests->limit( column => "id", operator => ">", value => $args{after}) if $args{after}; 51 return map { {%{$_->data}, id => $_->id} } @{$requests->items_array_ref}; 52 } else { 53 return @requests[$args{after}..$#requests]; 54 } 55} 56 57sub get_request { 58 my $self = shift; 59 my $id = shift; 60 61 if ($self->persistent) { 62 my $req = Jifty::Plugin::RequestInspector::Model::Request->new( 63 current_user => Jifty->app_class('CurrentUser')->superuser 64 ); 65 $req->load( $id ); 66 return undef unless $req->id; 67 return { %{$req->data}, id => $req->id }; 68 } else { 69 return $requests[$id - 1]; # 1-based 70 } 71} 72 73sub add_request { 74 my $self = shift; 75 76 return unless $current_inspection; 77 78 if ($self->persistent) { 79 my $req = Jifty::Plugin::RequestInspector::Model::Request->new( 80 current_user => Jifty->app_class('CurrentUser')->superuser 81 ); 82 my ($ok, $msg) = $req->create( 83 data => $current_inspection 84 ); 85 } else { 86 push @requests, $current_inspection; 87 $requests[-1]{id} = scalar @requests; 88 } 89} 90 91sub clear_requests { 92 my $self = shift; 93 94 if ($self->persistent) { 95 Jifty->handle->simple_query( "DELETE FROM ".Jifty::Plugin::RequestInspector::Model::Request->table ); 96 } else { 97 @requests = (); 98 } 99 undef $current_inspection; 100} 101 102sub last_id { 103 my $self = shift; 104 if ($self->persistent) { 105 return Jifty->handle->fetch_result( "SELECT MAX(id) FROM ". Jifty::Plugin::RequestInspector::Model::Request->table ); 106 } else { 107 return scalar @requests; 108 } 109} 110 111sub get_plugin_data { 112 my $self = shift; 113 my $id = shift; 114 my $plugin = shift; 115 116 return $self->get_request($id)->{plugin_data}{$plugin}; 117} 118 119sub get_all_plugin_data { 120 my $self = shift; 121 my $plugin = shift; 122 123 return map {$_->{plugin_data}{$plugin}} $self->requests; 124} 125 126sub new_request_inspection { 127 my ($self, $req) = @_; 128 129 my $ret = { 130 start => time, 131 url => $req->request_uri, 132 }; 133 134 if (my $cookie_name = $self->on_cookie) { 135 $ret->{cookie} = $req->cookies->{$cookie_name}; 136 } 137 return $ret; 138} 139 140do { 141 my $inspector_plugins; 142 sub inspector_plugins { 143 if (!defined($inspector_plugins)) { 144 $inspector_plugins = [ 145 grep { 146 $_->can('inspect_before_request') || 147 $_->can('inspect_after_request') 148 } Jifty->plugins 149 ]; 150 } 151 return @$inspector_plugins; 152 } 153}; 154 155sub before_request { 156 my ($self, $handler, $req) = @_; 157 158 return unless $self->should_handle_request($req); 159 160 $current_inspection = $self->new_request_inspection($req); 161 162 for my $plugin ($self->inspector_plugins) { 163 next unless $plugin->can('inspect_before_request'); 164 my $plugin_data = $plugin->inspect_before_request($req); 165 $current_inspection->{plugin_data}{ref $plugin} = $plugin_data; 166 } 167} 168 169sub after_request { 170 my ($self, $handler, $req) = @_; 171 172 if ($current_inspection) { 173 for my $plugin (reverse $self->inspector_plugins) { 174 next unless $plugin->can('inspect_after_request'); 175 my $plugin_data = $current_inspection->{plugin_data}{ref $plugin}; 176 my $new_plugin_data = 177 $plugin->inspect_after_request( $plugin_data, $req ); 178 if (defined($new_plugin_data)) { 179 $current_inspection->{plugin_data}{ref $plugin} = $new_plugin_data; 180 } 181 } 182 $current_inspection->{end} = time; 183 $self->add_request; 184 } 185 186 undef $current_inspection; 187} 188 189sub should_handle_request { 190 my $self = shift; 191 my $req = shift; 192 193 my $url = $req->request_uri; 194 return unless $url =~ $self->url_filter; 195 196 if (my $cookie_name = $self->on_cookie) { 197 return unless $req->cookies->{$cookie_name}; 198 } 199 200 return 1; 201} 202 2031; 204 205__END__ 206 207=head1 NAME 208 209Jifty::Plugin::RequestInspector - Inspect requests 210 211=head1 DESCRIPTION 212 213Do not use this plugin directly. Other plugins use this plugin. 214 215=head1 METHODS 216 217=head2 init 218 219Sets up hooks into the request cycle. 220 221=head2 before_request 222 223Hooks into the request cycle to forward "request is beginning" and more 224metadata to RequestInspector plugins. 225 226=head2 after_request 227 228Hooks into the request cycle to forward "request is done" and more metadata 229to RequestInspector plugins. 230 231=head2 clear_requests 232 233Clears the list of request inspections. 234 235=head2 add_request 236 237Adds the current request inspection to the data store. 238 239=head2 last_id 240 241Returns the most recent request ID. 242 243=head2 get_plugin_data RequestID, Plugin::Name 244 245Returns the B<opaque> plugin data for a particular request ID and plugin class 246name. 247 248=head2 get_all_plugin_data Plugin::Name 249 250Returns the B<opaque> plugin data for all requests, for a given plugin 251class name. 252 253=head2 get_request RequestID 254 255Returns all data for a particular request ID. 256 257=head2 requests 258 259Returns a list of all inspections for all requests. 260 261=head2 inspector_plugins 262 263Returns a list of plugin instances that hook into RequestInspector. 264 265=head2 new_request_inspection 266 267Instantiates a new request inspection, setting up some default values. 268 269=head2 should_handle_request CGI 270 271Decides whether the request described by the CGI parameter should be handled, 272based on plugin configuration. 273 274=head2 version 275 276This plugin is versioned because it provides model classes. 277 278=cut 279 280