root/lang/perl/MENTA/trunk/lib/MENTA.pm @ 28752

Revision 28752, 9.9 kB (checked in by gfx, 4 years ago)

optimize decode_input/encode_input when suppert_mobile is true

Line 
1package MENTA;
2use strict;
3use warnings;
4use utf8;
5use CGI::ExceptionManager;
6use MENTA::Dispatch ();
7
8require Class::Accessor::Lite;
9require MENTA::Context;
10require Text::MicroTemplate;
11
12our $VERSION = '0.13';
13our $REQ;
14our $CONFIG;
15our $STASH;
16
17sub import {
18    strict->import;
19    warnings->import;
20    utf8->import;
21}
22
23{
24    our $context;
25    sub context { $context }
26    sub run_context {
27        my ($class, $config, $req, $engine, $code) = @_;
28        local $context = MENTA::Context->new(
29            config   => $config,
30            request  => $req,
31            __engine => $engine,
32        );
33        $code->();
34    }
35}
36
37{
38    # Class::Trigger はロードに時間かかるので自前で実装してる
39    my $static_triggers;
40    sub call_trigger {
41        my ($class, $triggername, @args) = @_;
42        my $c = context();
43        for my $code (@{$c->{triggers}->{$triggername}}, @{ $static_triggers->{triggers}->{$triggername} || [] }) {
44            $code->($c, @args);
45        }
46    }
47
48    sub add_trigger {
49        my ($class, $triggername, $code) = @_;
50        if (ref context()) {
51            push @{context()->{triggers}->{$triggername}}, $code;
52        } else {
53            push @{$static_triggers->{triggers}->{$triggername}}, $code;
54        }
55    }
56}
57
58# run as cgi
59sub run_menta {
60    my ($class, $config) = @_;
61    $class->create_engine($config, 'MinimalCGI')->run;
62}
63
64sub create_engine {
65    my ($class, $config, $interface) = @_;
66
67    my $engine;
68    $engine = HTTP::Engine->new(
69        interface => {
70            module => $interface,
71            request_handler => sub {
72                my $req = shift;
73                local $MENTA::STASH;
74                CGI::ExceptionManager->run(
75                    callback => sub {
76                        MENTA->run_context(
77                            $config, $req, $engine, sub {
78                                MENTA->call_trigger('BEFORE_DISPATCH');
79                                MENTA::Dispatch->dispatch()
80                            }
81                        );
82                    },
83                    powered_by => '<strong>MENTA</strong>, Web Application Framework.',
84                    stacktrace_class => 'HTTPEngine',
85                    ($config->{menta}->{fatals_to_browser} ? () : (renderer => sub { "INTERNAL SERVER ERROR!" x 100 }))
86                );
87            }
88        }
89    );
90}
91
92sub config () { MENTA->context->config }
93
94sub escape_html {
95    local $_ = shift;
96    return $_ unless $_;
97    s/&/&amp;/g;
98    s/>/&gt;/g;
99    s/</&lt;/g;
100    s/"/&quot;/g;
101    s/'/&#39;/g;
102    return $_;
103}
104
105sub unescape_html {
106    local $_ = shift;
107    return $_ unless $_;
108    s/&gt;/>/g;
109    s/&lt;/</g;
110    s/&quot;/"/g;
111    s/&#0*39;/'/g;
112    s/&amp;/&/g;
113    return $_;
114}
115
116sub raw_string {
117    my $s = shift;
118    ref $s eq 'Text::MicroTemplate::EncodedString'
119        ? $s
120            : bless \$s, 'Text::MicroTemplate::EncodedString';
121}
122
123sub mt_cache_dir {
124    # $> は $EFFECTIVE_USER_ID です。詳しくは perldoc perlvar を参照。
125    my $cachedir = config->{menta}->{cache_dir};
126    return $cachedir if $cachedir;
127
128    require File::Spec;;
129    return File::Spec->catfile(File::Spec->tmpdir(), "menta.${MENTA::VERSION}.$>.mt_cache");
130}
131
132sub base_dir {
133    config->{menta}->{__processed_base_dir} ||= do {
134        my $basedir = config->{menta}->{base_dir};
135        unless ($basedir) {
136            require Cwd;
137            $basedir = Cwd::cwd();
138        }
139        $basedir =~ s!([^/])$!$1/!;
140        $basedir;
141    };
142}
143
144sub controller_dir {
145    config->{menta}->{controller_dir} ||= base_dir() . 'app/controller/';
146    config->{menta}->{controller_dir};
147}
148
149sub data_dir {
150    config->{menta}->{data_dir} ||= base_dir() . 'app/data/';
151    config->{menta}->{data_dir};
152}
153
154sub __render_partial {
155    my ($tmpl, $tmpldir, @params) = @_;
156    MENTA::TemplateLoader::__load($tmpl, @params);
157}
158
159# テンプレートの一部を描画する
160sub render {
161    my ($tmpl, @params) = @_;
162    my $out = MENTA::TemplateLoader::__load($tmpl, @params);
163    bless \$out, 'Text::MicroTemplate::EncodedString';
164}
165
166sub _finish {
167    my $res = shift;
168    MENTA->call_trigger('BEFORE_OUTPUT', $res);
169    CGI::ExceptionManager::detach($res);
170}
171
172sub render_and_print {
173    my ($tmpl, @params) = @_;
174    require MENTA::TemplateLoader;
175    my $out = MENTA::TemplateLoader::__load($tmpl, @params);
176    $out = MENTA::Util::encode_output($out);
177
178    my $res = HTTP::Engine::Response->new(
179        body => $out,
180    );
181    $res->headers->content_type("text/html; charset=" . MENTA::Util::_charset());
182    _finish($res);
183}
184
185sub redirect {
186    my ($location, ) = @_;
187
188    my $res = HTTP::Engine::Response->new(
189        status => 302,
190    );
191    $res->header('Location' => $location);
192    _finish($res);
193}
194
195sub finalize {
196    my $str = shift;
197    my $content_type = shift || ('text/html; charset=' . MENTA::Util::_charset());
198
199    my $res = HTTP::Engine::Response->new(
200        status => 200,
201        body   => $str,
202    );
203    $res->headers->content_type($content_type);
204    _finish($res);
205}
206
207sub param        { MENTA::Util::decode_input(MENTA->context->request->param(@_)) }
208sub upload       { MENTA->context->request->upload(@_) }
209sub mobile_agent { MENTA->context->mobile_agent() }
210sub current_url  {
211    my $req = MENTA->context->request;
212    my $protocol = 'http';
213    my $port     = $ENV{SERVER_PORT} || 80;
214    my $url = "http://" . $req->header('Host');
215    $url .= docroot();
216    $url .= "$ENV{PATH_INFO}";
217    $url .= '?' . $ENV{QUERY_STRING};
218}
219
220{
221    # プラグインの自動ロード機構
222    sub AUTOLOAD {
223        my $method = our $AUTOLOAD;
224        $method =~ s/.*:://o;
225        (my $prefix = $method) =~ s/_.+//;
226        die "変な関数よびだしてませんか?: $method" unless $prefix;
227        MENTA::Util::load_plugin($prefix);
228        my $code = MENTA->can($method);
229        die "${method} という関数が見つかりません" unless $code;
230        return $code->(@_);
231    }
232}
233
234sub is_post_request () {
235    my $method = $ENV{REQUEST_METHOD};
236    return $method eq 'POST';
237}
238
239sub docroot () { $ENV{SCRIPT_NAME} || '' }
240
241sub uri_for {
242    my ($path, $query) = @_;
243    my @q;
244    while (my ($key, $val) = each %$query) {
245        $val = join '', map { /^[a-zA-Z0-9_.!~*'()-]$/ ? $_ : '%' . uc(unpack('H2', $_)) } split //, $val;
246        push @q, "${key}=${val}";
247    }
248    docroot . '/' . $path . (scalar @q ? '?' . join('&', @q) : '');
249}
250
251sub static_file_path {
252    my $path = shift;
253    docroot . '/static/' . $path;
254}
255
256{
257    package MENTA::Util;
258    # ユーティリティメソッドたち。
259    # これらのメソッドは一般ユーザーはよぶべきではない。
260
261    # HTTP::MobileAgent::Plugin::Charset よりポート。
262    # cp932 の方が実績があるので優先させる方針。
263    # Shift_JIS とかじゃなくて cp932 にしとかないと、諸問題にひっかかりがちなので注意
264    sub _encoding {
265        MENTA->context->{encoding} ||= sub {
266            return 'utf-8' if !MENTA->context->config->{menta}->{support_mobile}; # mobileじゃないときはutf-8
267
268            my $ma = MENTA->context->mobile_agent();
269            return 'utf-8' if $ma->is_non_mobile;
270            return 'utf-8' if $ma->is_docomo && $ma->xhtml_compliant; # docomo の 3G 端末では UTF-8 の表示が保障されている
271            return 'utf-8' if $ma->is_softbank && $ma->is_type_3gc;   # SoftBank 3G の一部端末は CP932 だと絵文字を送ってこない不具合がある
272            return 'cp932';                                           # au は HTTPS のときに UTF-8 だと文字化ける場合がある
273        }->();
274    }
275
276    # HTTP の入り口んとこで decode させる用
277    sub decode_input {
278        my ($txt, $fb) = @_;
279
280        if (_encoding() eq 'utf-8') {
281            utf8::decode($txt);
282            $txt;
283        } else {
284            require Encode;
285            Encode::decode(_encoding(), $txt, $fb);
286        }
287    }
288
289    # 出力直前んとこで encode させる用
290    sub encode_output {
291        my ($txt, $fb) = @_;
292 
293        if (_encoding() eq 'utf-8'){
294            utf8::encode($txt);
295            $txt;
296        } else {
297            require Encode;
298            Encode::encode(_encoding(), $txt, $fb);
299        }
300    }
301
302    # charset に設定する文字列を生成
303    sub _charset {
304        if(_encoding() eq 'utf-8'){
305            return 'UTF-8';
306        } else {
307            return 'Shift_JIS'; # cp932
308        }
309    }
310
311    # 一回ロードしたクラスは二度ロードしないための仕組み。
312    {
313        my $required = {};
314        sub require_once {
315            my $path = shift;
316            return if $required->{$path};
317            require $path;
318            $required->{$path} = 1;
319        }
320    }
321
322    {
323        my %plugin_loaded;
324        my $__menta_extract_package = sub {
325            my $modulefile = shift;
326            open my $fh, '<', $modulefile or die "$modulefile を開けません: $!";
327            my $in_pod = 0;
328            while (<$fh>) {
329                $in_pod = 1 if m/^=\w/;
330                $in_pod = 0 if /^=cut/;
331                next if ( $in_pod || /^=cut/ );    # skip pod text
332                next if /^\s*\#/;
333
334                /^\s* package \s+ (\S*) \s* ;/xms and return $1;
335            }
336            return;
337        };
338        sub load_plugin {
339            my $plugin = shift;
340            return if $plugin_loaded{$plugin};
341
342            my $path = MENTA::base_dir() . "plugins/${plugin}.pl";
343            require $path;
344            $plugin_loaded{$plugin}++;
345
346            my $package = $__menta_extract_package->($path) || '';
347            die "${plugin} プラグインの中にパッケージ宣言がみつかりません" unless $package;
348
349
350            my $stash = do{ no strict 'refs'; \%{$package.'::'} };
351
352            while(my $name = each %{$stash}){
353                if($name =~ /$plugin/){
354                    no strict 'refs';
355                    my $code = \&{$package.'::'.$name};
356                    *{"MENTA::$name"} = $code if defined &{$code};
357                }
358            }
359            return $package;
360        }
361    }
362}
363
3641;
Note: See TracBrowser for help on using the browser.