root/lang/perl/Moxy/trunk/lib/Moxy.pm @ 10110

Revision 10110, 9.3 kB (checked in by tokuhirom, 6 years ago)

run メソッドは廃止されました。

Line 
1package Moxy;
2use strict;
3use warnings;
4use Class::Component;
5
6our $VERSION = '0.32';
7
8use Path::Class;
9use YAML;
10use Encode;
11use FindBin;
12use UNIVERSAL::require;
13use Carp;
14use Scalar::Util qw/blessed/;
15use URI;
16use HTML::Parser;
17use URI::Escape;
18use HTML::Entities;
19use Scalar::Util qw/blessed/;
20use LWP::UserAgent;
21use HTML::Entities;
22use URI::Escape;
23use MIME::Base64;
24use Params::Validate ':all';
25use URI::Heuristic qw(uf_uristr);
26use File::Spec::Functions;
27use YAML;
28use HTML::TreeBuilder;
29use Moxy::Util;
30use HTML::TreeBuilder::XPath;
31use HTTP::MobileAttribute plugins => [
32    qw/CarrierLetter IS/,
33    {
34        module => 'Display',
35        config => {
36            DoCoMoMap => YAML::LoadFile(
37                catfile( 'assets', 'common', 'docomo-display-map.yaml' )
38            )
39        }
40    },
41];
42
43__PACKAGE__->load_components(qw/Plaggerize Autocall::InjectMethod Context/);
44
45sub new {
46    my ($class, $config) = @_;
47
48    my $self = $class->NEXT( 'new' => { config => $config } );
49
50    $self->_init_storage;
51
52    return $self;
53}
54
55sub assets_path {
56    my $self = shift;
57
58    return $self->{__assets_path} ||= do {
59        $self->conf->{global}->{assets_path}
60            || dir( $FindBin::RealBin, 'assets' )->stringify;
61    };
62}
63
64# -------------------------------------------------------------------------
65
66sub _init_storage {
67    my ($self, ) = @_;
68
69    my $mod = $self->{config}->{global}->{storage}->{module};
70       $mod = $mod ? "Moxy::Storage::$mod" : 'Moxy::Storage::DBM_File';
71    $mod->use or die $@;
72    $self->{storage} = $mod->new($self, $self->conf->{global}->{storage} || {});
73}
74
75sub storage { shift->{storage} }
76
77# -------------------------------------------------------------------------
78
79sub run_hook_and_get_response {
80    my ($self, $hook, @args) = @_;
81
82    $self->log(debug => "Run hook and get response: $hook");
83    for my $action (@{$self->class_component_hooks->{$hook}}) {
84        my $code = $action->{plugin}->can($action->{method});
85        my $response = $code->($action->{plugin}, $self, @args);
86        return $response if blessed $response && $response->isa('HTTP::Response');
87    }
88    return; # not finished yet
89}
90
91sub rewrite {
92    my ($base, $html, $url) = @_;
93
94    my $base_url = URI->new($url);
95
96    # parse.
97    my $tree = HTML::TreeBuilder::XPath->new;
98    $tree->implicit_tags(0);
99    $tree->no_space_compacting(1);
100    $tree->ignore_ignorable_whitespace(0);
101    $tree->store_comments(1);
102    $tree->ignore_unknown(0);
103    $tree->parse($html);
104    $tree->eof;
105
106    # define replacer.
107    my $replace = sub {
108        my ( $tag, $attr_name ) = @_;
109
110        for my $node ( $tree->findnodes("//$tag") ) {
111            if ( my $attr = $node->attr($attr_name) ) {
112                $node->attr(
113                    $attr_name => sprintf( qq{%s%s%s},
114                        $base,
115                        ($base =~ m{/$} ? '' : '/'),
116                        uri_escape( URI->new($attr)->abs($base_url) ) )
117                );
118            }
119        }
120    };
121
122    # replace.
123    $replace->( 'img'    => 'src' );
124    $replace->( 'script' => 'src' );
125    $replace->( 'form'   => 'action' );
126    $replace->( 'a'      => 'href' );
127    $replace->( 'link'   => 'href' );
128
129    # dump.
130    my $result = $tree->as_HTML(q{<>"&'});
131    $tree = $tree->delete; # cleanup :-) HTML::TreeBuilder needs this.
132
133    # return result.
134    return $result;
135}
136
137sub render_control_panel {
138    my ($base, $current_url) = @_;
139
140    return sprintf(<<"...");
141<?xml version="1.0" encoding="utf-8"?>
142<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
143<html lang="ja" xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
144<head>
145    <meta http-equiv="content-script-type" content="text/javascript" />
146    <script type="text/javascript">
147        window.onload = function () {
148            document.getElementById('moxy_url').focus();
149        };
150    </script>
151</head>
152<body>
153    <form method="get" action="$base" onsubmit="location.href=location.href+encodeURIComponent(document.getElementById('moxy_url').value);return false;">
154        <input type="text" size="40" id="moxy_url" />
155        <input type="submit" value="go" />
156    </form>
157</body>
158</html>
159...
160}
161
162sub handle_request {
163    my $self = shift;
164    my %args = validate(
165        @_,
166        +{
167            request => { isa => 'HTTP::Request' },
168        }
169    );
170
171    my $uri = URI->new($args{request}->uri);
172    $self->log(debug => "Request URI: $uri");
173
174    my $base = $uri->clone;
175    $base->path('');
176    $base->query_form({});
177
178    my $auth_header = $args{request}->header('Authorization');
179    $self->log(debug => "Authorization header: $auth_header");
180    if ($auth_header =~ /^Basic (.+)$/) {
181        my $auth = decode_base64($1);
182        $self->log(debug => "auth: $auth");
183        (my $url = $uri->path_query) =~ s!^/!!;
184        $url = uf_uristr(uri_unescape $url);
185        $self->log(info => "REQUEST $auth, @{[ $url || '' ]}");
186        my $response = $self->_make_response(
187            url      => $url,
188            request  => $args{request},
189            base_url => $base,
190            user_id  => $auth,
191        );
192        return $response;
193    } else {
194        my $response = HTTP::Response->new(401, 'Moxy needs authentication');
195        $response->header( 'WWW-Authenticate' =>
196            qq{Basic realm="Moxy needs basic auth.Only for identification.Password is dummy."}
197        );
198        $response->content('authentication required');
199        return $response;
200    }
201}
202
203sub _make_response {
204    my $self = shift;
205    my %args = validate(
206        @_ => +{
207            url      => qr{^https?://},
208            request  => { isa  => 'HTTP::Request' },
209            base_url => qr{^https?://},
210            user_id  => { type => SCALAR },
211        }
212    );
213    my $url = $args{url};
214    my $base_url = $args{base_url};
215
216    if ($url) {
217        # do proxy
218        my $res = $self->_do_request(
219            url     => $url,
220            request => $args{request},
221            user_id => $args{user_id},
222        );
223        $self->log(debug => '-- response status: ' . $res->code);
224
225        if ($res->code == 302) {
226            # rewrite redirect
227            my $location = URI->new($res->header('Location'));
228            $self->log(debug => "redirect to $location");
229            my $uri = URI->new($url);
230            if ($uri->port != 80 && $location->port != $uri->port) {
231                $location->port($uri->port);
232            }
233            $res->header( 'Location' => $base_url . '/' . uri_escape( $location ) );
234            $self->log(debug => "redirect to " . $res->header('Location'));
235        } else {
236            my $content_type = $res->header('Content-Type');
237            $self->log("Content-Type: $content_type");
238            if ($content_type =~ /html/i) {
239                $res->content( encode($res->charset, rewrite($base_url, decode($res->charset, $res->content), $url)) );
240            }
241            use bytes;
242            $res->header('Content-Length' => bytes::length($res->content));
243        }
244        return $res;
245    } else {
246        # please input url.
247        my $res = HTTP::Response->new(200, 'about:blank');
248        $res->header('Content-Type' => 'text/html; charset=utf8');
249        my $panel = render_control_panel($base_url, '');
250        $res->content(qq{<html><head></head><body>$panel</body></html>});
251        return $res;
252    }
253}
254
255sub _do_request {
256    my $self = shift;
257    my %args = validate(
258        @_ => +{
259            url      => qr{^https?://},
260            request  => { isa  => 'HTTP::Request' },
261            user_id  => { type => SCALAR },
262        }
263    );
264
265    # make request
266    my $req = $args{request}->clone;
267    $req->uri($args{url});
268    $req->header('Host' => URI->new($args{url})->host);
269
270    $self->run_hook(
271        'request_filter_process_agent',
272        {   request => $req, # HTTP::Request object
273            user    => $args{user_id},
274        }
275    );
276    my $mobile_attribute = HTTP::MobileAttribute->new($req->headers);
277    my $carrier = $mobile_attribute->carrier;
278    for my $hook ('request_filter', "request_filter_$carrier") {
279        my $response = $self->run_hook_and_get_response(
280            $hook,
281            +{
282                request          => $req,              # HTTP::Request object
283                mobile_attribute => $mobile_attribute,
284                user             => $args{user_id},
285            }
286        );
287        if ($response) {
288            return $response; # finished
289        }
290    }
291
292    # do request
293    my $ua = LWP::UserAgent->new(
294        timeout           => $self->conf->{global}->{timeout} || 10,
295        max_redirects     => 0,
296        protocols_allowed => [qw/http https/],
297    );
298    my $response = $ua->request($req);
299    for my $hook ( 'response_filter', "response_filter_$carrier" ) {
300        $self->run_hook(
301            $hook,
302            {
303                response         => $response,           # HTTP::Response object
304                mobile_attribute => $mobile_attribute,
305                user             => $args{user_id},
306            }
307        );
308    }
309    $response;
310}
311
312
3131;
314__END__
315
316=head1 NAME
317
318Moxy - Mobile web development proxy
319
320=head1 DESCRIPTION
321
322Moxy is a mobile web development proxy.
323
324=head1 AUTHOR
325
326    Kan Fushihara
327    Tokuhiro Matsuno
328
329=head1 THANKS TO
330
331Kazuhiro Osawa
332
333=head1 LICENSE
334
335This library is free software; you can redistribute it and/or modify
336it under the same terms as Perl itself.
337
338=head1 SEE ALSO
339
340L<http://coderepos.org/share/wiki/ssb>
Note: See TracBrowser for help on using the browser.