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