line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MVC::Neaf::Route::Main; |
2
|
|
|
|
|
|
|
|
3
|
81
|
|
|
81
|
|
50209
|
use strict; |
|
81
|
|
|
|
|
185
|
|
|
81
|
|
|
|
|
2281
|
|
4
|
81
|
|
|
81
|
|
440
|
use warnings; |
|
81
|
|
|
|
|
164
|
|
|
81
|
|
|
|
|
3320
|
|
5
|
|
|
|
|
|
|
our $VERSION = '0.2800_01'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 NAME |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
MVC::Neaf::Route::Main - main application class for Not Even A Framework. |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 DESCRIPTION |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
This class contains a L application structure |
14
|
|
|
|
|
|
|
and implements the core of Neaf logic. |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
It is a L object itself, |
17
|
|
|
|
|
|
|
containing a hash of other routes designated by their path prefixes. |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 APPLICATION SETUP METHODS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=cut |
22
|
|
|
|
|
|
|
|
23
|
81
|
|
|
81
|
|
488
|
use Carp; |
|
81
|
|
|
|
|
181
|
|
|
81
|
|
|
|
|
4680
|
|
24
|
81
|
|
|
81
|
|
536
|
use Cwd qw(cwd abs_path); |
|
81
|
|
|
|
|
225
|
|
|
81
|
|
|
|
|
4450
|
|
25
|
81
|
|
|
81
|
|
42993
|
use Encode; |
|
81
|
|
|
|
|
1164621
|
|
|
81
|
|
|
|
|
6659
|
|
26
|
81
|
|
|
81
|
|
682
|
use File::Basename qw(dirname); |
|
81
|
|
|
|
|
213
|
|
|
81
|
|
|
|
|
8272
|
|
27
|
81
|
|
|
81
|
|
39238
|
use Module::Load; |
|
81
|
|
|
|
|
88481
|
|
|
81
|
|
|
|
|
534
|
|
28
|
81
|
|
|
81
|
|
4891
|
use Scalar::Util qw( blessed looks_like_number reftype ); |
|
81
|
|
|
|
|
207
|
|
|
81
|
|
|
|
|
4649
|
|
29
|
81
|
|
|
81
|
|
34574
|
use URI::Escape; |
|
81
|
|
|
|
|
116740
|
|
|
81
|
|
|
|
|
5017
|
|
30
|
|
|
|
|
|
|
|
31
|
81
|
|
|
81
|
|
581
|
use parent qw(MVC::Neaf::Route); |
|
81
|
|
|
|
|
181
|
|
|
81
|
|
|
|
|
443
|
|
32
|
81
|
|
|
81
|
|
40773
|
use MVC::Neaf::Request::PSGI; |
|
81
|
|
|
|
|
324
|
|
|
81
|
|
|
|
|
3060
|
|
33
|
81
|
|
|
81
|
|
545
|
use MVC::Neaf::Route::PreRoute; |
|
81
|
|
|
|
|
178
|
|
|
81
|
|
|
|
|
2622
|
|
34
|
81
|
|
|
|
|
7969
|
use MVC::Neaf::Util qw( |
35
|
|
|
|
|
|
|
caller_info |
36
|
|
|
|
|
|
|
canonize_path |
37
|
|
|
|
|
|
|
check_path |
38
|
|
|
|
|
|
|
data_fh |
39
|
|
|
|
|
|
|
decode_b64 |
40
|
|
|
|
|
|
|
encode_b64 |
41
|
|
|
|
|
|
|
extra_missing |
42
|
|
|
|
|
|
|
http_date |
43
|
|
|
|
|
|
|
maybe_list |
44
|
|
|
|
|
|
|
run_all |
45
|
|
|
|
|
|
|
run_all_nodie |
46
|
|
|
|
|
|
|
supported_methods |
47
|
81
|
|
|
81
|
|
412
|
); |
|
81
|
|
|
|
|
185
|
|
48
|
81
|
|
|
81
|
|
38199
|
use MVC::Neaf::Util::Container; |
|
81
|
|
|
|
|
219
|
|
|
81
|
|
|
|
|
225528
|
|
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
# TODO 0.30 remove |
51
|
|
|
|
|
|
|
sub _one_and_true { |
52
|
879
|
|
|
879
|
|
1538
|
my $self = shift; |
53
|
879
|
100
|
|
|
|
2735
|
return $self if ref $self; |
54
|
|
|
|
|
|
|
|
55
|
2
|
|
|
|
|
6
|
my $method = [caller 1]->[3]; |
56
|
2
|
|
|
|
|
88
|
$method =~ s/.*:://; |
57
|
|
|
|
|
|
|
|
58
|
2
|
100
|
|
|
|
10
|
if ($self eq 'MVC::Neaf') { |
59
|
1
|
|
|
|
|
6
|
require MVC::Neaf; |
60
|
1
|
|
|
|
|
20
|
carp "MVC::Neaf->$method() call is DEPRECATED, use neaf->$method or MVC::Neaf->new()"; |
61
|
1
|
|
|
|
|
717
|
return MVC::Neaf::neaf(); |
62
|
|
|
|
|
|
|
}; |
63
|
|
|
|
|
|
|
|
64
|
1
|
|
|
|
|
13
|
croak "Method $method called on unblessed '$self'"; |
65
|
|
|
|
|
|
|
}; |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=head2 new() |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
new( ) |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
This is also called by Cnew>, |
72
|
|
|
|
|
|
|
in case one wants to instantiate a Neaf application object |
73
|
|
|
|
|
|
|
instead of using the default L. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
A hash of %options may be added in the future, but isn't supported currently. |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=cut |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub new { |
80
|
96
|
|
|
96
|
1
|
14284
|
my ($class, %opt) = @_; |
81
|
|
|
|
|
|
|
|
82
|
96
|
100
|
|
|
|
492
|
croak('MVC::Neaf->new: no options currently supported: '.join ", ", sort keys %opt) |
83
|
|
|
|
|
|
|
if %opt; |
84
|
|
|
|
|
|
|
|
85
|
95
|
|
|
|
|
284
|
my $self = bless {}, $class; |
86
|
|
|
|
|
|
|
|
87
|
95
|
|
|
|
|
1027
|
$self->set_path_defaults( { -status => 200, -view => 'JS' } ); |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# This is required for $self->hooks to produce something. |
90
|
|
|
|
|
|
|
# See also todo_hooks where the real hooks sit. |
91
|
95
|
|
|
|
|
242
|
$self->{hooks} = {}; |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# magical by default |
94
|
95
|
|
|
|
|
234
|
$self->{magic} = 1; |
95
|
|
|
|
|
|
|
|
96
|
95
|
|
|
|
|
324
|
return $self; |
97
|
|
|
|
|
|
|
}; |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
=head2 add_route() |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
Define a handler for given by URI path and HTTP method(s). |
102
|
|
|
|
|
|
|
This is the backend behind NEAF's C route specifications. |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
route( '/path' => CODEREF, %options ) |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Any incoming request to uri matching C |
107
|
|
|
|
|
|
|
(C too, but NOT C) |
108
|
|
|
|
|
|
|
will now be directed to CODEREF. |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Longer paths are GUARANTEED to be checked first. |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
Dies if the same method and path combination is given twice |
113
|
|
|
|
|
|
|
(but see C and C below). |
114
|
|
|
|
|
|
|
Multiple methods may be given for the same path. |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
Exactly one leading slash will be prepended no matter what you do. |
117
|
|
|
|
|
|
|
(C, C and C////path> are all the same). |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
The C MUST accept exactly one argument, |
120
|
|
|
|
|
|
|
referred to as C<$request> or C<$req> hereafter, |
121
|
|
|
|
|
|
|
and return an unblessed hashref with response data. |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
%options may include: |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=over |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=item * C - list of allowed HTTP methods. |
128
|
|
|
|
|
|
|
Default is [GET, POST]. |
129
|
|
|
|
|
|
|
Multiple handles can be defined for the same path, provided that |
130
|
|
|
|
|
|
|
methods do not intersect. |
131
|
|
|
|
|
|
|
HEAD method is automatically handled if GET is present, however, |
132
|
|
|
|
|
|
|
one MAY define a separate HEAD handler explicitly. |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
=item * C => C - allow URI subpaths |
135
|
|
|
|
|
|
|
to be handled by this handler. |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
A 404 error will be generated unless C is present |
138
|
|
|
|
|
|
|
and PATH_INFO matches the regex (without the leading slashes). |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
If path_info_regex matches, it will be available in the controller |
141
|
|
|
|
|
|
|
as C<$req-Epath_info>. |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
If capture groups are present in said regular expression, |
144
|
|
|
|
|
|
|
their content will also be available as C<$req-Epath_info_split>. |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and semantics MAY change in the future. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=item * C => { name => C, name2 => C<'\d+'> } |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
Add predefined regular expression validation to certain request parameters, |
151
|
|
|
|
|
|
|
so that they can be queried by name only. |
152
|
|
|
|
|
|
|
See C in L. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and semantics MAY change in the future. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=item * strict => 1|0 |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
If true, request's C and C |
159
|
|
|
|
|
|
|
will emit HTTP error 422 |
160
|
|
|
|
|
|
|
whenever mandatory validation fails. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
If parameter or cookie is missing, just return default. |
163
|
|
|
|
|
|
|
This MAY change in the future. |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and meaning MAY change in the future. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=item * C - default View object for this Controller. |
168
|
|
|
|
|
|
|
Must be a name of preloaded view, |
169
|
|
|
|
|
|
|
an object with a C method, or a CODEREF |
170
|
|
|
|
|
|
|
receiving hashref and returning a list of two scalars |
171
|
|
|
|
|
|
|
(content and content-type). |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
B<[DEPRECATED]> Use C<-view> instead, meaning is exactly the same. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
=item * C - if set, set Expires: HTTP header accordingly. |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and semantics MAY change in the future. |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
=item * C - a C<\%hash> of values that will be added to results |
180
|
|
|
|
|
|
|
EVERY time the handler returns. |
181
|
|
|
|
|
|
|
Consider using C below if you need to append |
182
|
|
|
|
|
|
|
the same values to multiple paths. |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
=item * C => 1 - replace old route even if it exists. |
185
|
|
|
|
|
|
|
If not set, route collisions causes exception. |
186
|
|
|
|
|
|
|
Use this if you know better. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
This still issues a warning. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and meaning may change in the future. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
=item * C => 1 - if route is already defined, do nothing. |
193
|
|
|
|
|
|
|
If not, allow to redefine it later. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name and meaning may change in the future. |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=item * C - just for information, has no action on execution. |
198
|
|
|
|
|
|
|
This will be displayed if application called with --list (see L). |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
=item * C => 0|1 - a flag just for information. |
201
|
|
|
|
|
|
|
In theory, public endpoints should be searchable from the outside |
202
|
|
|
|
|
|
|
while non-public ones should only be reachable from other parts of application. |
203
|
|
|
|
|
|
|
This is not enforced whatsoever. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=back |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Also, any number of dash-prefixed keys MAY be present. |
208
|
|
|
|
|
|
|
This is the same as putting them into C hash. |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=cut |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
my $year = 365 * 24 * 60 * 60; |
213
|
|
|
|
|
|
|
my %known_route_args; |
214
|
|
|
|
|
|
|
$known_route_args{$_}++ for qw( |
215
|
|
|
|
|
|
|
default method view cache_ttl |
216
|
|
|
|
|
|
|
path_info_regex param_regex strict |
217
|
|
|
|
|
|
|
description caller tentative override public |
218
|
|
|
|
|
|
|
); |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub add_route { |
221
|
117
|
|
|
117
|
1
|
7355
|
my $self = shift; |
222
|
|
|
|
|
|
|
|
223
|
117
|
100
|
|
|
|
542
|
$self->my_croak( "Odd number of elements in hash assignment" ) |
224
|
|
|
|
|
|
|
if @_ % 2; |
225
|
116
|
|
|
|
|
578
|
my ($path, $sub, %args) = @_; |
226
|
116
|
|
|
|
|
423
|
$self = _one_and_true($self); |
227
|
|
|
|
|
|
|
|
228
|
115
|
100
|
|
|
|
568
|
$self->my_croak( "handler must be a coderef, not ".ref $sub ) |
229
|
|
|
|
|
|
|
unless UNIVERSAL::isa( $sub, "CODE" ); |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
# check defaults to be a hash before accessing them |
232
|
|
|
|
|
|
|
$self->my_croak( "default must be unblessed hash" ) |
233
|
113
|
100
|
100
|
|
|
485
|
if $args{default} and ref $args{default} ne 'HASH'; |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
# minus-prefixed keys are typically defaults |
236
|
|
|
|
|
|
|
$_ =~ /^-/ and $args{default}{$_} = delete $args{$_} |
237
|
112
|
|
66
|
|
|
866
|
for keys %args; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
# kill extra args |
240
|
112
|
|
|
|
|
387
|
my @extra = grep { !$known_route_args{$_} } keys %args; |
|
219
|
|
|
|
|
708
|
|
241
|
112
|
100
|
|
|
|
353
|
$self->my_croak( "Unexpected keys in route setup: @extra" ) |
242
|
|
|
|
|
|
|
if @extra; |
243
|
|
|
|
|
|
|
|
244
|
111
|
|
|
|
|
497
|
$args{path} = $path = check_path canonize_path( $path ); |
245
|
|
|
|
|
|
|
|
246
|
111
|
|
|
|
|
541
|
$args{method} = maybe_list( $args{method}, qw( GET POST ) ); |
247
|
111
|
|
|
|
|
290
|
$_ = uc $_ for @{ $args{method} }; |
|
111
|
|
|
|
|
544
|
|
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
$self->my_croak("Public endpoint must have nonempty description") |
250
|
111
|
100
|
100
|
|
|
502
|
if $args{public} and not $args{description}; |
251
|
|
|
|
|
|
|
|
252
|
110
|
|
|
|
|
717
|
my @real_method = $self->_detect_duplicate( \%args, $args{method} ); |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# Do the work |
255
|
106
|
|
|
|
|
301
|
$args{parent} = $self; |
256
|
106
|
|
|
|
|
265
|
$args{code} = $sub; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# Always have regex defined to simplify routing |
259
|
|
|
|
|
|
|
$args{path_info_regex} = (defined $args{path_info_regex}) |
260
|
106
|
100
|
|
|
|
957
|
? qr#^$args{path_info_regex}$# |
261
|
|
|
|
|
|
|
: qr#^$#; |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
# Just for information |
264
|
106
|
100
|
|
|
|
438
|
$args{public} = $args{public} ? 1 : 0; |
265
|
106
|
|
100
|
|
|
625
|
$args{caller} ||= [caller(0)]; # save file,line |
266
|
|
|
|
|
|
|
|
267
|
106
|
100
|
|
|
|
708
|
if (exists $args{view}) { |
268
|
|
|
|
|
|
|
# TODO 0.30 |
269
|
1
|
|
|
|
|
16
|
carp "NEAF: route(): view argument is deprecated, use -view instead"; |
270
|
1
|
|
|
|
|
767
|
$args{default}{-view} = delete $args{view}; |
271
|
|
|
|
|
|
|
}; |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
# preload view so that we can fail early |
274
|
|
|
|
|
|
|
$args{default}{-view} = $self->get_view( $args{default}{-view} ) |
275
|
106
|
100
|
|
|
|
529
|
if $args{default}{-view}; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# ready, shallow copy handler & burn cache |
278
|
106
|
|
|
|
|
251
|
delete $self->{route_re}; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
$self->{route}{ $path }{$_} = MVC::Neaf::Route->new( %args, method => $_ ) |
281
|
106
|
|
|
|
|
880
|
for @real_method; |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
# This is for get+post sugar |
284
|
105
|
|
|
|
|
367
|
$self->{last_added} = \%args; |
285
|
|
|
|
|
|
|
|
286
|
105
|
|
|
|
|
417
|
return $self; |
287
|
|
|
|
|
|
|
}; # end sub route |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
# in: { path => '/...', tentative => 0|1, override=> 0|1 }, \@method_list |
290
|
|
|
|
|
|
|
# out: @real_method_list |
291
|
|
|
|
|
|
|
# dies/warns if violations found |
292
|
|
|
|
|
|
|
sub _detect_duplicate { |
293
|
117
|
|
|
117
|
|
377
|
my ($self, $profile, $methods) = @_; |
294
|
|
|
|
|
|
|
|
295
|
117
|
|
|
|
|
298
|
my $path = $profile->{path}; |
296
|
|
|
|
|
|
|
# Handle duplicate route definitions |
297
|
|
|
|
|
|
|
my @dupe = grep { |
298
|
117
|
|
|
|
|
291
|
exists $self->{route}{$path}{$_} |
299
|
154
|
100
|
|
|
|
1144
|
and !$self->{route}{$path}{$_}{tentative}; |
300
|
|
|
|
|
|
|
} @$methods; |
301
|
|
|
|
|
|
|
|
302
|
117
|
100
|
|
|
|
381
|
if (@dupe) { |
303
|
7
|
|
|
|
|
14
|
my %olddef; |
304
|
7
|
|
|
|
|
17
|
foreach (@dupe) { |
305
|
9
|
|
|
|
|
21
|
my $where = $self->{route}{$path}{$_}{where}; |
306
|
9
|
|
|
|
|
15
|
push @{ $olddef{$where} }, $_; |
|
9
|
|
|
|
|
35
|
|
307
|
|
|
|
|
|
|
}; |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# flatten olddef hash, format list |
310
|
7
|
|
|
|
|
23
|
my $oldwhere = join ", ", map { "$_ [@{ $olddef{$_} }]" } keys %olddef; |
|
8
|
|
|
|
|
34
|
|
|
8
|
|
|
|
|
39
|
|
311
|
7
|
|
100
|
|
|
26
|
my $oldpath = $path || '/'; |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Alas, must do error message by hand |
314
|
7
|
|
|
|
|
27
|
my $caller = [caller 1]->[3]; |
315
|
7
|
|
|
|
|
286
|
$caller =~ s/.*:://; |
316
|
7
|
100
|
|
|
|
31
|
if ($profile->{override}) { |
|
|
100
|
|
|
|
|
|
317
|
2
|
|
|
|
|
25
|
carp( (ref $self)."->$caller: Overriding old handler for" |
318
|
|
|
|
|
|
|
." $oldpath defined $oldwhere"); |
319
|
|
|
|
|
|
|
} elsif( $profile->{tentative} ) { |
320
|
|
|
|
|
|
|
# if we're tentative, filter out already known method/route pairs |
321
|
1
|
|
|
|
|
4
|
my %filter; |
322
|
1
|
|
|
|
|
2
|
$filter{$_}++ for @{ $methods }; |
|
1
|
|
|
|
|
5
|
|
323
|
1
|
|
|
|
|
3
|
delete $filter{$_} for @dupe; |
324
|
1
|
|
|
|
|
5
|
return keys %filter; |
325
|
|
|
|
|
|
|
} else { |
326
|
4
|
|
|
|
|
306
|
croak( (ref $self)."->$caller: Attempting to set duplicate handler for" |
327
|
|
|
|
|
|
|
." $oldpath defined $oldwhere"); |
328
|
|
|
|
|
|
|
}; |
329
|
|
|
|
|
|
|
}; |
330
|
|
|
|
|
|
|
|
331
|
112
|
|
|
|
|
1739
|
return @$methods; |
332
|
|
|
|
|
|
|
}; |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
# This is for get+post sugar |
335
|
|
|
|
|
|
|
# TODO 0.90 merge with alias, GET => implicit HEAD |
336
|
|
|
|
|
|
|
# TODO 0.30 public method |
337
|
|
|
|
|
|
|
sub _dup_route { |
338
|
7
|
|
|
7
|
|
27
|
my ($self, $method, $profile) = @_; |
339
|
|
|
|
|
|
|
|
340
|
7
|
|
33
|
|
|
39
|
$profile ||= $self->{last_added}; |
341
|
7
|
|
|
|
|
19
|
my $path = $profile->{path}; |
342
|
|
|
|
|
|
|
|
343
|
7
|
|
|
|
|
27
|
my @real_method = $self->_detect_duplicate($profile, [ $method ]); |
344
|
|
|
|
|
|
|
|
345
|
7
|
|
|
|
|
24
|
delete $self->{route_re}; |
346
|
|
|
|
|
|
|
$self->{route}{ $path }{$_} = MVC::Neaf::Route->new( %$profile, method => $_ ) |
347
|
7
|
|
|
|
|
55
|
for @real_method; |
348
|
|
|
|
|
|
|
}; |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
=head2 static() |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
$neaf->static( '/path' => $local_path, %options ); |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
$neaf->static( '/other/path' => [ "content", "content-type" ] ); |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
Serve static content located under C<$file_path>. |
357
|
|
|
|
|
|
|
Both directories and single files may be added. |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
If an arrayref of C<[ $content, $content_type ]> is given as second argument, |
360
|
|
|
|
|
|
|
serve content from memory instead. |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
%options may include: |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=over |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item * C => C - buffer size for reading/writing files. |
367
|
|
|
|
|
|
|
Default is 4096. Smaller values may be set, but are NOT recommended. |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
=item * C => C - if given, files below the buffer size |
370
|
|
|
|
|
|
|
will be stored in memory for C seconds. |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Cache API is not yet established. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=item * allow_dots => 1|0 - if true, serve files/directories |
375
|
|
|
|
|
|
|
starting with a dot (.git etc), otherwise give a 404. |
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=item * dir_index => 1|0 - if true, generate index for a directory; |
380
|
|
|
|
|
|
|
otherwise a 404 is returned, and deliberately so, for security reasons. |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
=item * dir_template - specify template for directory listing |
385
|
|
|
|
|
|
|
(with images etc). A sane default is provided. |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=item * view - specify view object for rendering directory template. |
390
|
|
|
|
|
|
|
By default a localized C instance is used. |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> Name MAY be changed (dir_view etc). |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
=item * override - override the route that was here before. |
395
|
|
|
|
|
|
|
See C above. |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
=item * tentative - don't complain if replaced later. |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
=item * description - comment. The default is "Static content at $directory" |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
=item * public => 0|1 - a flag just for information. |
402
|
|
|
|
|
|
|
In theory, public endpoints should be searchable from the outside |
403
|
|
|
|
|
|
|
while non-public ones should only be reachable from other parts of application. |
404
|
|
|
|
|
|
|
This is not enforced whatsoever. |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
=back |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
See L for implementation. |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
File type detection is based on extentions so far, and the list is quite short. |
411
|
|
|
|
|
|
|
This MAY change in the future. |
412
|
|
|
|
|
|
|
Known file types are listed in C<%MVC::Neaf::X::Files::ExtType> hash. |
413
|
|
|
|
|
|
|
Patches welcome. |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
I
|
416
|
|
|
|
|
|
|
using a web application framework. |
417
|
|
|
|
|
|
|
Use a real web server instead. |
418
|
|
|
|
|
|
|
Not need to set up one for merely testing icons/js/css, though.> |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=cut |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
sub static { |
423
|
5
|
|
|
5
|
1
|
64
|
my ($self, $path, $dir, %options) = @_; |
424
|
5
|
|
|
|
|
42
|
$self = _one_and_true($self); |
425
|
|
|
|
|
|
|
|
426
|
5
|
|
50
|
|
|
151
|
$options{caller} ||= [caller 0]; |
427
|
|
|
|
|
|
|
|
428
|
5
|
|
|
|
|
16
|
my %fwd_opt; |
429
|
|
|
|
|
|
|
defined $options{$_} and $fwd_opt{$_} = delete $options{$_} |
430
|
5
|
|
66
|
|
|
97
|
for qw( tentative override caller public ); |
431
|
|
|
|
|
|
|
|
432
|
5
|
100
|
|
|
|
29
|
if (ref $dir eq 'ARRAY') { |
433
|
1
|
|
|
|
|
7
|
my $sub = $self->_static_global->preload( $path => $dir )->one_file_handler; |
434
|
1
|
|
|
|
|
254
|
return $self->route( $path => $sub, method => 'GET', %fwd_opt, |
435
|
|
|
|
|
|
|
, description => Carp::shortmess( "Static content from memory" )); |
436
|
|
|
|
|
|
|
}; |
437
|
|
|
|
|
|
|
|
438
|
4
|
|
|
|
|
1428
|
require MVC::Neaf::X::Files; |
439
|
4
|
|
|
|
|
52
|
my $xfiles = MVC::Neaf::X::Files->new( |
440
|
|
|
|
|
|
|
%options, root => $self->dir($dir), base_url => $path ); |
441
|
4
|
|
|
|
|
15
|
return $self->route( $xfiles->make_route, %fwd_opt ); |
442
|
|
|
|
|
|
|
}; |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
# Instantiate a global static handler to preload in-memory |
445
|
|
|
|
|
|
|
# static files into. |
446
|
|
|
|
|
|
|
# TODO 0.30 lame name, find better |
447
|
|
|
|
|
|
|
sub _static_global { |
448
|
6
|
|
|
6
|
|
17
|
my $self = shift; |
449
|
|
|
|
|
|
|
|
450
|
6
|
|
66
|
|
|
37
|
return $self->{global_static} ||= do { |
451
|
5
|
|
|
|
|
2543
|
require MVC::Neaf::X::Files; |
452
|
5
|
|
|
|
|
46
|
MVC::Neaf::X::Files->new( root => '/dev/null' ); |
453
|
|
|
|
|
|
|
}; |
454
|
|
|
|
|
|
|
}; |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
=head2 alias() |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
$neaf->alias( $newpath => $oldpath ) |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
Create a new name for already registered route. |
462
|
|
|
|
|
|
|
The handler will be executed as is, |
463
|
|
|
|
|
|
|
but the hooks and defaults will be re-calculated. |
464
|
|
|
|
|
|
|
So be careful. |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
B<[CAUTION]> As of 0.21, C does NOT adhere tentative/override switches. |
467
|
|
|
|
|
|
|
This needs to be fixed in the future. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
=cut |
470
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
# TODO 0.30 add_alias or something |
472
|
|
|
|
|
|
|
sub alias { |
473
|
3
|
|
|
3
|
1
|
8
|
my ($self, $new, $old) = @_; |
474
|
3
|
|
|
|
|
7
|
$self = _one_and_true($self); |
475
|
|
|
|
|
|
|
|
476
|
3
|
|
|
|
|
10
|
$new = canonize_path( $new ); |
477
|
3
|
|
|
|
|
7
|
$old = canonize_path( $old ); |
478
|
|
|
|
|
|
|
|
479
|
3
|
|
|
|
|
10
|
check_path( $old, $new ); |
480
|
|
|
|
|
|
|
|
481
|
3
|
100
|
|
|
|
18
|
$self->{route}{$old} |
482
|
|
|
|
|
|
|
or $self->my_croak( "Cannot create alias for unknown route $old" ); |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
# TODO 0.30 restrict methods, handle tentative/override, detect dupes |
485
|
|
|
|
|
|
|
$self->my_croak( "Attempting to set duplicate handler for path " |
486
|
|
|
|
|
|
|
.( length $new ? $new : "/" ) ) |
487
|
2
|
50
|
|
|
|
13
|
if $self->{route}{ $new }; |
|
|
100
|
|
|
|
|
|
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# reset cache |
490
|
1
|
|
|
|
|
2
|
delete $self->{route_re}; |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
# FIXME clone() |
493
|
1
|
|
|
|
|
3
|
$self->{route}{$new} = $self->{route}{$old}; |
494
|
1
|
|
|
|
|
13
|
return $self; |
495
|
|
|
|
|
|
|
}; |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
=head2 set_path_defaults |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
set_path_defaults( { version => 0.99 }, path => '/api', %options ); |
500
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
%options may include: |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=over |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=item * path - restrict this set of defaults to given prefix(es); |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=item * method - restrict this set of defaults to given method(s); |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=item * exclude - exclude some prefixes; |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
=back |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
Append the given values to the hash returned by I route |
514
|
|
|
|
|
|
|
under the given path(s) and method(s). |
515
|
|
|
|
|
|
|
|
516
|
|
|
|
|
|
|
Longer paths take over the shorter ones. |
517
|
|
|
|
|
|
|
Route's own default values take over any path-based defaults. |
518
|
|
|
|
|
|
|
Whatever the controller returns overrides all of these. |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
=cut |
521
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
# TODO 0.30 rename defaults => [something] |
523
|
|
|
|
|
|
|
sub set_path_defaults { |
524
|
105
|
|
|
105
|
1
|
324
|
my $self = shift; |
525
|
105
|
|
|
|
|
383
|
$self = _one_and_true($self); |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Old form - path => \%hash |
528
|
|
|
|
|
|
|
# TODO 0.30 kill |
529
|
105
|
100
|
|
|
|
477
|
if (@_ == 2) { |
530
|
1
|
|
|
|
|
57
|
carp "set_path_defaults(): '/prefix' => \%values form is DEPRECATED, use \%values, path => '/prefix' instead"; |
531
|
1
|
|
|
|
|
776
|
push @_, path => shift; |
532
|
|
|
|
|
|
|
}; |
533
|
|
|
|
|
|
|
|
534
|
105
|
|
|
|
|
325
|
my ($values, %opt) = @_; |
535
|
|
|
|
|
|
|
|
536
|
105
|
100
|
|
|
|
691
|
$self->my_croak( "values must be a \%hash" ) |
537
|
|
|
|
|
|
|
unless ref $values eq 'HASH'; |
538
|
|
|
|
|
|
|
|
539
|
104
|
|
|
|
|
836
|
extra_missing( \%opt, { path => 1, method => 1 } ); |
540
|
|
|
|
|
|
|
|
541
|
104
|
|
66
|
|
|
2152
|
$self->{defaults} ||= MVC::Neaf::Util::Container->new; |
542
|
104
|
|
|
|
|
672
|
$self->{defaults}->store( $values, %opt ); |
543
|
|
|
|
|
|
|
|
544
|
104
|
|
|
|
|
251
|
return $self; |
545
|
|
|
|
|
|
|
}; |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
=head2 get_path_defaults |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
get_path_defaults ( $methods, $path, [ \%override ... ] ) |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
Fetch default values for given (path, method) combo as a single hash. |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
=cut |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
sub get_path_defaults { |
556
|
204
|
|
|
204
|
1
|
709
|
my ($self, $method, $path, @override) = @_; |
557
|
|
|
|
|
|
|
|
558
|
204
|
|
|
|
|
897
|
my @source = $self->{defaults}->fetch( method => $method, path => $path ); |
559
|
204
|
|
|
|
|
684
|
my %hash = map { %$_ } @source, grep defined, @override; |
|
420
|
|
|
|
|
1560
|
|
560
|
|
|
|
|
|
|
defined $hash{$_} or delete $hash{$_} |
561
|
204
|
|
66
|
|
|
1223
|
for keys %hash; |
562
|
|
|
|
|
|
|
|
563
|
204
|
|
|
|
|
834
|
\%hash; |
564
|
|
|
|
|
|
|
}; |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=head2 add_hook() |
568
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
$neaf->add_hook ( phase => CODEREF, %options ); |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
Set hook that will be executed on a given request processing phase. |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
Valid phases include: |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
=over |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
=item * pre_route [die] |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
=item * pre_logic [die] |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
=item * pre_content |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=item * pre_render [die] |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=item * pre_reply [reverse] |
586
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
=item * pre_cleanup [reverse] |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
=back |
590
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
See L below for detailed |
592
|
|
|
|
|
|
|
discussion of each phase. |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
The CODEREF receives one and only argument - the C<$request> object. |
595
|
|
|
|
|
|
|
Return value is B, see explanation below. |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
Use C<$request>'s C, C, and C methods |
598
|
|
|
|
|
|
|
for communication between hooks. |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
Dying in a hook MAY cause interruption of request processing |
601
|
|
|
|
|
|
|
or merely a warning, depending on the phase. |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
%options may include: |
604
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
=over |
606
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=item * path => '/path' - where the hook applies. Default is '/'. |
608
|
|
|
|
|
|
|
Multiple locations may be supplied via C<[ /foo, /bar ...]> |
609
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
=item * exclude => '/path/skip' - don't apply to these locations, |
611
|
|
|
|
|
|
|
even if under '/path'. |
612
|
|
|
|
|
|
|
Multiple locations may be supplied via C<[ /foo, /bar ...]> |
613
|
|
|
|
|
|
|
|
614
|
|
|
|
|
|
|
=item * method => 'METHOD' || [ list ] |
615
|
|
|
|
|
|
|
List of request HTTP methods to which given hook applies. |
616
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
=item * prepend => 0|1 - all other parameters being equal, |
618
|
|
|
|
|
|
|
hooks will be executed in order of adding. |
619
|
|
|
|
|
|
|
This option allows to override this and run given hook first. |
620
|
|
|
|
|
|
|
Note that this does NOT override path bubbling order. |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=back |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
=cut |
625
|
|
|
|
|
|
|
|
626
|
|
|
|
|
|
|
my %add_hook_args; |
627
|
|
|
|
|
|
|
$add_hook_args{$_}++ for qw(method path exclude prepend); |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
our %hook_phases; |
630
|
|
|
|
|
|
|
$hook_phases{$_}++ for qw(pre_route |
631
|
|
|
|
|
|
|
pre_logic pre_content pre_render pre_reply pre_cleanup); |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
sub add_hook { |
634
|
35
|
|
|
35
|
1
|
239
|
my ($self, $phase, $code, %opt) = @_; |
635
|
35
|
|
|
|
|
97
|
$self = _one_and_true($self); |
636
|
|
|
|
|
|
|
|
637
|
35
|
|
|
|
|
137
|
extra_missing( \%opt, \%add_hook_args ); |
638
|
35
|
100
|
|
|
|
136
|
$self->my_croak( "hook must be a coderef, not ".ref $code ) |
639
|
|
|
|
|
|
|
unless UNIVERSAL::isa( $code, 'CODE' ); |
640
|
|
|
|
|
|
|
$self->my_croak( "illegal phase: $phase" ) |
641
|
34
|
100
|
|
|
|
115
|
unless $hook_phases{$phase}; |
642
|
|
|
|
|
|
|
|
643
|
33
|
|
|
|
|
138
|
$opt{method} = maybe_list( $opt{method}, supported_methods() ); |
644
|
33
|
100
|
|
|
|
125
|
if ($phase eq 'pre_route') { |
645
|
|
|
|
|
|
|
# handle pre_route separately |
646
|
|
|
|
|
|
|
$self->my_croak("cannot specify paths/excludes for $phase") |
647
|
11
|
100
|
100
|
|
|
121
|
if defined $opt{path} || defined $opt{exclude}; |
648
|
|
|
|
|
|
|
}; |
649
|
|
|
|
|
|
|
|
650
|
31
|
|
|
|
|
94
|
$opt{path} = maybe_list( $opt{path}, '' ); |
651
|
31
|
|
50
|
|
|
314
|
$opt{caller} ||= [ caller(0) ]; # where the hook was set |
652
|
|
|
|
|
|
|
|
653
|
31
|
|
66
|
|
|
306
|
$self->{todo_hooks}{$phase} ||= MVC::Neaf::Util::Container->new; |
654
|
31
|
|
|
|
|
164
|
$self->{todo_hooks}{$phase}->store( $code, %opt ); |
655
|
|
|
|
|
|
|
|
656
|
31
|
|
|
|
|
139
|
return $self; |
657
|
|
|
|
|
|
|
}; |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
=head2 get_hooks |
660
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
get_hooks( $method, $path ) |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
Fetch all hooks previously set for given path as a { phase => [ list ] } hash. |
664
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
=cut |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
sub get_hooks { |
668
|
201
|
|
|
201
|
1
|
608
|
my ($self, $method, $path) = @_; |
669
|
|
|
|
|
|
|
|
670
|
201
|
|
|
|
|
321
|
my %ret; |
671
|
|
|
|
|
|
|
|
672
|
201
|
|
|
|
|
344
|
foreach my $phase ( keys %{ $self->{todo_hooks} } ) { |
|
201
|
|
|
|
|
690
|
|
673
|
51
|
|
|
|
|
156
|
$ret{$phase} = [ $self->{todo_hooks}{$phase}->fetch( method => $method, path => $path ) ]; |
674
|
|
|
|
|
|
|
}; |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
# Some hooks to be executed in reverse order |
677
|
13
|
|
|
|
|
40
|
$ret{$_} and @{ $ret{$_} } = reverse @{ $ret{$_} } |
|
13
|
|
|
|
|
26
|
|
678
|
201
|
|
66
|
|
|
920
|
for qw( pre_reply pre_cleanup ); |
679
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
# Prepend session handler unconditionally, if present |
681
|
201
|
100
|
|
|
|
661
|
if (my $key = $self->{session_view_as}) { |
682
|
3
|
|
|
|
|
12
|
unshift @{ $ret{pre_render} }, sub { |
683
|
2
|
|
|
2
|
|
9
|
$_[0]->reply->{$key} = $_[0]->load_session; |
684
|
3
|
|
|
|
|
4
|
}; |
685
|
|
|
|
|
|
|
}; |
686
|
|
|
|
|
|
|
|
687
|
201
|
100
|
|
|
|
636
|
if (my $force_view = $self->{force_view}) { |
688
|
|
|
|
|
|
|
# TODO 0.40 also push pre-rendered -content through force_view |
689
|
4
|
|
|
2
|
|
5
|
push @{ $ret{pre_render} }, sub { $_[0]->reply->{-view} = $force_view }; |
|
4
|
|
|
|
|
29
|
|
|
2
|
|
|
|
|
7
|
|
690
|
|
|
|
|
|
|
}; |
691
|
|
|
|
|
|
|
|
692
|
201
|
|
|
|
|
659
|
return \%ret; |
693
|
|
|
|
|
|
|
}; |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
=head2 set_helper |
696
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
set_helper( name => \&code, %options ) |
698
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
=cut |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
sub set_helper { |
702
|
18
|
|
|
18
|
1
|
74
|
my ($self, $name, $code, %opt) = @_; |
703
|
|
|
|
|
|
|
|
704
|
18
|
100
|
66
|
|
|
134
|
$self->my_croak( "helper must be a CODEREF, not ".ref $code ) |
705
|
|
|
|
|
|
|
unless ref $code and UNIVERSAL::isa( $code, 'CODE' ); |
706
|
17
|
|
|
|
|
65
|
_install_helper( $name ); |
707
|
|
|
|
|
|
|
|
708
|
14
|
|
66
|
|
|
134
|
$self->{todo_helpers}{$name} ||= MVC::Neaf::Util::Container->new( exclusive => 1 ); |
709
|
14
|
|
|
|
|
70
|
$self->{todo_helpers}{$name}->store( $code, %opt ); |
710
|
|
|
|
|
|
|
}; |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
sub _install_helper { |
713
|
17
|
|
|
17
|
|
36
|
my $name = shift; |
714
|
|
|
|
|
|
|
|
715
|
17
|
100
|
|
|
|
61
|
return if $MVC::Neaf::Request::allow_helper{$name}; |
716
|
|
|
|
|
|
|
|
717
|
7
|
100
|
100
|
|
|
81
|
croak( "NEAF: helper: inappropriate helper name '$name'" ) |
718
|
|
|
|
|
|
|
if $name !~ /^[a-z][a-z_0-9]*/ or $name =~ /^(?:do|neaf)/; |
719
|
|
|
|
|
|
|
|
720
|
5
|
100
|
|
|
|
93
|
croak "NEAF: helper: Cannot override existing method '$name' in Request" |
721
|
|
|
|
|
|
|
if MVC::Neaf::Request->can( $name ); |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
my $sub = sub { |
724
|
9
|
|
|
9
|
|
53
|
my $req = shift; |
725
|
|
|
|
|
|
|
|
726
|
9
|
|
|
|
|
39
|
my $code = $req->route->helpers->{$name}; |
727
|
9
|
100
|
|
|
|
26
|
croak ("Helper '$name' is not defined for ".$req->method." ".$req->route->path) |
728
|
|
|
|
|
|
|
unless $code; |
729
|
|
|
|
|
|
|
|
730
|
8
|
|
|
|
|
24
|
$code->( $req, @_ ); |
731
|
4
|
|
|
|
|
19
|
}; |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
# HACK magic here - plant method into request |
734
|
|
|
|
|
|
|
{ |
735
|
81
|
|
|
81
|
|
713
|
no strict 'refs'; ## no critic |
|
81
|
|
|
|
|
221
|
|
|
81
|
|
|
|
|
3315
|
|
|
4
|
|
|
|
|
7
|
|
736
|
81
|
|
|
81
|
|
572
|
use warnings FATAL => qw(all); |
|
81
|
|
|
|
|
212
|
|
|
81
|
|
|
|
|
372360
|
|
737
|
4
|
|
|
|
|
9
|
*{"MVC::Neaf::Request::$name"} = $sub; |
|
4
|
|
|
|
|
25
|
|
738
|
|
|
|
|
|
|
}; |
739
|
|
|
|
|
|
|
|
740
|
4
|
|
|
|
|
14
|
$MVC::Neaf::Request::allow_helper{$name}++; |
741
|
|
|
|
|
|
|
}; |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
=head2 get_helpers |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
=cut |
746
|
|
|
|
|
|
|
|
747
|
|
|
|
|
|
|
sub get_helpers { |
748
|
201
|
|
|
201
|
1
|
513
|
my ($self, $method, $path) = @_; |
749
|
|
|
|
|
|
|
|
750
|
201
|
|
|
|
|
394
|
my $todo = $self->{todo_helpers}; |
751
|
|
|
|
|
|
|
|
752
|
201
|
|
|
|
|
301
|
my %ret; |
753
|
201
|
|
|
|
|
677
|
foreach my $name( keys %$todo ) { |
754
|
29
|
|
|
|
|
91
|
my ($last) = reverse $todo->{$name}->fetch( method => $method, path => $path ); |
755
|
29
|
100
|
|
|
|
112
|
$ret{$name} = $last if $last; |
756
|
|
|
|
|
|
|
}; |
757
|
|
|
|
|
|
|
|
758
|
201
|
|
|
|
|
732
|
return \%ret; |
759
|
|
|
|
|
|
|
}; |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
=head2 load_view() |
762
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
load_view( "name", $view_object ); # stores object |
764
|
|
|
|
|
|
|
# assuming it's an L |
765
|
|
|
|
|
|
|
load_view( "name", $module_name, %params ); # calls new() |
766
|
|
|
|
|
|
|
load_view( "name", $module_alias ); # ditto, see list of aliases below |
767
|
|
|
|
|
|
|
load_view( "name", \&CODE ); # use that sub to generate |
768
|
|
|
|
|
|
|
# content from hash |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
Setup view under name C<$name>. |
771
|
|
|
|
|
|
|
Subsequent requests with C<-view = $name> would be processed by that view |
772
|
|
|
|
|
|
|
object. |
773
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
Use C to fetch the object itself. |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
=over |
777
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
=item * if object is given, just save it. |
779
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
=item * if module name + parameters is given, try to load module |
781
|
|
|
|
|
|
|
and create new() instance. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
Short aliases C, C, and C may be used |
784
|
|
|
|
|
|
|
for corresponding C modules. |
785
|
|
|
|
|
|
|
|
786
|
|
|
|
|
|
|
=item * if coderef is given, use it as a C method. |
787
|
|
|
|
|
|
|
|
788
|
|
|
|
|
|
|
=back |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
Returns the view object, NOT the object this method was called on. |
791
|
|
|
|
|
|
|
|
792
|
|
|
|
|
|
|
=cut |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
my %view_alias = ( |
795
|
|
|
|
|
|
|
TT => 'MVC::Neaf::View::TT', |
796
|
|
|
|
|
|
|
JS => 'MVC::Neaf::View::JS', |
797
|
|
|
|
|
|
|
Dumper => 'MVC::Neaf::View::Dumper', |
798
|
|
|
|
|
|
|
); |
799
|
|
|
|
|
|
|
sub load_view { |
800
|
40
|
|
|
40
|
1
|
177
|
my ($self, $name, $obj, @param) = @_; |
801
|
40
|
|
|
|
|
120
|
$self = _one_and_true($self); |
802
|
|
|
|
|
|
|
|
803
|
40
|
100
|
100
|
|
|
284
|
$self->my_croak("At least two arguments required") |
804
|
|
|
|
|
|
|
unless defined $name and defined $obj; |
805
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
# Instantiate if needed |
807
|
38
|
100
|
|
|
|
171
|
if (!ref $obj) { |
808
|
|
|
|
|
|
|
# in case an alias is used, apply alias |
809
|
36
|
|
33
|
|
|
185
|
$obj = $view_alias{ $obj } || $obj; |
810
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
# Try loading... |
812
|
36
|
100
|
|
|
|
370
|
if (!$obj->can("new")) { |
813
|
34
|
50
|
|
|
|
102
|
eval { load $obj; 1 } |
|
34
|
|
|
|
|
226
|
|
|
34
|
|
|
|
|
568
|
|
814
|
|
|
|
|
|
|
or $self->my_croak( "Failed to load view $name=>$obj: $@" ); |
815
|
|
|
|
|
|
|
}; |
816
|
36
|
|
|
|
|
445
|
$obj = $obj->new( neaf_base_dir => $self->neaf_base_dir, @param ); |
817
|
|
|
|
|
|
|
}; |
818
|
|
|
|
|
|
|
|
819
|
38
|
100
|
66
|
|
|
745
|
$self->my_croak( "view must be a coderef or a MVC::Neaf::View object" ) |
|
|
|
100
|
|
|
|
|
820
|
|
|
|
|
|
|
unless blessed $obj and $obj->can("render") |
821
|
|
|
|
|
|
|
or ref $obj eq 'CODE'; |
822
|
|
|
|
|
|
|
|
823
|
37
|
|
|
|
|
185
|
$self->{seen_view}{$name} = $obj; |
824
|
|
|
|
|
|
|
|
825
|
37
|
|
|
|
|
112
|
return $obj; |
826
|
|
|
|
|
|
|
}; |
827
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
=head2 set_forced_view() |
829
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
$neaf->set_forced_view( $view ) |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
If set, this view object will be user instead of ANY other view. |
833
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
See L. |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
Returns self. |
837
|
|
|
|
|
|
|
|
838
|
|
|
|
|
|
|
=cut |
839
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
sub set_forced_view { |
841
|
2
|
|
|
2
|
1
|
7
|
my ($self, $view) = @_; |
842
|
2
|
|
|
|
|
17
|
$self = _one_and_true($self); |
843
|
|
|
|
|
|
|
|
844
|
2
|
|
|
|
|
13
|
delete $self->{force_view}; |
845
|
2
|
50
|
|
|
|
9
|
return $self unless $view; |
846
|
|
|
|
|
|
|
|
847
|
2
|
|
|
|
|
12
|
$self->{force_view} = $self->get_view( $view ); |
848
|
|
|
|
|
|
|
|
849
|
2
|
|
|
|
|
7
|
return $self; |
850
|
|
|
|
|
|
|
}; |
851
|
|
|
|
|
|
|
|
852
|
|
|
|
|
|
|
=head2 magic( bool ) |
853
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
Get/set "magic" bit that triggers stuff like loading resources from __DATA__ |
855
|
|
|
|
|
|
|
on run() and such. |
856
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
Neaf is magical by default. |
858
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
=cut |
860
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
# Dumb accessor(boolean) |
862
|
|
|
|
|
|
|
sub magic { |
863
|
3
|
|
|
3
|
1
|
10
|
my $self = shift; |
864
|
3
|
100
|
|
|
|
9
|
if (@_) { |
865
|
1
|
|
|
|
|
3
|
$self->{magic} = !! shift; |
866
|
1
|
|
|
|
|
4
|
return $self; |
867
|
|
|
|
|
|
|
} else { |
868
|
2
|
|
|
|
|
13
|
return $self->{magic}; |
869
|
|
|
|
|
|
|
}; |
870
|
|
|
|
|
|
|
}; |
871
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
=head2 load_resources() |
873
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
$neaf->load_resources( $file_name || \*FH ) |
875
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
Load pseudo-files from a file (typically C<__DATA__>), |
877
|
|
|
|
|
|
|
say templates or static files. |
878
|
|
|
|
|
|
|
|
879
|
|
|
|
|
|
|
As of 0.27, load_resources happens automatically upon L, |
880
|
|
|
|
|
|
|
but only once for each calling file. |
881
|
|
|
|
|
|
|
Use Cmagic(0)> if you know better |
882
|
|
|
|
|
|
|
(e.g. you want to use __DATA__ for something else). |
883
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
The format is as follows: |
885
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
@@ /main.html view=TT |
887
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
[% some_tt_template %] |
889
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
@@ /favicon.ico format=base64 type=png |
891
|
|
|
|
|
|
|
|
892
|
|
|
|
|
|
|
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAAL |
893
|
|
|
|
|
|
|
GPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hS<....more encoded lines> |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
I, |
896
|
|
|
|
|
|
|
in a slightly incompatible way.> |
897
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
An entry starts with a literal C<@@>, followed by 1 or more spaces, |
899
|
|
|
|
|
|
|
followed by a slash and a file name, optionally followed by a list |
900
|
|
|
|
|
|
|
of options, and finally by a newline. |
901
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
Everything following the newline and until next such entry |
903
|
|
|
|
|
|
|
is considered file content. |
904
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
Options may include: |
906
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
=over |
908
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
=item * C |
910
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
=item * C |
912
|
|
|
|
|
|
|
|
913
|
|
|
|
|
|
|
=item * C - specify a template for given view(s) |
914
|
|
|
|
|
|
|
Leading slash will be stripped in this case. |
915
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
=back |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
Entries with unknown options will be skipped with a warning. |
919
|
|
|
|
|
|
|
|
920
|
|
|
|
|
|
|
B<[EXPERIMENTAL]> This method and exact format of data is being worked on. |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
=cut |
923
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
# TODO split this sub & move to a separate file |
925
|
|
|
|
|
|
|
my $INLINE_SPEC = qr/^(?:\[(\w+)\]\s+)?(\S+)((?:\s+\w+=\S+)*)$/; |
926
|
|
|
|
|
|
|
my %load_resources_opt; |
927
|
|
|
|
|
|
|
$load_resources_opt{$_}++ for qw( view format type ); |
928
|
|
|
|
|
|
|
sub load_resources { |
929
|
13
|
|
|
13
|
1
|
88
|
my ($self, $file, $name) = @_; |
930
|
|
|
|
|
|
|
|
931
|
13
|
100
|
66
|
|
|
62
|
if (!ref $file and defined $file) { |
932
|
2
|
50
|
|
|
|
99
|
open my $fd, "<", $file |
933
|
|
|
|
|
|
|
or $self->my_croak( "Failed to open(r) $file: $!" ); |
934
|
2
|
|
|
|
|
8
|
$name = $file; |
935
|
2
|
|
|
|
|
7
|
$file = $fd; |
936
|
|
|
|
|
|
|
}; |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
# Don't load the same filename twice |
939
|
|
|
|
|
|
|
return $self |
940
|
13
|
100
|
100
|
|
|
78
|
if defined $name and $self->{load_resources}{$name}++; |
941
|
|
|
|
|
|
|
|
942
|
12
|
|
|
|
|
23
|
my $content; |
943
|
|
|
|
|
|
|
|
944
|
12
|
100
|
|
|
|
52
|
if (ref $file eq 'GLOB') { |
|
|
100
|
|
|
|
|
|
945
|
6
|
|
|
|
|
27
|
local $/; |
946
|
6
|
|
|
|
|
167
|
$content = <$file>; |
947
|
6
|
50
|
|
|
|
40
|
defined $content |
948
|
|
|
|
|
|
|
or $self->my_croak( "Failed to read from $file: $!" ); |
949
|
6
|
|
|
|
|
93
|
close $file; |
950
|
|
|
|
|
|
|
# Die later |
951
|
|
|
|
|
|
|
} elsif (ref $file eq 'SCALAR') { |
952
|
5
|
|
|
|
|
11
|
$content = $$file; |
953
|
|
|
|
|
|
|
} else { |
954
|
1
|
|
|
|
|
4
|
$self->my_croak( "Argument must be a scalar, a scalar ref, or a file descriptor" ); |
955
|
|
|
|
|
|
|
}; |
956
|
|
|
|
|
|
|
|
957
|
11
|
100
|
|
|
|
46
|
defined $content |
958
|
|
|
|
|
|
|
or $self->my_croak( "Failed load content" ); |
959
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
# TODO 0.40 The regex should be: ^@@\s+(/\S+(?:\s+\w+=\S+)*)\s*$ |
961
|
|
|
|
|
|
|
# but we must deprecate '[TT] foo.html' first |
962
|
10
|
|
|
|
|
141
|
my @parts = split m{^@@\s+(\S.*?)\s*$}m, $content, -1; |
963
|
10
|
|
|
|
|
25
|
shift @parts; |
964
|
10
|
50
|
|
|
|
49
|
confess "NEAF load_resources failed unexpectedly, file a bug in MVC::Neaf" |
965
|
|
|
|
|
|
|
if @parts % 2; |
966
|
|
|
|
|
|
|
|
967
|
10
|
|
|
|
|
27
|
my %templates; |
968
|
|
|
|
|
|
|
my %static; |
969
|
10
|
|
|
|
|
33
|
while (@parts) { |
970
|
|
|
|
|
|
|
# parse pseudo-file |
971
|
17
|
|
|
|
|
36
|
my $spec = shift @parts; |
972
|
17
|
|
|
|
|
30
|
my $content = shift @parts; |
973
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
# process header |
975
|
17
|
|
|
|
|
147
|
my ($dest, $name, $extra) = ($spec =~ $INLINE_SPEC); |
976
|
17
|
50
|
|
|
|
53
|
$self->my_croak("Bad resource spec format @@ $spec") |
977
|
|
|
|
|
|
|
unless defined $name; |
978
|
17
|
|
|
|
|
77
|
my %opt = $extra =~ /(\w+)=(\S+)/g; |
979
|
17
|
100
|
|
|
|
46
|
if ($dest) { |
980
|
1
|
|
|
|
|
4
|
$opt{view} = $dest; |
981
|
1
|
|
|
|
|
25
|
carp "DEPRECATED '@@ [$dest]' resource format," |
982
|
|
|
|
|
|
|
." use '@@ $name view=$dest' instead"; |
983
|
|
|
|
|
|
|
}; |
984
|
|
|
|
|
|
|
|
985
|
17
|
100
|
|
|
|
843
|
if ( my @unknown = grep { !$load_resources_opt{$_} } keys %opt ) { |
|
14
|
|
|
|
|
59
|
|
986
|
1
|
|
|
|
|
26
|
carp "Unknown options (@unknown) in '@@ name' in $file, skipping"; |
987
|
1
|
|
|
|
|
701
|
next; |
988
|
|
|
|
|
|
|
}; |
989
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
# process content |
991
|
16
|
100
|
|
|
|
52
|
if (!$opt{format}) { |
|
|
50
|
|
|
|
|
|
992
|
13
|
|
|
|
|
54
|
$content =~ s/^\n+//s; |
993
|
13
|
|
|
|
|
55
|
$content =~ s/\s+$//s; |
994
|
13
|
|
|
|
|
174
|
$content = Encode::decode_utf8( $content, 1 ); |
995
|
|
|
|
|
|
|
} elsif ($opt{format} eq 'base64') { |
996
|
3
|
|
|
|
|
10
|
$content = decode_b64( $content ); |
997
|
|
|
|
|
|
|
} else { |
998
|
|
|
|
|
|
|
# TODO 0.50 calculate line |
999
|
0
|
|
|
|
|
0
|
$self->my_croak("Unknown format $opt{format} in '@@ $spec' in $file"); |
1000
|
|
|
|
|
|
|
}; |
1001
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
# store for loading |
1003
|
16
|
100
|
|
|
|
146
|
if (defined( my $view = $opt{view} )) { |
1004
|
|
|
|
|
|
|
# template |
1005
|
|
|
|
|
|
|
$self->my_croak("Duplicate template '@@ $spec' in $file") |
1006
|
9
|
50
|
|
|
|
33
|
if defined $templates{$view}{$name}; |
1007
|
9
|
|
|
|
|
42
|
$templates{$view}{$name} = $content; |
1008
|
|
|
|
|
|
|
} else { |
1009
|
|
|
|
|
|
|
# static file |
1010
|
|
|
|
|
|
|
$self->my_croak("Duplicate static file '@@ $spec' in $file") |
1011
|
7
|
100
|
|
|
|
48
|
if $static{$name}; |
1012
|
6
|
|
|
|
|
36
|
$static{$name} = [ $content, $opt{type} ]; |
1013
|
|
|
|
|
|
|
}; |
1014
|
|
|
|
|
|
|
}; # end while @parts |
1015
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
# now do the loading |
1017
|
9
|
|
|
|
|
37
|
foreach my $name( keys %templates ) { |
1018
|
6
|
|
|
|
|
614
|
my $view = $self->get_view( $name, 1 ); |
1019
|
6
|
100
|
|
|
|
53
|
if (!$view) { |
|
|
100
|
|
|
|
|
|
1020
|
2
|
|
|
|
|
37
|
carp "NEAF: Unknown view $name mentioned in $file"; |
1021
|
|
|
|
|
|
|
} elsif ($view->can("preload")) { |
1022
|
3
|
|
|
|
|
8
|
$view->preload( %{ $templates{$name} } ); |
|
3
|
|
|
|
|
20
|
|
1023
|
|
|
|
|
|
|
} else { |
1024
|
1
|
|
|
|
|
15
|
carp "NEAF: View $name mentioned in $file doesn't support template preloading"; |
1025
|
|
|
|
|
|
|
}; |
1026
|
|
|
|
|
|
|
}; |
1027
|
9
|
100
|
|
|
|
1113
|
if( %static ) { |
1028
|
5
|
|
|
|
|
57
|
my $st = $self->_static_global; |
1029
|
5
|
|
|
|
|
30
|
$st->preload( %static ); |
1030
|
5
|
|
|
|
|
20
|
foreach( keys %static ) { |
1031
|
5
|
|
|
|
|
22
|
$self->add_route( $_ => $st->one_file_handler, method => 'GET' |
1032
|
|
|
|
|
|
|
, description => "Static resource from $file" ); |
1033
|
|
|
|
|
|
|
}; |
1034
|
|
|
|
|
|
|
}; |
1035
|
|
|
|
|
|
|
|
1036
|
9
|
|
|
|
|
57
|
return $self; |
1037
|
|
|
|
|
|
|
}; |
1038
|
|
|
|
|
|
|
|
1039
|
|
|
|
|
|
|
=head2 set_session_handler() |
1040
|
|
|
|
|
|
|
|
1041
|
|
|
|
|
|
|
$neaf->set_session_handler( %options ) |
1042
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
Set a handler for managing sessions. |
1044
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
If such handler is set, the request object will provide C, |
1046
|
|
|
|
|
|
|
C, and C methods to manage |
1047
|
|
|
|
|
|
|
cross-request user data. |
1048
|
|
|
|
|
|
|
|
1049
|
|
|
|
|
|
|
% options may include: |
1050
|
|
|
|
|
|
|
|
1051
|
|
|
|
|
|
|
=over |
1052
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
=item * C (required in method form, first argument in DSL form) |
1054
|
|
|
|
|
|
|
- an object providing the storage primitives; |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
=item * C - time to live for session (default is 0, which means until |
1057
|
|
|
|
|
|
|
browser is closed); |
1058
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
=item * C - name of cookie storing session id. |
1060
|
|
|
|
|
|
|
The default is "session". |
1061
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
=item * C - if set, add the whole session into data hash |
1063
|
|
|
|
|
|
|
under this name before view processing. |
1064
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
=back |
1066
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
The engine MUST provide the following methods |
1068
|
|
|
|
|
|
|
(see L for details): |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
=over |
1071
|
|
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
=item * session_ttl (implemented in MVC::Neaf::X::Session); |
1073
|
|
|
|
|
|
|
|
1074
|
|
|
|
|
|
|
=item * session_id_regex (implemented in MVC::Neaf::X::Session); |
1075
|
|
|
|
|
|
|
|
1076
|
|
|
|
|
|
|
=item * get_session_id (implemented in MVC::Neaf::X::Session); |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
=item * create_session (implemented in MVC::Neaf::X::Session); |
1079
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
=item * save_session (required); |
1081
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
=item * load_session (required); |
1083
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
=item * delete_session (implemented in MVC::Neaf::X::Session); |
1085
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
=back |
1087
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
=cut |
1089
|
|
|
|
|
|
|
|
1090
|
|
|
|
|
|
|
sub set_session_handler { |
1091
|
5
|
|
|
5
|
1
|
27
|
my ($self, %opt) = @_; # TODO 0.30 use helpers when ready |
1092
|
5
|
|
|
|
|
19
|
$self = _one_and_true($self); |
1093
|
|
|
|
|
|
|
|
1094
|
5
|
|
|
|
|
20
|
my $sess = delete $opt{engine}; |
1095
|
5
|
|
100
|
|
|
28
|
my $cook = $opt{cookie} || 'neaf.session'; |
1096
|
|
|
|
|
|
|
|
1097
|
5
|
50
|
|
|
|
19
|
$self->my_croak("engine parameter is required") |
1098
|
|
|
|
|
|
|
unless $sess; |
1099
|
|
|
|
|
|
|
|
1100
|
5
|
100
|
|
|
|
20
|
if (!ref $sess) { |
1101
|
2
|
|
33
|
|
|
12
|
$opt{session_ttl} = delete $opt{ttl} || $opt{session_ttl}; |
1102
|
|
|
|
|
|
|
|
1103
|
2
|
50
|
|
|
|
3
|
my $obj = eval { load $sess; $sess->new( %opt ); } |
|
2
|
|
|
|
|
8
|
|
|
2
|
|
|
|
|
53
|
|
1104
|
|
|
|
|
|
|
or $self->my_croak("Failed to load session '$sess': $@"); |
1105
|
|
|
|
|
|
|
|
1106
|
2
|
|
|
|
|
5
|
$sess = $obj; |
1107
|
|
|
|
|
|
|
}; |
1108
|
|
|
|
|
|
|
|
1109
|
5
|
|
|
|
|
17
|
my @missing = grep { !$sess->can($_) } |
|
35
|
|
|
|
|
163
|
|
1110
|
|
|
|
|
|
|
qw(get_session_id session_id_regex session_ttl |
1111
|
|
|
|
|
|
|
create_session load_session save_session delete_session ); |
1112
|
5
|
50
|
|
|
|
19
|
$self->my_croak("engine object does not have methods: @missing") |
1113
|
|
|
|
|
|
|
if @missing; |
1114
|
|
|
|
|
|
|
|
1115
|
5
|
|
|
|
|
23
|
my $regex = $sess->session_id_regex; |
1116
|
5
|
|
50
|
|
|
24
|
my $ttl = $opt{ttl} || $sess->session_ttl || 0; |
1117
|
|
|
|
|
|
|
|
1118
|
5
|
|
|
|
|
27
|
my $setup = { |
1119
|
|
|
|
|
|
|
engine => $sess, |
1120
|
|
|
|
|
|
|
cookie => $cook, |
1121
|
|
|
|
|
|
|
regex => $regex, |
1122
|
|
|
|
|
|
|
ttl => $ttl, |
1123
|
|
|
|
|
|
|
}; |
1124
|
|
|
|
|
|
|
|
1125
|
5
|
|
|
20
|
|
44
|
$self->set_helper( _session_setup => sub { $setup }, override => 1 ); |
|
20
|
|
|
|
|
80
|
|
1126
|
5
|
|
|
|
|
18
|
$self->{session_view_as} = $opt{view_as}; |
1127
|
5
|
|
|
|
|
21
|
return $self; |
1128
|
|
|
|
|
|
|
}; |
1129
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
=head2 set_error_handler() |
1131
|
|
|
|
|
|
|
|
1132
|
|
|
|
|
|
|
$neaf->set_error_handler ( $status => CODEREF( $request, %options ), %where ) |
1133
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
Set custom error handler. |
1135
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
Status MUST be a 3-digit number (as in HTTP). |
1137
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
%where may include C, C, and C keys. |
1139
|
|
|
|
|
|
|
If omitted, just install error handler globally. |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
Other allowed keys MAY appear in the future. |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
The following options will be passed to coderef: |
1144
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
=over |
1146
|
|
|
|
|
|
|
|
1147
|
|
|
|
|
|
|
=item * status - status being returned; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
=item * caller - file:line where the route was set up; |
1150
|
|
|
|
|
|
|
This is DEPRECATED and will silently disappear around version 0.25 |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
=item * error - exception, an L object. |
1153
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
=back |
1155
|
|
|
|
|
|
|
|
1156
|
|
|
|
|
|
|
The coderef MUST return an unblessed hash just like a normal controller does. |
1157
|
|
|
|
|
|
|
|
1158
|
|
|
|
|
|
|
In case of exception or unexpected return format |
1159
|
|
|
|
|
|
|
default HTML error page will be returned. |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
Also available in static form, as C \%hash )>. |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
This is a synonym to C $status, ... } }>. |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
=cut |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
sub set_error_handler { |
1168
|
9
|
|
|
9
|
1
|
49
|
my ($self, $status, $code, %where) = @_; |
1169
|
9
|
|
|
|
|
27
|
$self = _one_and_true($self); |
1170
|
|
|
|
|
|
|
|
1171
|
9
|
50
|
|
|
|
40
|
$status =~ /^(?:\d\d\d)$/ |
1172
|
|
|
|
|
|
|
or $self->my_croak( "1st argument must be an http status"); |
1173
|
9
|
|
|
|
|
67
|
extra_missing( \%where, { path => 1, exclude => 1, method => 1 } ); |
1174
|
|
|
|
|
|
|
|
1175
|
9
|
100
|
|
|
|
41
|
if (ref $code eq 'HASH') { |
1176
|
2
|
|
|
|
|
5
|
my $hash = $code; |
1177
|
|
|
|
|
|
|
$code = sub { |
1178
|
3
|
|
|
3
|
|
14
|
my ($req, %opt) = @_; |
1179
|
|
|
|
|
|
|
|
1180
|
3
|
|
|
|
|
35
|
return { -status => $opt{status}, %opt, %$hash }; |
1181
|
2
|
|
|
|
|
11
|
}; |
1182
|
|
|
|
|
|
|
}; |
1183
|
9
|
50
|
|
|
|
49
|
reftype $code eq 'CODE' |
1184
|
|
|
|
|
|
|
or $self->my_croak( "2nd argument must be a callback or hash"); |
1185
|
|
|
|
|
|
|
|
1186
|
9
|
|
66
|
|
|
92
|
my $store = $self->{error_template}{$status} |
1187
|
|
|
|
|
|
|
||= MVC::Neaf::Util::Container->new(); |
1188
|
|
|
|
|
|
|
|
1189
|
9
|
|
|
|
|
48
|
$store->store( $code, %where ); |
1190
|
|
|
|
|
|
|
|
1191
|
9
|
|
|
|
|
34
|
return $self; |
1192
|
|
|
|
|
|
|
}; |
1193
|
|
|
|
|
|
|
|
1194
|
|
|
|
|
|
|
=head2 on_error() |
1195
|
|
|
|
|
|
|
|
1196
|
|
|
|
|
|
|
on_error( sub { my ($request, $error) = @_ } ) |
1197
|
|
|
|
|
|
|
|
1198
|
|
|
|
|
|
|
Install custom error handler for a dying controller. |
1199
|
|
|
|
|
|
|
Neaf's own exceptions, redirects, and C status returns will NOT |
1200
|
|
|
|
|
|
|
trigger it. |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
E.g. write to log, or something. |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
Return value from this callback is ignored. |
1205
|
|
|
|
|
|
|
If it dies, only a warning is emitted. |
1206
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
=cut |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
sub on_error { |
1210
|
1
|
|
|
1
|
1
|
4
|
my ($self, $code) = @_; |
1211
|
1
|
|
|
|
|
3
|
$self = _one_and_true($self); |
1212
|
|
|
|
|
|
|
|
1213
|
1
|
50
|
|
|
|
5
|
if (defined $code) { |
1214
|
1
|
50
|
|
|
|
4
|
ref $code eq 'CODE' |
1215
|
|
|
|
|
|
|
or $self->my_croak( "Argument MUST be a callback" ); |
1216
|
1
|
|
|
|
|
3
|
$self->{on_error} = $code; |
1217
|
|
|
|
|
|
|
} else { |
1218
|
0
|
|
|
|
|
0
|
delete $self->{on_error}; |
1219
|
|
|
|
|
|
|
}; |
1220
|
|
|
|
|
|
|
|
1221
|
1
|
|
|
|
|
2
|
return $self; |
1222
|
|
|
|
|
|
|
}; |
1223
|
|
|
|
|
|
|
|
1224
|
|
|
|
|
|
|
=head2 post_setup |
1225
|
|
|
|
|
|
|
|
1226
|
|
|
|
|
|
|
This function is run after configuration has been completed, |
1227
|
|
|
|
|
|
|
but before first request is served. |
1228
|
|
|
|
|
|
|
|
1229
|
|
|
|
|
|
|
It goes as follows: |
1230
|
|
|
|
|
|
|
|
1231
|
|
|
|
|
|
|
=over |
1232
|
|
|
|
|
|
|
|
1233
|
|
|
|
|
|
|
=item * compile all the routes into a giant regexp; |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
=item * Add HEAD handling to where only GET exists; |
1236
|
|
|
|
|
|
|
|
1237
|
|
|
|
|
|
|
=item * finish set_session_handler works |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
=item * set the lock on route; |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=back |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
Despite the locking, further modifications are not prohibited. |
1244
|
|
|
|
|
|
|
This MAY change in the future. |
1245
|
|
|
|
|
|
|
|
1246
|
|
|
|
|
|
|
=cut |
1247
|
|
|
|
|
|
|
|
1248
|
|
|
|
|
|
|
sub post_setup { |
1249
|
170
|
|
|
170
|
1
|
314
|
my $self = shift; |
1250
|
|
|
|
|
|
|
|
1251
|
|
|
|
|
|
|
# TODO 0.30 disallow calling this method twice |
1252
|
|
|
|
|
|
|
# confess "Attempt to call post_setup twice" |
1253
|
|
|
|
|
|
|
# if $self->{lock}; |
1254
|
|
|
|
|
|
|
|
1255
|
170
|
|
66
|
|
|
1074
|
$self->{route_re} ||= $self->_make_route_re; |
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
# Add implicit HEAD for all GETs via shallow copy |
1258
|
170
|
|
|
|
|
345
|
foreach my $node (values %{ $self->{route} }) { |
|
170
|
|
|
|
|
677
|
|
1259
|
268
|
100
|
|
|
|
1010
|
$node->{GET} or next; |
1260
|
251
|
|
66
|
|
|
1084
|
$node->{HEAD} ||= $node->{GET}->clone( method => 'HEAD' ); |
1261
|
|
|
|
|
|
|
}; |
1262
|
|
|
|
|
|
|
|
1263
|
170
|
|
|
|
|
492
|
$self->{lock}++; |
1264
|
|
|
|
|
|
|
}; |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
# Create a giant regexp from a hash of paths |
1267
|
|
|
|
|
|
|
# PURE |
1268
|
|
|
|
|
|
|
# The regex can be matched against an URI path, |
1269
|
|
|
|
|
|
|
# in which case it returns either nothing, |
1270
|
|
|
|
|
|
|
# or mathed route in $1 (prefix) and the rest of the string in $2 (postfix) |
1271
|
|
|
|
|
|
|
sub _make_route_re { |
1272
|
75
|
|
|
75
|
|
250
|
my ($self, $hash) = @_; |
1273
|
|
|
|
|
|
|
|
1274
|
75
|
|
66
|
|
|
517
|
$hash ||= $self->{route}; |
1275
|
|
|
|
|
|
|
|
1276
|
|
|
|
|
|
|
# Make longest paths come first |
1277
|
75
|
|
|
|
|
486
|
my @path_list = reverse sort keys %$hash; |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
# escape all metacharacters except / |
1280
|
|
|
|
|
|
|
# which is converted to '/+' so that foo///bar is also matched |
1281
|
|
|
|
|
|
|
my $re = join "|", map { |
1282
|
75
|
|
|
|
|
305
|
join '/+', map { |
1283
|
98
|
|
|
|
|
698
|
quotemeta |
1284
|
188
|
|
|
|
|
815
|
} split /\/+/, $_ |
1285
|
|
|
|
|
|
|
} @path_list; |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
# split path into (/foo/bar)/(baz)?param=value |
1288
|
|
|
|
|
|
|
# return prefix as $1 and postfix as $2, if present |
1289
|
75
|
|
|
|
|
3321
|
return qr{^($re)(?:/+([^?]*))?(?:\?|$)}; |
1290
|
|
|
|
|
|
|
}; |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
=head2 run() |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
$neaf->run(); |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
Run the application. |
1297
|
|
|
|
|
|
|
This SHOULD be the last statement in your application's main file. |
1298
|
|
|
|
|
|
|
|
1299
|
|
|
|
|
|
|
When run() is called, the routes are compiled into one giant regex, |
1300
|
|
|
|
|
|
|
and the post-setup is run, if needed. |
1301
|
|
|
|
|
|
|
|
1302
|
|
|
|
|
|
|
Additionally if neaf is in magical mode, |
1303
|
|
|
|
|
|
|
L is called on the enclosing file's DATA descriptor. |
1304
|
|
|
|
|
|
|
Magic mode is on by default. See L. |
1305
|
|
|
|
|
|
|
|
1306
|
|
|
|
|
|
|
If called in void context, assumes execution as C |
1307
|
|
|
|
|
|
|
and prints results to C. |
1308
|
|
|
|
|
|
|
If command line options are present at the moment, |
1309
|
|
|
|
|
|
|
enters debug mode via L. |
1310
|
|
|
|
|
|
|
Call C for more. |
1311
|
|
|
|
|
|
|
|
1312
|
|
|
|
|
|
|
Otherwise returns a C-compliant coderef. |
1313
|
|
|
|
|
|
|
This will also happen if you application is C'd, |
1314
|
|
|
|
|
|
|
meaning that it returns a true value and actually serves nothing until |
1315
|
|
|
|
|
|
|
C is called again. |
1316
|
|
|
|
|
|
|
|
1317
|
|
|
|
|
|
|
Running under mod_perl requires setting a handler with |
1318
|
|
|
|
|
|
|
L. |
1319
|
|
|
|
|
|
|
|
1320
|
|
|
|
|
|
|
=cut |
1321
|
|
|
|
|
|
|
|
1322
|
|
|
|
|
|
|
sub run { |
1323
|
176
|
|
|
176
|
1
|
12396
|
my $self = shift; |
1324
|
176
|
|
|
|
|
445
|
$self = _one_and_true($self); |
1325
|
|
|
|
|
|
|
|
1326
|
|
|
|
|
|
|
# "Magically" load __DATA__ section from calling file |
1327
|
176
|
100
|
|
|
|
701
|
if ($self->{magic}) { |
1328
|
174
|
|
|
|
|
674
|
my ($file, $data) = data_fh(1); |
1329
|
174
|
100
|
|
|
|
535
|
$self->load_resources( $data, $file ) |
1330
|
|
|
|
|
|
|
if $data; |
1331
|
|
|
|
|
|
|
}; |
1332
|
|
|
|
|
|
|
|
1333
|
176
|
100
|
|
|
|
524
|
if (!defined wantarray) { |
1334
|
|
|
|
|
|
|
# void context - we're being called as CGI |
1335
|
6
|
100
|
|
|
|
18
|
if (@ARGV) { |
1336
|
5
|
|
|
|
|
30
|
require MVC::Neaf::CLI; |
1337
|
5
|
|
|
|
|
27
|
MVC::Neaf::CLI->run($self); |
1338
|
|
|
|
|
|
|
} else { |
1339
|
1
|
|
|
|
|
413
|
require Plack::Handler::CGI; |
1340
|
|
|
|
|
|
|
# Somehow this caused uninitialized warning in Plack::Handler::CGI |
1341
|
|
|
|
|
|
|
$ENV{SCRIPT_NAME} = '' |
1342
|
1
|
50
|
|
|
|
861
|
unless defined $ENV{SCRIPT_NAME}; |
1343
|
1
|
|
|
|
|
5
|
Plack::Handler::CGI->new->run( $self->run ); |
1344
|
|
|
|
|
|
|
}; |
1345
|
6
|
|
|
|
|
139
|
return; |
1346
|
|
|
|
|
|
|
}; |
1347
|
|
|
|
|
|
|
|
1348
|
|
|
|
|
|
|
# Do postsetup after CGI/CLI execution |
1349
|
|
|
|
|
|
|
# because it's unneeded there - only one route may be needed so why bother |
1350
|
170
|
|
|
|
|
898
|
$self->post_setup; |
1351
|
|
|
|
|
|
|
|
1352
|
|
|
|
|
|
|
return sub { |
1353
|
21
|
|
|
21
|
|
25756
|
$self->handle_request( |
1354
|
|
|
|
|
|
|
MVC::Neaf::Request::PSGI->new( env => $_[0] )); |
1355
|
170
|
|
|
|
|
1269
|
}; |
1356
|
|
|
|
|
|
|
}; |
1357
|
|
|
|
|
|
|
|
1358
|
|
|
|
|
|
|
=head1 INTROSPECTION AND TESTING METHODS |
1359
|
|
|
|
|
|
|
|
1360
|
|
|
|
|
|
|
=head2 run_test() |
1361
|
|
|
|
|
|
|
|
1362
|
|
|
|
|
|
|
$neaf->run_test( \%PSGI_ENV, %options ) |
1363
|
|
|
|
|
|
|
|
1364
|
|
|
|
|
|
|
$neaf->run_test( "/path?parameter=value", %options ) |
1365
|
|
|
|
|
|
|
|
1366
|
|
|
|
|
|
|
Run a L request and return a list of |
1367
|
|
|
|
|
|
|
C<($status, HTTP::Headers::Fast, $whole_content )>. |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
Returns just the content in scalar context. |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
Just as the name suggests, useful for testing only (it reduces boilerplate). |
1372
|
|
|
|
|
|
|
|
1373
|
|
|
|
|
|
|
Continuation responses are supported, but will be returned in one chunk. |
1374
|
|
|
|
|
|
|
|
1375
|
|
|
|
|
|
|
%options may include: |
1376
|
|
|
|
|
|
|
|
1377
|
|
|
|
|
|
|
=over |
1378
|
|
|
|
|
|
|
|
1379
|
|
|
|
|
|
|
=item * method - set method (default is GET) |
1380
|
|
|
|
|
|
|
|
1381
|
|
|
|
|
|
|
=item * cookie = \%hash - force HTTP_COOKIE header |
1382
|
|
|
|
|
|
|
|
1383
|
|
|
|
|
|
|
=item * header = \%hash - override some headers |
1384
|
|
|
|
|
|
|
This gets overridden by type, cookie etc. in case of conflict |
1385
|
|
|
|
|
|
|
|
1386
|
|
|
|
|
|
|
=item * body = 'DATA' - force body in request |
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
=item * type - content-type of body |
1389
|
|
|
|
|
|
|
|
1390
|
|
|
|
|
|
|
=item * uploads - a hash of L objects. |
1391
|
|
|
|
|
|
|
|
1392
|
|
|
|
|
|
|
=item * secure = 0|1 - C vs C |
1393
|
|
|
|
|
|
|
|
1394
|
|
|
|
|
|
|
=item * override = \%hash - force certain data in C |
1395
|
|
|
|
|
|
|
Gets overridden by all of the above. |
1396
|
|
|
|
|
|
|
|
1397
|
|
|
|
|
|
|
=back |
1398
|
|
|
|
|
|
|
|
1399
|
|
|
|
|
|
|
=cut |
1400
|
|
|
|
|
|
|
|
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
my %run_test_allow; |
1403
|
|
|
|
|
|
|
$run_test_allow{$_}++ |
1404
|
|
|
|
|
|
|
for qw( type method cookie body override secure uploads header ); |
1405
|
|
|
|
|
|
|
sub run_test { |
1406
|
135
|
|
|
135
|
1
|
12426
|
my ($self, $env, %opt) = @_; |
1407
|
135
|
|
|
|
|
428
|
$self = _one_and_true($self); |
1408
|
|
|
|
|
|
|
|
1409
|
135
|
|
|
|
|
479
|
my @extra = grep { !$run_test_allow{$_} } keys %opt; |
|
48
|
|
|
|
|
170
|
|
1410
|
135
|
50
|
|
|
|
467
|
$self->my_croak( "Extra keys @extra" ) |
1411
|
|
|
|
|
|
|
if @extra; |
1412
|
|
|
|
|
|
|
|
1413
|
135
|
100
|
|
|
|
428
|
if (!ref $env) { |
1414
|
130
|
|
|
|
|
992
|
$env =~ /^(.*?)(?:\?(.*))?$/; |
1415
|
130
|
100
|
|
|
|
1647
|
$env = { |
1416
|
|
|
|
|
|
|
REQUEST_URI => $env, |
1417
|
|
|
|
|
|
|
REQUEST_METHOD => 'GET', |
1418
|
|
|
|
|
|
|
QUERY_STRING => defined $2 ? $2 : '', |
1419
|
|
|
|
|
|
|
SERVER_NAME => 'localhost', |
1420
|
|
|
|
|
|
|
SERVER_PORT => 80, |
1421
|
|
|
|
|
|
|
SCRIPT_NAME => '', |
1422
|
|
|
|
|
|
|
PATH_INFO => $1, |
1423
|
|
|
|
|
|
|
'psgi.version' => [1,1], |
1424
|
|
|
|
|
|
|
'psgi.errors' => \*STDERR, |
1425
|
|
|
|
|
|
|
} |
1426
|
|
|
|
|
|
|
}; |
1427
|
|
|
|
|
|
|
# TODO 0.30 complete emulation of everything a sane person needs |
1428
|
135
|
100
|
|
|
|
555
|
$env->{REQUEST_METHOD} = $opt{method} if $opt{method}; |
1429
|
135
|
|
|
|
|
272
|
$env->{$_} = $opt{override}{$_} for keys %{ $opt{override} }; |
|
135
|
|
|
|
|
578
|
|
1430
|
|
|
|
|
|
|
|
1431
|
135
|
100
|
|
|
|
546
|
if (my $head = $opt{header} ) { |
1432
|
4
|
|
|
|
|
11
|
foreach (keys %$head) { |
1433
|
4
|
|
|
|
|
10
|
my $name = uc $_; |
1434
|
4
|
|
|
|
|
8
|
$name =~ tr/-/_/; |
1435
|
4
|
|
|
|
|
15
|
$env->{"HTTP_$name"} = $head->{$_}; |
1436
|
|
|
|
|
|
|
}; |
1437
|
|
|
|
|
|
|
}; |
1438
|
135
|
100
|
|
|
|
426
|
if (exists $opt{secure}) { |
1439
|
1
|
50
|
|
|
|
6
|
$env->{'psgi.url_scheme'} = $opt{secure} ? 'https' : 'http'; |
1440
|
|
|
|
|
|
|
}; |
1441
|
135
|
100
|
|
|
|
428
|
if (my $cook = $opt{cookie}) { |
1442
|
14
|
100
|
|
|
|
44
|
if (ref $cook eq 'HASH') { |
1443
|
|
|
|
|
|
|
$cook = join '; ', map { |
1444
|
12
|
|
|
|
|
39
|
uri_escape_utf8($_).'='.uri_escape_utf8($cook->{$_}) |
|
13
|
|
|
|
|
105
|
|
1445
|
|
|
|
|
|
|
} keys %$cook; |
1446
|
|
|
|
|
|
|
}; |
1447
|
|
|
|
|
|
|
$env->{HTTP_COOKIE} = $env->{HTTP_COOKIE} |
1448
|
14
|
50
|
|
|
|
588
|
? "$env->{HTTP_COOKIE}; $cook" |
1449
|
|
|
|
|
|
|
: $cook; |
1450
|
|
|
|
|
|
|
}; |
1451
|
135
|
100
|
|
|
|
410
|
if (my $body = $opt{body} ) { |
1452
|
6
|
50
|
|
2
|
|
146
|
open my $dummy, "<", \$body |
|
2
|
|
|
|
|
16
|
|
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
14
|
|
1453
|
|
|
|
|
|
|
or die ("NEAF: FATAL: Redirect failed in run_test"); |
1454
|
6
|
|
|
|
|
1704
|
$env->{'psgi.input'} = $dummy; |
1455
|
6
|
|
|
|
|
20
|
$env->{CONTENT_LENGTH} = length $body; |
1456
|
|
|
|
|
|
|
}; |
1457
|
135
|
100
|
|
|
|
399
|
if (my $type = $opt{type}) { |
1458
|
1
|
50
|
|
|
|
4
|
$type = 'application/x-www-form-urlencoded' if $type eq '?'; |
1459
|
|
|
|
|
|
|
$env->{CONTENT_TYPE} = $opt{type} eq '?' ? '' : $opt{type} |
1460
|
1
|
50
|
|
|
|
8
|
}; |
1461
|
|
|
|
|
|
|
|
1462
|
135
|
|
|
|
|
239
|
my %fake; |
1463
|
135
|
|
|
|
|
397
|
$fake{uploads} = delete $opt{uploads}; |
1464
|
|
|
|
|
|
|
|
1465
|
135
|
|
|
|
|
677
|
scalar $self->run; # warm up caches |
1466
|
|
|
|
|
|
|
|
1467
|
135
|
|
|
|
|
1822
|
my $req = MVC::Neaf::Request::PSGI->new( %fake, env => $env ); |
1468
|
|
|
|
|
|
|
|
1469
|
135
|
|
|
|
|
638
|
my $ret = $self->handle_request( $req ); |
1470
|
135
|
100
|
|
|
|
548
|
if (ref $ret eq 'CODE') { |
1471
|
|
|
|
|
|
|
# PSGI functional interface used. |
1472
|
5
|
|
|
|
|
1831
|
require MVC::Neaf::Request::FakeWriter; |
1473
|
5
|
|
|
|
|
39
|
$ret = MVC::Neaf::Request::FakeWriter->new->respond( $ret ); |
1474
|
|
|
|
|
|
|
}; |
1475
|
|
|
|
|
|
|
|
1476
|
|
|
|
|
|
|
return ( |
1477
|
|
|
|
|
|
|
$ret->[0], |
1478
|
135
|
|
|
|
|
565
|
HTTP::Headers::Fast->new( @{ $ret->[1] } ), |
1479
|
135
|
|
|
|
|
326
|
join '', @{ $ret->[2] }, |
|
135
|
|
|
|
|
11569
|
|
1480
|
|
|
|
|
|
|
); |
1481
|
|
|
|
|
|
|
}; |
1482
|
|
|
|
|
|
|
|
1483
|
|
|
|
|
|
|
=head2 get_routes() |
1484
|
|
|
|
|
|
|
|
1485
|
|
|
|
|
|
|
$neaf->get_routes( $callback->(\%route_spec, $path, $method) ) |
1486
|
|
|
|
|
|
|
|
1487
|
|
|
|
|
|
|
Returns a 2-level hashref with ALL routes for inspection. |
1488
|
|
|
|
|
|
|
|
1489
|
|
|
|
|
|
|
So C<$hash{'/path'}{'GET'} = { handler, expected params, description etc }> |
1490
|
|
|
|
|
|
|
|
1491
|
|
|
|
|
|
|
If callback is present, run it against route definition |
1492
|
|
|
|
|
|
|
and append to hash its return value, but ONLY if it's true. |
1493
|
|
|
|
|
|
|
|
1494
|
|
|
|
|
|
|
As of 0.20, route definitions are only protected by shallow copy, |
1495
|
|
|
|
|
|
|
so be careful with them. |
1496
|
|
|
|
|
|
|
|
1497
|
|
|
|
|
|
|
This SHOULD NOT be used by application itself. |
1498
|
|
|
|
|
|
|
|
1499
|
|
|
|
|
|
|
=cut |
1500
|
|
|
|
|
|
|
|
1501
|
|
|
|
|
|
|
# TODO 0.30 Route->inspect, Route::Main->inspect |
1502
|
|
|
|
|
|
|
sub get_routes { |
1503
|
11
|
|
|
11
|
1
|
2000
|
my ($self, $code) = @_; |
1504
|
11
|
|
|
|
|
122
|
$self = _one_and_true($self); |
1505
|
|
|
|
|
|
|
|
1506
|
11
|
|
100
|
20
|
|
156
|
$code ||= sub { $_[0] }; |
|
20
|
|
|
|
|
36
|
|
1507
|
11
|
|
|
|
|
126
|
scalar $self->run; # burn caches |
1508
|
|
|
|
|
|
|
|
1509
|
|
|
|
|
|
|
# TODO 0.30 must do deeper copying |
1510
|
11
|
|
|
|
|
55
|
my $all = $self->{route}; |
1511
|
11
|
|
|
|
|
23
|
my %ret; |
1512
|
11
|
|
|
|
|
45
|
foreach my $path ( keys %$all ) { |
1513
|
17
|
|
|
|
|
38
|
my $batch = $all->{$path}; |
1514
|
17
|
|
|
|
|
61
|
foreach my $method ( keys %$batch ) { |
1515
|
48
|
|
|
|
|
80
|
my $route = $batch->{$method}; |
1516
|
48
|
100
|
|
|
|
122
|
$route->post_setup |
1517
|
|
|
|
|
|
|
unless $route->is_locked; |
1518
|
|
|
|
|
|
|
|
1519
|
48
|
|
|
|
|
118
|
my $filtered = $code->( $route->clone, $path, $method ); |
1520
|
48
|
100
|
|
|
|
290
|
$ret{$path}{$method} = $filtered if $filtered; |
1521
|
|
|
|
|
|
|
}; |
1522
|
|
|
|
|
|
|
}; |
1523
|
|
|
|
|
|
|
|
1524
|
11
|
|
|
|
|
69
|
return \%ret; |
1525
|
|
|
|
|
|
|
}; |
1526
|
|
|
|
|
|
|
|
1527
|
|
|
|
|
|
|
=head1 RUN TIME METHODS |
1528
|
|
|
|
|
|
|
|
1529
|
|
|
|
|
|
|
=head2 handle_request |
1530
|
|
|
|
|
|
|
|
1531
|
|
|
|
|
|
|
handle_request( $req ) |
1532
|
|
|
|
|
|
|
|
1533
|
|
|
|
|
|
|
This is the CORE of Not Even A Framework. |
1534
|
|
|
|
|
|
|
Should not be called directly - use C instead. |
1535
|
|
|
|
|
|
|
|
1536
|
|
|
|
|
|
|
C really boils down to |
1537
|
|
|
|
|
|
|
|
1538
|
|
|
|
|
|
|
my ($self, $req) = @_; |
1539
|
|
|
|
|
|
|
|
1540
|
|
|
|
|
|
|
my $req->path =~ /($self->{GIANT_ROUTING_RE})/ |
1541
|
|
|
|
|
|
|
or die 404; |
1542
|
|
|
|
|
|
|
|
1543
|
|
|
|
|
|
|
my $endpoint = $self->{ROUTES}{$1}{ $req->method } |
1544
|
|
|
|
|
|
|
or die 405; |
1545
|
|
|
|
|
|
|
|
1546
|
|
|
|
|
|
|
my $reply_hash = $endpoint->{CODE}->($req); |
1547
|
|
|
|
|
|
|
|
1548
|
|
|
|
|
|
|
my $content = $reply_hash->{-view}->render( $reply_hash ); |
1549
|
|
|
|
|
|
|
|
1550
|
|
|
|
|
|
|
return [ $reply_hash->{-status}, [...], [ $content ] ]; |
1551
|
|
|
|
|
|
|
|
1552
|
|
|
|
|
|
|
The rest 200+ lines of it, spread across this module and L, |
1553
|
|
|
|
|
|
|
are for running callbacks, handling corner cases, and substituting sane defaults. |
1554
|
|
|
|
|
|
|
|
1555
|
|
|
|
|
|
|
=cut |
1556
|
|
|
|
|
|
|
|
1557
|
|
|
|
|
|
|
sub handle_request { |
1558
|
157
|
|
|
157
|
1
|
431
|
my ($self, $req) = @_; |
1559
|
157
|
|
|
|
|
435
|
$self = _one_and_true($self); |
1560
|
|
|
|
|
|
|
|
1561
|
157
|
|
|
|
|
350
|
my $data = eval { |
1562
|
157
|
|
|
|
|
789
|
my $hash = $self->dispatch_logic( $req, '', $req->path ); |
1563
|
114
|
|
|
|
|
705
|
$hash = $req->_set_reply( $hash ); |
1564
|
|
|
|
|
|
|
|
1565
|
113
|
100
|
|
|
|
391
|
if (my $hooks = $req->route->hooks->{pre_content}) { |
1566
|
|
|
|
|
|
|
run_all_nodie( $hooks, sub { |
1567
|
0
|
|
|
0
|
|
0
|
$req->log_error( "NEAF: pre_content hook failed: $@" ) |
1568
|
2
|
|
|
|
|
12
|
}, $req ); |
1569
|
|
|
|
|
|
|
}; |
1570
|
|
|
|
|
|
|
|
1571
|
|
|
|
|
|
|
$hash->{-content} = $self->dispatch_view( $req ) |
1572
|
113
|
100
|
|
|
|
576
|
unless defined $hash->{-content}; |
1573
|
109
|
|
|
|
|
277
|
$hash; |
1574
|
|
|
|
|
|
|
}; |
1575
|
|
|
|
|
|
|
|
1576
|
157
|
100
|
|
|
|
1867
|
if (!$data) { |
1577
|
|
|
|
|
|
|
# TODO 0.30 Error handler should be route-dependent. |
1578
|
48
|
|
|
|
|
335
|
$req->_unset_reply; |
1579
|
48
|
|
|
|
|
256
|
$data = $self->_error_to_reply( $req, $@ ); |
1580
|
|
|
|
|
|
|
}; |
1581
|
|
|
|
|
|
|
|
1582
|
|
|
|
|
|
|
# Encode content, fix headers - do it before hooks |
1583
|
157
|
|
|
|
|
1004
|
$req->_mangle_headers; |
1584
|
157
|
|
|
|
|
899
|
$req->_apply_late_hooks; |
1585
|
157
|
|
|
|
|
770
|
$req->_respond; |
1586
|
|
|
|
|
|
|
}; |
1587
|
|
|
|
|
|
|
|
1588
|
|
|
|
|
|
|
=head2 get_view() |
1589
|
|
|
|
|
|
|
|
1590
|
|
|
|
|
|
|
$route->get_view( "name", $lazy ) |
1591
|
|
|
|
|
|
|
|
1592
|
|
|
|
|
|
|
Fetch view object by name. |
1593
|
|
|
|
|
|
|
|
1594
|
|
|
|
|
|
|
This is used to fetch/instantiate whatever is in C<-view> of the |
1595
|
|
|
|
|
|
|
controller return hash. |
1596
|
|
|
|
|
|
|
|
1597
|
|
|
|
|
|
|
Uses C ( name => name ) if needed, unless $lazy flag is on. |
1598
|
|
|
|
|
|
|
|
1599
|
|
|
|
|
|
|
If L was called, return its argument instead. |
1600
|
|
|
|
|
|
|
|
1601
|
|
|
|
|
|
|
=cut |
1602
|
|
|
|
|
|
|
|
1603
|
|
|
|
|
|
|
sub get_view { |
1604
|
79
|
|
|
79
|
1
|
243
|
my ($self, $view, $lazy) = @_; |
1605
|
79
|
|
|
|
|
197
|
$self = _one_and_true($self); |
1606
|
|
|
|
|
|
|
|
1607
|
|
|
|
|
|
|
# An object/code means controller knows better |
1608
|
79
|
100
|
|
|
|
259
|
return $view |
1609
|
|
|
|
|
|
|
if ref $view; |
1610
|
|
|
|
|
|
|
|
1611
|
|
|
|
|
|
|
# Try loading & caching if not present. |
1612
|
|
|
|
|
|
|
$self->load_view( $view, $view ) |
1613
|
65
|
100
|
100
|
|
|
574
|
unless $lazy || $self->{seen_view}{$view}; |
1614
|
|
|
|
|
|
|
|
1615
|
|
|
|
|
|
|
# Finally, return the thing. |
1616
|
65
|
|
|
|
|
201
|
return $self->{seen_view}{$view}; |
1617
|
|
|
|
|
|
|
}; |
1618
|
|
|
|
|
|
|
|
1619
|
|
|
|
|
|
|
=head2 INTERNAL LOGIC METHODS |
1620
|
|
|
|
|
|
|
|
1621
|
|
|
|
|
|
|
The following methods are part of NEAF's core and should not be called |
1622
|
|
|
|
|
|
|
unless you want something I special. |
1623
|
|
|
|
|
|
|
|
1624
|
|
|
|
|
|
|
The following terminology is used hereafter: |
1625
|
|
|
|
|
|
|
|
1626
|
|
|
|
|
|
|
=over |
1627
|
|
|
|
|
|
|
|
1628
|
|
|
|
|
|
|
=item * prefix - part of URI that matched given NEAF route; |
1629
|
|
|
|
|
|
|
|
1630
|
|
|
|
|
|
|
=item * suffix - anything after the matching part |
1631
|
|
|
|
|
|
|
but before query parameters (the infamous C). |
1632
|
|
|
|
|
|
|
|
1633
|
|
|
|
|
|
|
=back |
1634
|
|
|
|
|
|
|
|
1635
|
|
|
|
|
|
|
When recursive routing is applied, C is left untouched, |
1636
|
|
|
|
|
|
|
C becomes prefix, and C is split into new C + C. |
1637
|
|
|
|
|
|
|
|
1638
|
|
|
|
|
|
|
When a leaf route is found, it matches $suffix to its own regex |
1639
|
|
|
|
|
|
|
and either dies 404 or proceeds with application logic. |
1640
|
|
|
|
|
|
|
|
1641
|
|
|
|
|
|
|
=head2 find_route( $method, $suffix ) |
1642
|
|
|
|
|
|
|
|
1643
|
|
|
|
|
|
|
Find subtree that matches given ($method, $suffix) pair. |
1644
|
|
|
|
|
|
|
|
1645
|
|
|
|
|
|
|
May die 404 or 405 if no suitable route is found. |
1646
|
|
|
|
|
|
|
|
1647
|
|
|
|
|
|
|
Otherwise returns (route, new_stem, new_suffix). |
1648
|
|
|
|
|
|
|
|
1649
|
|
|
|
|
|
|
=cut |
1650
|
|
|
|
|
|
|
|
1651
|
|
|
|
|
|
|
sub find_route { |
1652
|
157
|
|
|
157
|
1
|
467
|
my ($self, $method, $path) = @_; |
1653
|
|
|
|
|
|
|
|
1654
|
|
|
|
|
|
|
# Lookup the rules for the given path |
1655
|
|
|
|
|
|
|
$path =~ $self->{route_re} |
1656
|
157
|
100
|
|
|
|
1470
|
or die "404\n"; |
1657
|
|
|
|
|
|
|
|
1658
|
151
|
|
|
|
|
733
|
my ($prefix, $postfix) = ($1, $2); |
1659
|
151
|
|
|
|
|
392
|
$prefix =~ s#//+#/#g; # CANONIZE |
1660
|
|
|
|
|
|
|
|
1661
|
151
|
100
|
|
|
|
534
|
my $node = $self->{route}{$prefix} |
1662
|
|
|
|
|
|
|
or die "404\n"; |
1663
|
|
|
|
|
|
|
|
1664
|
147
|
|
|
|
|
326
|
my $route = $node->{ $method }; |
1665
|
147
|
100
|
|
|
|
426
|
unless ($route) { |
1666
|
4
|
|
|
|
|
47
|
die MVC::Neaf::Exception->new( |
1667
|
|
|
|
|
|
|
-status => 405, |
1668
|
|
|
|
|
|
|
-headers => [Allow => join ", ", keys %$node] |
1669
|
|
|
|
|
|
|
); |
1670
|
|
|
|
|
|
|
}; |
1671
|
|
|
|
|
|
|
|
1672
|
143
|
100
|
|
|
|
448
|
$postfix = '' unless defined $postfix; |
1673
|
143
|
|
|
|
|
518
|
return ($route, $prefix, $postfix); |
1674
|
|
|
|
|
|
|
}; |
1675
|
|
|
|
|
|
|
|
1676
|
|
|
|
|
|
|
=head2 dispatch_logic |
1677
|
|
|
|
|
|
|
|
1678
|
|
|
|
|
|
|
dispatch_logic( $req, $prefix, $suffix ) |
1679
|
|
|
|
|
|
|
|
1680
|
|
|
|
|
|
|
Find a matching route and apply it to the request. |
1681
|
|
|
|
|
|
|
|
1682
|
|
|
|
|
|
|
This is recursive, may die, and may spoil C<$req>. |
1683
|
|
|
|
|
|
|
|
1684
|
|
|
|
|
|
|
Upon successful termination, a reply hash is returned. |
1685
|
|
|
|
|
|
|
See also L. |
1686
|
|
|
|
|
|
|
|
1687
|
|
|
|
|
|
|
=cut |
1688
|
|
|
|
|
|
|
|
1689
|
|
|
|
|
|
|
sub dispatch_logic { |
1690
|
157
|
|
|
157
|
1
|
604
|
my ($self, $req, $stem, $suffix) = @_; |
1691
|
|
|
|
|
|
|
|
1692
|
|
|
|
|
|
|
$self->post_setup |
1693
|
157
|
50
|
|
|
|
496
|
unless $self->{lock}; |
1694
|
|
|
|
|
|
|
|
1695
|
157
|
|
|
|
|
709
|
my $method = $req->method; |
1696
|
|
|
|
|
|
|
|
1697
|
|
|
|
|
|
|
# We MUST now ensure that $req->route is avail at any time |
1698
|
|
|
|
|
|
|
# so add self to route |
1699
|
|
|
|
|
|
|
# but maybe this whould be in dispatch_logic |
1700
|
157
|
|
66
|
|
|
2514
|
my $stub = $self->{pre_route_stub}{ $method } |
1701
|
|
|
|
|
|
|
||= MVC::Neaf::Route::PreRoute->new( |
1702
|
|
|
|
|
|
|
method => $method, parent => $self ); |
1703
|
157
|
|
|
|
|
860
|
$req->_import_route( $stub ); |
1704
|
|
|
|
|
|
|
|
1705
|
|
|
|
|
|
|
# run pre_route hooks if any |
1706
|
157
|
|
|
|
|
828
|
my $pre_route_hooks = $stub->hooks->{pre_route}; |
1707
|
157
|
100
|
|
|
|
499
|
run_all( $pre_route_hooks, $req ) |
1708
|
|
|
|
|
|
|
if $pre_route_hooks; |
1709
|
|
|
|
|
|
|
|
1710
|
157
|
|
|
|
|
651
|
my ($route, $new_stem, $new_suffix) = $self->find_route( $method, $suffix ); |
1711
|
|
|
|
|
|
|
|
1712
|
143
|
|
|
|
|
652
|
$route->dispatch_logic( $req, $new_stem, $new_suffix ); |
1713
|
|
|
|
|
|
|
}; |
1714
|
|
|
|
|
|
|
|
1715
|
|
|
|
|
|
|
=head2 dispatch_view |
1716
|
|
|
|
|
|
|
|
1717
|
|
|
|
|
|
|
Apply view to a request. |
1718
|
|
|
|
|
|
|
|
1719
|
|
|
|
|
|
|
=cut |
1720
|
|
|
|
|
|
|
|
1721
|
|
|
|
|
|
|
sub dispatch_view { |
1722
|
56
|
|
|
56
|
1
|
173
|
my ($self, $req) = @_; |
1723
|
|
|
|
|
|
|
|
1724
|
56
|
|
|
|
|
243
|
my $data = $req->reply; |
1725
|
56
|
|
|
|
|
175
|
my $route = $req->route; |
1726
|
|
|
|
|
|
|
|
1727
|
56
|
|
|
|
|
105
|
my $content; |
1728
|
|
|
|
|
|
|
|
1729
|
56
|
|
|
|
|
108
|
eval { |
1730
|
|
|
|
|
|
|
run_all( $route->hooks->{pre_render}, $req ) |
1731
|
56
|
100
|
|
|
|
169
|
if $route->hooks->{pre_render}; |
1732
|
|
|
|
|
|
|
|
1733
|
55
|
|
|
|
|
258
|
my $view = $self->get_view( $data->{-view} ); |
1734
|
|
|
|
|
|
|
|
1735
|
55
|
50
|
|
|
|
440
|
($content, my $type) = blessed $view |
1736
|
|
|
|
|
|
|
? $view->render( $data ) : $view->( $data ); |
1737
|
|
|
|
|
|
|
|
1738
|
52
|
|
66
|
|
|
455
|
$data->{-type} ||= $type; |
1739
|
|
|
|
|
|
|
}; |
1740
|
|
|
|
|
|
|
|
1741
|
56
|
100
|
|
|
|
2490
|
if (!defined $content) { |
1742
|
4
|
|
50
|
|
|
90
|
$req->log_error( "NEAF: Request processed, but rendering failed: ". ($@ || "unknown error") ); |
1743
|
4
|
|
|
|
|
209
|
die MVC::Neaf::Exception->new( |
1744
|
|
|
|
|
|
|
-status => 500, |
1745
|
|
|
|
|
|
|
-reason => "Rendering error: $@" |
1746
|
|
|
|
|
|
|
); |
1747
|
|
|
|
|
|
|
}; |
1748
|
|
|
|
|
|
|
|
1749
|
52
|
|
|
|
|
239
|
return $content; |
1750
|
|
|
|
|
|
|
}; |
1751
|
|
|
|
|
|
|
|
1752
|
|
|
|
|
|
|
sub _error_to_reply { |
1753
|
48
|
|
|
48
|
|
157
|
my ($self, $req, $err) = @_; |
1754
|
|
|
|
|
|
|
|
1755
|
|
|
|
|
|
|
# Convert all errors to Neaf expt. |
1756
|
48
|
100
|
|
|
|
317
|
if (!blessed $err) { |
|
|
100
|
|
|
|
|
|
1757
|
38
|
|
|
|
|
357
|
$err = MVC::Neaf::Exception->new( |
1758
|
|
|
|
|
|
|
-status => $err, |
1759
|
|
|
|
|
|
|
-nocaller => 1, |
1760
|
|
|
|
|
|
|
); |
1761
|
|
|
|
|
|
|
} |
1762
|
|
|
|
|
|
|
elsif ( !$err->isa("MVC::Neaf::Exception")) { |
1763
|
1
|
|
|
|
|
8
|
$err = MVC::Neaf::Exception->new( |
1764
|
|
|
|
|
|
|
-status => 500, |
1765
|
|
|
|
|
|
|
-sudden => 1, |
1766
|
|
|
|
|
|
|
-reason => $err, |
1767
|
|
|
|
|
|
|
-nocaller => 1, |
1768
|
|
|
|
|
|
|
); |
1769
|
|
|
|
|
|
|
}; |
1770
|
|
|
|
|
|
|
|
1771
|
|
|
|
|
|
|
# Now $err is guaranteed to be a Neaf error |
1772
|
|
|
|
|
|
|
|
1773
|
|
|
|
|
|
|
# Use on_error callback to fixup error or gather stats |
1774
|
48
|
100
|
100
|
|
|
208
|
if( $err->is_sudden and exists $self->{on_error}) { |
1775
|
1
|
50
|
0
|
|
|
3
|
eval { |
1776
|
1
|
|
|
|
|
9
|
$self->{on_error}->($req, $err, $req->endpoint_origin); |
1777
|
1
|
|
|
|
|
9
|
1; |
1778
|
|
|
|
|
|
|
} |
1779
|
|
|
|
|
|
|
or $req->log_error( "NEAF: on_error callback failed: ".($@ || "unknown reason") ); |
1780
|
|
|
|
|
|
|
}; |
1781
|
|
|
|
|
|
|
|
1782
|
|
|
|
|
|
|
# Try fancy error template |
1783
|
48
|
100
|
|
|
|
287
|
if (my $tpl = $self->_get_error_handler( $err->status, $req )) { |
1784
|
9
|
|
|
|
|
21
|
my $ret = eval { |
1785
|
9
|
|
|
|
|
34
|
my $data = $tpl->( $req, |
1786
|
|
|
|
|
|
|
status => $err->status, |
1787
|
|
|
|
|
|
|
error => $err, |
1788
|
|
|
|
|
|
|
); |
1789
|
8
|
|
66
|
|
|
45
|
$data->{-status} ||= $err->status; |
1790
|
8
|
|
|
|
|
52
|
$data = $req->_set_reply( $data ); |
1791
|
8
|
|
66
|
|
|
58
|
$data->{-content} ||= $self->dispatch_view( $req ); |
1792
|
8
|
|
|
|
|
22
|
$data; |
1793
|
|
|
|
|
|
|
}; |
1794
|
9
|
100
|
|
|
|
71
|
return $ret if $ret; |
1795
|
1
|
|
50
|
|
|
7
|
$req->log_error( "NEAF: error_template for ".$err->status." failed:" |
1796
|
|
|
|
|
|
|
.( $@ || "unknown reason") ); |
1797
|
|
|
|
|
|
|
}; |
1798
|
|
|
|
|
|
|
|
1799
|
|
|
|
|
|
|
# Options exhausted - return plain error message, |
1800
|
|
|
|
|
|
|
# keep track of reason on the inside |
1801
|
40
|
100
|
|
|
|
157
|
$req->log_error( $err->reason ) |
1802
|
|
|
|
|
|
|
if $err->is_sudden; |
1803
|
40
|
|
|
|
|
545
|
$req->_set_reply( $err->make_reply( $req ) ); |
1804
|
|
|
|
|
|
|
}; |
1805
|
|
|
|
|
|
|
|
1806
|
|
|
|
|
|
|
sub _get_error_handler { |
1807
|
48
|
|
|
48
|
|
184
|
my ($self, $status, $req) = @_; |
1808
|
|
|
|
|
|
|
|
1809
|
48
|
|
|
|
|
149
|
my $store = $self->{error_template}{$status}; |
1810
|
48
|
100
|
|
|
|
252
|
return unless $store; |
1811
|
|
|
|
|
|
|
|
1812
|
10
|
|
|
|
|
46
|
return $store->fetch_last( method => $req->method, path => $req->path ); |
1813
|
|
|
|
|
|
|
}; |
1814
|
|
|
|
|
|
|
|
1815
|
|
|
|
|
|
|
=head2 neaf_base_dir() |
1816
|
|
|
|
|
|
|
|
1817
|
|
|
|
|
|
|
Returns the containing directory of the first non-Neaf calling file, |
1818
|
|
|
|
|
|
|
or cwd() with a warning otherwise. |
1819
|
|
|
|
|
|
|
|
1820
|
|
|
|
|
|
|
=cut |
1821
|
|
|
|
|
|
|
|
1822
|
|
|
|
|
|
|
# Should we cache? If so, how to determine we're in a different file now? |
1823
|
|
|
|
|
|
|
sub neaf_base_dir { |
1824
|
38
|
|
|
38
|
1
|
120
|
my $self = shift; |
1825
|
|
|
|
|
|
|
|
1826
|
38
|
|
|
|
|
211
|
my $file = caller_info()->[1]; |
1827
|
38
|
50
|
33
|
|
|
1070
|
if (defined $file and -f $file) { |
1828
|
38
|
|
|
|
|
1136
|
$file = abs_path($file); |
1829
|
|
|
|
|
|
|
# TODO actually don't use magic, add use param instead |
1830
|
38
|
100
|
|
|
|
3325
|
return $file =~ /(.*)\.pm$/ ? $1 : dirname $file; |
1831
|
|
|
|
|
|
|
}; |
1832
|
|
|
|
|
|
|
|
1833
|
0
|
|
|
|
|
0
|
my $cwd = cwd; |
1834
|
0
|
|
|
|
|
0
|
carp "Unable to determine relative path via caller, consider using absolute paths. Defaulting to cwd='$cwd'"; |
1835
|
0
|
|
|
|
|
0
|
return $cwd; |
1836
|
|
|
|
|
|
|
}; |
1837
|
|
|
|
|
|
|
|
1838
|
|
|
|
|
|
|
=head1 DEPRECATED METHODS |
1839
|
|
|
|
|
|
|
|
1840
|
|
|
|
|
|
|
Some methods become obsolete during Neaf development. |
1841
|
|
|
|
|
|
|
Anything that is considered deprecated will continue to be supported |
1842
|
|
|
|
|
|
|
I after official deprecation |
1843
|
|
|
|
|
|
|
and a corresponding warning being added. |
1844
|
|
|
|
|
|
|
|
1845
|
|
|
|
|
|
|
Please keep an eye on C though. |
1846
|
|
|
|
|
|
|
|
1847
|
|
|
|
|
|
|
B |
1848
|
|
|
|
|
|
|
|
1849
|
|
|
|
|
|
|
=over |
1850
|
|
|
|
|
|
|
|
1851
|
|
|
|
|
|
|
=item * route |
1852
|
|
|
|
|
|
|
|
1853
|
|
|
|
|
|
|
Old alias for L. |
1854
|
|
|
|
|
|
|
|
1855
|
|
|
|
|
|
|
=cut |
1856
|
|
|
|
|
|
|
|
1857
|
|
|
|
|
|
|
sub route { |
1858
|
29
|
|
|
29
|
1
|
999
|
my $self = shift; |
1859
|
|
|
|
|
|
|
|
1860
|
|
|
|
|
|
|
# TODO 0.30 deprecate |
1861
|
|
|
|
|
|
|
|
1862
|
29
|
|
|
|
|
173
|
$self->add_route(@_); |
1863
|
|
|
|
|
|
|
}; |
1864
|
|
|
|
|
|
|
|
1865
|
|
|
|
|
|
|
=back |
1866
|
|
|
|
|
|
|
|
1867
|
|
|
|
|
|
|
=head1 LICENSE AND COPYRIGHT |
1868
|
|
|
|
|
|
|
|
1869
|
|
|
|
|
|
|
This module is part of L suite. |
1870
|
|
|
|
|
|
|
|
1871
|
|
|
|
|
|
|
Copyright 2016-2023 Konstantin S. Uvarin C. |
1872
|
|
|
|
|
|
|
|
1873
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it |
1874
|
|
|
|
|
|
|
under the terms of either: the GNU General Public License as published |
1875
|
|
|
|
|
|
|
by the Free Software Foundation; or the Artistic License. |
1876
|
|
|
|
|
|
|
|
1877
|
|
|
|
|
|
|
See L for more information. |
1878
|
|
|
|
|
|
|
|
1879
|
|
|
|
|
|
|
=cut |
1880
|
|
|
|
|
|
|
|
1881
|
|
|
|
|
|
|
1; |