File Coverage

blib/lib/AxKit2/Client.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # Copyright 2001-2006 The Apache Software Foundation
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14             #
15              
16             package AxKit2::Client;
17              
18 9     9   54 use strict;
  9         29  
  9         339  
19 9     9   50 use warnings;
  9         15  
  9         278  
20              
21 9     9   4996 use AxKit2::Plugin;
  9         30  
  9         269  
22 9     9   65 use AxKit2::Constants;
  9         18  
  9         1157  
23 9     9   6377 use AxKit2::Processor;
  0            
  0            
24             use AxKit2::Utils qw(xml_escape);
25             use Carp qw(croak);
26              
27             our %PLUGINS;
28              
29             sub load_plugin {
30             my ($class, $conf, $plugin) = @_;
31            
32             my $package;
33            
34             if ($plugin =~ m/::/) {
35             # "full" package plugin (My::Plugin)
36             $package = $plugin;
37             $package =~ s/[^_a-z0-9:]+//gi;
38             my $eval = qq[require $package;\n]
39             .qq[sub ${plugin}::plugin_name { '$plugin' }]
40             .qq[sub ${plugin}::hook_name { shift->{_hook}; }];
41             $eval =~ m/(.*)/s;
42             $eval = $1;
43             eval $eval;
44             die "Failed loading $package - eval $@" if $@;
45             $class->log(LOGDEBUG, "Loaded Plugin $package");
46             }
47             else {
48            
49             my $dir = $conf->plugin_dir || "./plugins";
50            
51             my $plugin_name = plugin_to_name($plugin);
52             $package = "AxKit2::Plugin::$plugin_name";
53            
54             # don't reload plugins if they are already loaded
55             unless ( defined &{"${package}::plugin_name"} ) {
56             AxKit2::Plugin->_compile($plugin_name,
57             $package, "$dir/$plugin");
58             }
59             }
60            
61             return if $PLUGINS{$plugin};
62            
63             my $plug = $package->new();
64             $PLUGINS{$plugin} = $plug;
65             $plug->_register();
66             }
67              
68             sub plugin_to_name {
69             my $plugin = shift;
70            
71             my $plugin_name = $plugin;
72            
73             # Escape everything into valid perl identifiers
74             $plugin_name =~ s/([^A-Za-z0-9_\/])/sprintf("_%2x",unpack("C",$1))/eg;
75              
76             # second pass cares for slashes and words starting with a digit
77             $plugin_name =~ s{
78             (/+) # directory
79             (\d?) # package's first character
80             }[
81             "::" . (length $2 ? sprintf("_%2x",unpack("C",$2)) : "")
82             ]egx;
83              
84            
85             return $plugin_name;
86             }
87              
88             sub plugin_instance {
89             my $plugin = shift;
90             return $PLUGINS{$plugin};
91             }
92              
93             sub config {
94             # should be subclassed - clients get a server config
95             AxKit2::Config->global;
96             }
97              
98             sub run_hooks {
99             my ($self, $hook) = (shift, shift);
100            
101             my $conf = $self->config();
102            
103             if (my $cached_hooks = $conf->cached_hooks($hook)) {
104             return $self->_run_hooks($conf, $hook, [@_], $cached_hooks, 0);
105             }
106            
107             my @hooks;
108             for my $plugin ($conf->plugins) {
109             my $plug = $PLUGINS{$plugin} || next;
110             push @hooks, map { [$plugin, $plug, $_] } $plug->hooks($hook);
111             }
112            
113             $conf->cached_hooks($hook, \@hooks);
114             $self->_run_hooks($conf, $hook, [@_], \@hooks, 0);
115             }
116              
117             sub finish_continuation {
118             my ($self) = @_;
119             my $todo = $self->{continuation} || croak "No continuation in progress";
120             $self->continue_read();
121             $self->{continuation} = undef;
122             my $hook = shift @$todo;
123             my $args = shift @$todo;
124             my $pos = shift @$todo;
125             my $conf = $self->config;
126             my $hooks = $conf->cached_hooks($hook);
127             $self->_run_hooks($conf, $hook, $args, $hooks, $pos+1);
128             }
129              
130             sub _run_hooks {
131             my $self = shift;
132             my ($conf, $hook, $args, $hooks, $pos) = @_;
133            
134             my $last_hook = $#$hooks;
135            
136             my @r;
137             if ($pos <= $last_hook) {
138             for my $idx ($pos .. $last_hook) {
139             my $info = $hooks->[$idx];
140             my ($plugin, $plug, $h) = @$info;
141             # $self->log(LOGDEBUG, "$plugin ($idx) running hook $hook") unless $hook eq 'logging';
142             eval { @r = $plug->$h($self, $conf, @$args) };
143             if ($@) {
144             my $err = $@;
145             $self->log(LOGERROR, "FATAL PLUGIN ERROR: $err");
146             $self->hook_error($err) unless $hook eq 'error';
147             return DONE;
148             }
149             next unless @r;
150             if (!defined $r[0]) {
151             print "r0 not defined in hook $hook\[$idx]\n";
152             }
153             if ($r[0] == CONTINUATION) {
154             $self->pause_read();
155             $self->{continuation} = [$hook, $args, $idx];
156             }
157             last unless $r[0] == DECLINED;
158             }
159             }
160            
161             $r[0] = DECLINED if not defined $r[0];
162             if ($r[0] != CONTINUATION) {
163             my $responder = "hook_${hook}_end";
164             if (my $meth = $self->can($responder)) {
165             return $meth->($self, $r[0], $r[1], @$args);
166             }
167             }
168             return @r;
169             }
170              
171             sub log {
172             my $self = shift;
173             $self->run_hooks('logging', @_);
174             }
175              
176             sub hook_connect {
177             my $self = shift;
178             $self->run_hooks('connect');
179             }
180              
181             sub hook_connect_end {
182             my $self = shift;
183             my ($ret, $out) = @_;
184             if ($ret == DECLINED || $ret == OK) {
185             # success
186             $self->run_hooks('pre_request');
187             }
188             else {
189             $self->close("connect hook closing");
190             return;
191             }
192             }
193              
194             sub hook_pre_request {
195             my $self = shift;
196             $self->run_hooks('pre_request');
197             }
198              
199             sub hook_pre_request_end {
200             my $self = shift;
201             my ($ret, $out) = @_;
202             # TODO: Manage $ret
203             return;
204             }
205              
206             sub hook_body_data {
207             my $self = shift;
208             $self->run_hooks('body_data', @_);
209             }
210              
211             sub hook_body_data_end {
212             my ($self, $ret) = @_;
213             if ($ret == DECLINED || $ret == DONE) {
214             return $self->process_request();
215             }
216             elsif ($ret == OK) {
217             return 1;
218             }
219             else {
220             $self->default_error_out($ret);
221             }
222             }
223              
224             sub hook_write_body_data {
225             my $self = shift;
226             my ($ret) = $self->run_hooks('write_body_data');
227             if ($ret == CONTINUATION) {
228             die "Continuations not supported on write_body_data";
229             }
230             elsif ($ret == DECLINED || $ret == DONE) {
231             return;
232             }
233             elsif ($ret == OK) {
234             return 1;
235             }
236             else {
237             $self->default_error_out($ret);
238             }
239             }
240              
241             sub hook_post_read_request {
242             my $self = shift;
243             $self->run_hooks('post_read_request', @_);
244             }
245              
246             sub hook_post_read_request_end {
247             my ($self, $ret, $out, $hd) = @_;
248             if ($ret == DECLINED || $ret == OK) {
249             if ($hd->request_method =~ /GET|HEAD/) {
250             return $self->process_request;
251             }
252             return;
253             }
254             elsif ($ret == DONE) {
255             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
256             }
257             else {
258             $self->default_error_out($ret);
259             }
260             }
261              
262             sub hook_uri_translation {
263             my ($self, $hd, $uri) = @_;
264             $self->run_hooks('uri_translation', $hd, $uri);
265             }
266              
267             sub hook_uri_translation_end {
268             my ($self, $ret, $out, $hd) = @_;
269             if ($ret == DECLINED || $ret == OK) {
270             return $self->run_hooks('mime_map', $hd, $hd->filename);
271             }
272             elsif ($ret == DONE) {
273             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
274             }
275             else {
276             $self->default_error_out($ret);
277             }
278             }
279              
280             sub hook_mime_map_end {
281             my ($self, $ret, $out, $hd) = @_;
282             if ($ret == DECLINED || $ret == OK) {
283             return $self->run_hooks('access_control', $hd);
284             }
285             elsif ($ret == DONE) {
286             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
287             }
288             else {
289             $self->default_error_out($ret);
290             }
291             }
292              
293             sub hook_access_control_end {
294             my ($self, $ret, $out, $hd) = @_;
295             if ($ret == DECLINED || $ret == OK) {
296             return $self->run_hooks('authentication', $hd);
297             }
298             elsif ($ret == DONE) {
299             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
300             }
301             else {
302             $self->default_error_out($ret);
303             }
304             }
305              
306             sub hook_authentication_end {
307             my ($self, $ret, $out, $hd) = @_;
308             if ($ret == DECLINED || $ret == OK) {
309             return $self->run_hooks('authorization', $hd);
310             }
311             elsif ($ret == DONE) {
312             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
313             }
314             else {
315             $self->default_error_out($ret);
316             }
317             }
318              
319             sub hook_authorization_end {
320             my ($self, $ret, $out, $hd) = @_;
321             if ($ret == DECLINED || $ret == OK) {
322             return $self->run_hooks('fixup', $hd);
323             }
324             elsif ($ret == DONE) {
325             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
326             }
327             else {
328             $self->default_error_out($ret);
329             }
330             }
331              
332             sub hook_fixup_end {
333             my ($self, $ret, $out, $hd) = @_;
334             if ($ret == DECLINED || $ret == OK) {
335             return $self->run_hooks(
336             'xmlresponse',
337             AxKit2::Processor->new($self, $hd->filename),
338             $hd);
339             }
340             elsif ($ret == DONE) {
341             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
342             }
343             else {
344             $self->default_error_out($ret);
345             }
346             }
347              
348             sub hook_xmlresponse_end {
349             my ($self, $ret, $out, $input, $hd) = @_;
350             if ($ret == DECLINED) {
351             return $self->run_hooks('response', $hd);
352             }
353             elsif ($ret == DONE) {
354             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
355             }
356             elsif ($ret == OK) {
357             $out->output() if $out;
358             $self->write(sub { $self->http_response_sent($self->headers_out->response_code) });
359             }
360             else {
361             $self->default_error_out($ret);
362             }
363             }
364              
365             sub hook_response_end {
366             my ($self, $ret, $out, $hd) = @_;
367             if ($ret == DECLINED) {
368             $self->default_error_out(NOT_FOUND);
369             }
370             elsif ($ret == OK || $ret == DONE) {
371             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
372             }
373             else {
374             $self->default_error_out($ret);
375             }
376            
377             }
378              
379             sub hook_response_sent {
380             my $self = shift;
381             $self->run_hooks('response_sent', @_);
382             }
383              
384             sub hook_response_sent_end {
385             my ($self, $ret, $out, $code) = @_;
386             if ($ret == DONE) {
387             $self->close("plugin decided not to keep connection open");
388             }
389             elsif ($ret == DECLINED || $ret == OK) {
390             return $self->http_response_sent;
391             }
392             else {
393             $self->default_error_out($ret);
394             }
395             }
396              
397             sub hook_error {
398             my $self = shift;
399             $self->headers_out->code(SERVER_ERROR);
400             $self->run_hooks('error', @_);
401             }
402              
403             sub hook_error_end {
404             my ($self, $ret) = @_;
405             if ($ret == DECLINED) {
406             $self->default_error_out(SERVER_ERROR);
407             }
408             elsif ($ret == OK || $ret == DONE) {
409             # we assume some hook handled the error
410             }
411             else {
412             $self->default_error_out($ret);
413             }
414             }
415              
416             # stolen shamelessly from httpd-2.2.2/modules/http/http_protocol.c
417             sub default_error_out {
418             my ($self, $code, $extras) = @_;
419             $extras = '' unless defined $extras;
420            
421             $self->initialize_response;
422            
423             $self->headers_out->code($code);
424            
425             if ($code == NOT_MODIFIED) {
426             $self->send_http_headers;
427             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
428             # The 304 response MUST NOT contain a message-body
429             return;
430             }
431            
432             $self->headers_out->header('Content-Type', 'text/html');
433             $self->headers_out->header('Connection', 'close');
434             $self->send_http_headers;
435            
436             $self->write("\n" .
437             "\n" .
438             "$code ".$self->headers_out->http_code_english."\n" .
439             "\n" .
440             "

".$self->headers_out->http_code_english."

\n"
441             );
442            
443             if ($code == REDIRECT) {
444             my $new_uri = $self->headers_out->header('Location')
445             || die "No Location header set for REDIRECT";
446             $self->write('The document has moved 447             xml_escape($new_uri) . "\">here.

\n");

448             }
449             elsif ($code == BAD_REQUEST) {
450             $self->write("

Your browser sent a request that this server could not understand.
\n" .

451             xml_escape($extras)."

\n");
452             }
453             elsif ($code == UNAUTHORIZED) {
454             $self->write("

This server could not verify that you\n" .

455             "are authorized to access the document\n" .
456             "requested. Either you supplied the wrong\n" .
457             "credentials (e.g., bad password), or your\n" .
458             "browser doesn't understand how to supply\n" .
459             "the credentials required.

\n");
460             }
461             elsif ($code == FORBIDDEN) {
462             $self->write("

You don't have permission to access " .

463             xml_escape($self->headers_in->uri) .
464             "\non this server.

\n");
465             }
466             elsif ($code == NOT_FOUND) {
467             $self->write("

The requested URL " .

468             xml_escape($self->headers_in->uri) .
469             " was not found on this server.

\n");
470             }
471             elsif ($code == SERVICE_UNAVAILABLE) {
472             $self->write("

The server is temporarily unable to service your\n" .

473             "request due to maintenance downtime or capacity\n" .
474             "problems. Please try again later.

\n");
475             }
476             else {
477             $self->write("The server encountered an internal error or \n" .
478             "misconfiguration and was unable to complete \n" .
479             "your request.

\n" .

480             "More information about this error may be available\n" .
481             "in the server error log.

\n");

482             }
483            
484             $self->write(<
485            
486            
487             EOT
488              
489             $self->write(sub { $self->hook_response_sent($self->headers_out->response_code) });
490             }
491              
492             1;