File Coverage

blib/lib/Kossy.pm
Criterion Covered Total %
statement 147 175 84.0
branch 30 52 57.6
condition 9 19 47.3
subroutine 28 31 90.3
pod 6 7 85.7
total 220 284 77.4


line stmt bran cond sub pod time code
1             package Kossy;
2              
3 7     7   398555 use strict;
  7         55  
  7         213  
4 7     7   35 use warnings;
  7         13  
  7         168  
5 7     7   169 use 5.008004;
  7         29  
6 7     7   3828 use utf8;
  7         96  
  7         38  
7 7     7   292 use Carp qw//;
  7         13  
  7         106  
8 7     7   31 use Cwd qw//;
  7         14  
  7         108  
9 7     7   34 use File::Basename qw//;
  7         10  
  7         120  
10 7     7   4153 use Text::Xslate;
  7         91893  
  7         380  
11 7     7   4351 use HTML::FillInForm::Lite qw//;
  7         25743  
  7         211  
12 7     7   3904 use Try::Tiny;
  7         15481  
  7         394  
13 7     7   4024 use Encode;
  7         106030  
  7         565  
14 7     7   3435 use Router::Boom;
  7         27873  
  7         371  
15             use Class::Accessor::Lite (
16 7         36 new => 0,
17             rw => [qw/root_dir/]
18 7     7   67 );
  7         33  
19 7     7   516 use base qw/Exporter/;
  7         13  
  7         617  
20 7     7   3425 use Kossy::Exception;
  7         20  
  7         239  
21 7     7   3381 use Kossy::Connection;
  7         21  
  7         222  
22 7     7   3038 use Kossy::Request;
  7         24  
  7         252  
23 7     7   50 use Kossy::Response;
  7         14  
  7         153  
24 7     7   40 use HTTP::Headers::Fast;
  7         18  
  7         13151  
25              
26             our $VERSION = '0.50';
27             our @EXPORT = qw/new root_dir psgi build_app _router _connect get post router filter _wrap_filter/;
28              
29             our $XSLATE_CACHE = 1;
30             our $XSLATE_CACHE_DIR;
31             our $SECURITY_HEADER = 1;
32              
33             # cache underscore translation
34             HTTP::Headers::Fast::_standardize_field_name('X-Frame-Options');
35              
36             sub new {
37 5     5 1 1487 my $class = shift;
38 5         10 my %args;
39 5 100       16 if ( @_ < 2 ) {
40 2         3 my $root_dir = shift;
41 2         7 my @caller = caller;
42 2   66     68 $root_dir ||= File::Basename::dirname( Cwd::realpath($caller[1]) );
43 2         8 $args{root_dir} = $root_dir;
44             }
45             else {
46 3         11 %args = @_;
47             }
48              
49 5         17 bless \%args, $class;
50             }
51              
52             sub psgi {
53 2     2 1 1884 my $self = shift;
54 2 50       8 if ( ! ref $self ) {
55 2         5 my %args;
56 2 50       6 if ( @_ < 2 ) {
57 2         4 my $root_dir = shift;
58 2         6 my @caller = caller;
59 2   33     6 $root_dir ||= File::Basename::dirname( Cwd::realpath($caller[1]) );
60 2         8 $args{root_dir} = $root_dir;
61             }
62             else {
63 0         0 %args = @_;
64             }
65 2         8 $self = $self->new(%args);
66             }
67              
68 2         5 $self->build_app;
69             }
70              
71             sub build_app {
72 2     2 0 4 my $self = shift;
73              
74             #router
75 2         11 my $router = Router::Boom->new;
76 2         50 $router->add($_ => $self->_router->{$_} ) for keys %{$self->_router};
  2         5  
77 2         114 my $xslate_cache_local = $XSLATE_CACHE;
78 2         4 my $xslate_cache_dir_local = $XSLATE_CACHE_DIR;
79 2         4 my $security_header_local = $SECURITY_HEADER;
80 2         4 my %match_cache;
81              
82             #xslate
83 2         13 my $fif = HTML::FillInForm::Lite->new();
84             my $tx = Text::Xslate->new(
85             path => [ $self->root_dir . '/views' ],
86             input_layer => ':utf8',
87             module => ['Text::Xslate::Bridge::TT2Like','Number::Format' => [':subs']],
88             function => {
89             fillinform => sub {
90 0     0   0 my $q = shift;
91             return sub {
92 0         0 my ($html) = @_;
93 0         0 return Text::Xslate::mark_raw( $fif->fill( \$html, $q ) );
94             }
95 0         0 }
96             },
97 2 50       63 cache => $xslate_cache_local,
98             defined $xslate_cache_dir_local ? ( cache_dir => $xslate_cache_dir_local ) : (),
99             );
100              
101             sub {
102 18     18   79755 my $env = shift;
103 18         36 $Kossy::Response::SECURITY_HEADER = $security_header_local;
104             try {
105 18 100       712 my $header = bless {
106             'content-type' => 'text/html; charset=UTF-8',
107             $security_header_local ? ('x-frame-options' => 'DENY') : (),
108             }, 'HTTP::Headers::Fast';
109 18         96 my $c = Kossy::Connection->new({
110             tx => $tx,
111             req => Kossy::Request->new($env),
112             res => Kossy::Response->new(200, $header),
113             stash => {},
114             });
115 18         278 my $method = uc($env->{REQUEST_METHOD});
116 18         52 my $cache_key = $method . '-' . $env->{PATH_INFO};
117             my ($match,$args) = try {
118 18 100       679 if ( exists $match_cache{$cache_key} ) {
119 2         3 return @{$match_cache{$cache_key}};
  2         7  
120             }
121 16         219 my $path_info = Encode::decode_utf8( $env->{PATH_INFO}, Encode::FB_CROAK | Encode::LEAVE_SRC );
122 16         156 my @match = $router->match($path_info);
123 16 100       5259 if ( !@match ) {
124 2         8 $c->halt(404);
125             }
126            
127 14 100       78 if ( !exists $match[0]->{$method}) {
128 2         15 $c->halt(405);
129             }
130             $match_cache{$cache_key} = [$match[0]->{$method},$match[1]]
131 12 100       19 if ! scalar keys %{$match[1]};
  12         63  
132 12         43 return ($match[0]->{$method},$match[1]);
133             } catch {
134 4 50 33     70 if ( ref $_ && ref $_ eq 'Kossy::Exception' ) {
135 4         17 die $_; #rethrow
136             }
137 0         0 $c->halt(400,'unexpected character in request');
138 18         126 };
139            
140 14         277 my $code = $match->{__action__};
141 14   50     40 my $filters = $match->{__filter__} || [];
142 14 50 33     61 if ( $] == 5.020000 || $] == 5.020100 ) {
143             # workaround for 5.20.0 or 5.20.1 https://github.com/kazeburo/Kossy/pull/10
144 0         0 my %args = map { $_ => Encode::decode_utf8($args->{$_}) } keys %$args;
  0         0  
145 0         0 $c->args(\%args);
146             } else {
147 14         58 $c->args({%$args});
148             }
149             my $app = sub {
150 14         29 my ($self, $c) = @_;
151 14         22 my $response;
152 14         39 my $res = $code->($self, $c);
153 14 50       454 Carp::croak "Undefined Response" if ! defined $res;
154 14   100     47 my $res_t = ref($res) || '';
155 14 100       56 if ( $res_t eq 'Kossy::Response' ) {
    50          
    50          
    50          
156 1         2 $response = $res;
157             }
158             elsif ( $res_t eq 'Plack::Response' ) {
159 0         0 $response = bless $res, 'Kossy::Response';
160             }
161             elsif ( $res_t eq 'ARRAY' ) {
162 0         0 $response = Kossy::Response->new(@$res);
163             }
164             elsif ( !$res_t ) {
165 13         33 $c->res->body($res);
166 13         98 $response = $c->res;
167             }
168             else {
169 0         0 Carp::croak sprintf "Unknown Response: %s", $res_t;
170             }
171 14         71 $response;
172 14         125 };
173            
174 14         32 for my $filter ( reverse @$filters ) {
175 0         0 $app = $self->_wrap_filter($filter,$app);
176             }
177             # do all
178 14         22 local $Kossy::Response::DIRECT;
179 14         27 $app->($self, $c)->finalize;
180             } catch {
181 4 50 33     98 if ( ref $_ && ref $_ eq 'Kossy::Exception' ) {
182 4         12 return $_->response;
183             }
184 0         0 die $_;
185 18         125 };
186 2         61763 };
187             }
188              
189              
190              
191             my $_ROUTER={};
192             sub _router {
193 71     71   892 my $klass = shift;
194 71 100       113 my $class = ref $klass ? ref $klass : $klass;
195 71 100       137 if ( !$_ROUTER->{$class} ) {
196 3         8 $_ROUTER->{$class} = {};
197             }
198 71         279 $_ROUTER->{$class};
199             }
200              
201             sub _connect {
202 27     27   40 my $class = shift;
203 27         44 my ( $methods, $pattern, $filter, $code ) = @_;
204 27 50       52 $methods = ref($methods) ? $methods : [$methods];
205 27 50       49 if (!$code) {
206 27         36 $code = $filter;
207 27         44 $filter = [];
208             }
209 27         50 for my $method ( @$methods ) {
210 51         133 $class->_router->{$pattern}->{$method} = {
211             __action__ => $code,
212             __filter__ => $filter
213             };
214             }
215             }
216              
217             sub get {
218 21     21 1 2226 my $class = caller;
219 21         74 $class->_connect( ['GET','HEAD'], @_ );
220             }
221              
222             sub post {
223 3     3 1 27 my $class = caller;
224 3         11 $class->_connect( ['POST'], @_ );
225             }
226              
227             sub router {
228 3     3 1 21 my $class = caller;
229 3         23 $class->_connect( @_ );
230             }
231              
232             my $_FILTER={};
233             sub filter {
234 0     0 1   my $class = caller;
235 0 0         if ( !$_FILTER->{$class} ) {
236 0           $_FILTER->{$class} = {};
237             }
238 0 0         if ( @_ ) {
239 0           $_FILTER->{$class}->{$_[0]} = $_[1];
240             }
241 0           $_FILTER->{$class};
242             }
243              
244             sub _wrap_filter {
245 0     0     my $klass = shift;
246 0 0         my $class = ref $klass ? ref $klass : $klass;
247 0 0         if ( !$_FILTER->{$class} ) {
248 0           $_FILTER->{$class} = {};
249             }
250 0           my ($filter,$app) = @_;
251 0           my $filter_subref = $_FILTER->{$class}->{$filter};
252 0 0         Carp::croak sprintf("Filter:%s is not exists", $filter) unless $filter_subref;
253 0           return $filter_subref->($app);
254             }
255              
256             1;
257              
258             __END__