line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Mojolicious::Controller; |
2
|
51
|
|
|
51
|
|
64850
|
use Mojo::Base -base; |
|
51
|
|
|
|
|
131
|
|
|
51
|
|
|
|
|
400
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
# No imports, for security reasons! |
5
|
51
|
|
|
51
|
|
358
|
use Carp (); |
|
51
|
|
|
|
|
150
|
|
|
51
|
|
|
|
|
915
|
|
6
|
51
|
|
|
51
|
|
285
|
use Digest::SHA (); |
|
51
|
|
|
|
|
114
|
|
|
51
|
|
|
|
|
970
|
|
7
|
51
|
|
|
51
|
|
1186
|
use Mojo::ByteStream; |
|
51
|
|
|
|
|
138
|
|
|
51
|
|
|
|
|
2482
|
|
8
|
51
|
|
|
51
|
|
1224
|
use Mojo::DynamicMethods -dispatch; |
|
51
|
|
|
|
|
144
|
|
|
51
|
|
|
|
|
495
|
|
9
|
51
|
|
|
51
|
|
1261
|
use Mojo::URL; |
|
51
|
|
|
|
|
175
|
|
|
51
|
|
|
|
|
427
|
|
10
|
51
|
|
|
51
|
|
366
|
use Mojo::Util; |
|
51
|
|
|
|
|
149
|
|
|
51
|
|
|
|
|
2394
|
|
11
|
51
|
|
|
51
|
|
23481
|
use Mojolicious::Routes::Match; |
|
51
|
|
|
|
|
166
|
|
|
51
|
|
|
|
|
430
|
|
12
|
51
|
|
|
51
|
|
397
|
use Scalar::Util (); |
|
51
|
|
|
|
|
162
|
|
|
51
|
|
|
|
|
172269
|
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
has [qw(app tx)] => undef, weak => 1; |
15
|
|
|
|
|
|
|
has match => sub { Mojolicious::Routes::Match->new(root => shift->app->routes) }; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
sub BUILD_DYNAMIC { |
18
|
3237
|
|
|
3237
|
0
|
6930
|
my ($class, $method, $dyn_methods) = @_; |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
return sub { |
21
|
503
|
|
|
503
|
|
3104
|
my $self = shift; |
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
|
159
|
|
|
|
22
|
503
|
|
|
|
|
3297
|
my $dynamic = $dyn_methods->{$self->{app}{renderer}}{$method}; |
23
|
503
|
50
|
|
|
|
2280
|
return $self->$dynamic(@_) if $dynamic; |
24
|
0
|
|
|
|
|
0
|
my $package = ref $self; |
25
|
0
|
|
|
|
|
0
|
Carp::croak qq{Can't locate object method "$method" via package "$package"}; |
26
|
3237
|
|
|
|
|
19613
|
}; |
27
|
|
|
|
|
|
|
} |
28
|
|
|
|
|
|
|
|
29
|
1
|
|
|
1
|
1
|
8
|
sub continue { $_[0]->app->routes->continue($_[0]) } |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub cookie { |
32
|
148
|
|
|
148
|
1
|
694
|
my ($self, $name) = (shift, shift); |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# Response cookie |
35
|
148
|
100
|
|
|
|
349
|
if (@_) { |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Cookie too big |
38
|
114
|
|
100
|
|
|
188
|
my $cookie = {name => $name, value => shift, %{shift // {}}}; |
|
114
|
|
|
|
|
772
|
|
39
|
114
|
50
|
|
|
|
404
|
$self->helpers->log->error(qq{Cookie "$name" is bigger than 4KiB}) if length $cookie->{value} > 4096; |
40
|
|
|
|
|
|
|
|
41
|
114
|
|
|
|
|
300
|
$self->res->cookies($cookie); |
42
|
114
|
|
|
|
|
501
|
return $self; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# Request cookies |
46
|
34
|
100
|
|
|
|
77
|
return undef unless my $cookie = $self->req->cookie($name); |
47
|
21
|
|
|
|
|
63
|
return $cookie->value; |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
237
|
|
|
237
|
1
|
402
|
sub every_cookie { [map { $_->value } @{shift->req->every_cookie(shift)}] } |
|
68
|
|
|
|
|
176
|
|
|
237
|
|
|
|
|
526
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
sub every_param { |
53
|
388
|
|
|
388
|
1
|
922
|
my ($self, $name) = @_; |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# Captured unreserved values |
56
|
388
|
|
100
|
|
|
922
|
my $captures = $self->stash->{'mojo.captures'} //= {}; |
57
|
388
|
100
|
100
|
|
|
1461
|
if (exists $captures->{$name} && !$self->app->routes->is_reserved($name)) { |
58
|
84
|
|
|
|
|
180
|
my $value = $captures->{$name}; |
59
|
84
|
100
|
|
|
|
719
|
return ref $value eq 'ARRAY' ? $value : [$value]; |
60
|
|
|
|
|
|
|
} |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# Uploads or param values |
63
|
304
|
|
|
|
|
716
|
my $req = $self->req; |
64
|
304
|
|
|
|
|
1416
|
my $uploads = $req->every_upload($name); |
65
|
304
|
100
|
|
|
|
1258
|
return @$uploads ? $uploads : $req->every_param($name); |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
sub every_signed_cookie { |
69
|
235
|
|
|
235
|
1
|
506
|
my ($self, $name) = @_; |
70
|
|
|
|
|
|
|
|
71
|
235
|
|
|
|
|
589
|
my $secrets = $self->app->secrets; |
72
|
235
|
|
|
|
|
427
|
my @results; |
73
|
235
|
|
|
|
|
380
|
for my $value (@{$self->every_cookie($name)}) { |
|
235
|
|
|
|
|
665
|
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# Check signature with rotating secrets |
76
|
66
|
100
|
|
|
|
519
|
if ($value =~ s/--([^\-]+)$//) { |
77
|
57
|
|
|
|
|
163
|
my $signature = $1; |
78
|
|
|
|
|
|
|
|
79
|
57
|
|
|
|
|
96
|
my $valid; |
80
|
57
|
|
|
|
|
138
|
for my $secret (@$secrets) { |
81
|
65
|
|
|
|
|
1156
|
my $check = Digest::SHA::hmac_sha256_hex("$name=$value", $secret); |
82
|
65
|
100
|
50
|
|
|
282
|
++$valid and last if Mojo::Util::secure_compare($signature, $check); |
83
|
|
|
|
|
|
|
} |
84
|
57
|
100
|
|
|
|
169
|
if ($valid) { push @results, $value } |
|
45
|
|
|
|
|
184
|
|
85
|
|
|
|
|
|
|
|
86
|
12
|
|
|
|
|
32
|
else { $self->helpers->log->trace(qq{Cookie "$name" has bad signature}) } |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
|
89
|
9
|
|
|
|
|
31
|
else { $self->helpers->log->trace(qq{Cookie "$name" is not signed}) } |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
235
|
|
|
|
|
1234
|
return \@results; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub finish { |
96
|
18
|
|
|
18
|
1
|
136
|
my $self = shift; |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# WebSocket |
99
|
18
|
|
33
|
|
|
127
|
my $tx = $self->tx || Carp::croak 'Transaction already destroyed'; |
100
|
18
|
100
|
50
|
|
|
87
|
$tx->finish(@_) and return $tx->established ? $self : $self->rendered(101) if $tx->is_websocket; |
|
|
100
|
|
|
|
|
|
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Chunked stream |
103
|
9
|
100
|
|
|
|
46
|
return @_ ? $self->write_chunk(@_)->write_chunk('') : $self->write_chunk('') if $tx->res->content->is_chunked; |
|
|
100
|
|
|
|
|
|
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Normal stream |
106
|
3
|
100
|
|
|
|
34
|
return @_ ? $self->write(@_)->write('') : $self->write(''); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
4641
|
|
|
4641
|
1
|
12653
|
sub helpers { $_[0]->app->renderer->get_helper('')->($_[0]) } |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
sub on { |
112
|
93
|
|
|
93
|
1
|
619
|
my ($self, $name, $cb) = @_; |
113
|
93
|
|
33
|
|
|
275
|
my $tx = $self->tx || Carp::croak 'Transaction already destroyed'; |
114
|
93
|
100
|
66
|
|
|
299
|
$self->rendered(101) if $tx->is_websocket && !$tx->established; |
115
|
93
|
|
|
89
|
|
564
|
return $tx->on($name => sub { shift; $self->$cb(@_) }); |
|
89
|
|
|
|
|
178
|
|
|
89
|
|
|
|
|
326
|
|
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
sub param { |
119
|
261
|
|
|
261
|
1
|
1166
|
my ($self, $name) = (shift, shift); |
120
|
261
|
100
|
|
|
|
966
|
return $self->every_param($name)->[-1] unless @_; |
121
|
27
|
100
|
|
|
|
96
|
$self->stash->{'mojo.captures'}{$name} = @_ > 1 ? [@_] : $_[0]; |
122
|
27
|
|
|
|
|
142
|
return $self; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub render { |
126
|
958
|
|
|
958
|
1
|
2631
|
my $self = shift; |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# Template may be first argument |
129
|
958
|
100
|
|
|
|
5460
|
my ($template, $args) = (@_ % 2 ? shift : undef, {@_}); |
130
|
958
|
100
|
|
|
|
2490
|
$args->{template} = $template if $template; |
131
|
958
|
|
|
|
|
2485
|
my $app = $self->app; |
132
|
958
|
|
|
|
|
2780
|
my $plugins = $app->plugins->emit_hook(before_render => $self, $args); |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# Localize "extends" and "layout" to allow argument overrides |
135
|
958
|
|
|
|
|
2042
|
my ($maybe, $ts) = @{$args}{'mojo.maybe', 'mojo.string'}; |
|
958
|
|
|
|
|
2465
|
|
136
|
958
|
|
|
|
|
2177
|
my $stash = $self->stash; |
137
|
958
|
100
|
|
|
|
2870
|
local $stash->{layout} = $stash->{layout} if exists $stash->{layout}; |
138
|
958
|
100
|
|
|
|
2512
|
local $stash->{extends} = $stash->{extends} if exists $stash->{extends}; |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
# Rendering to string |
141
|
958
|
100
|
100
|
|
|
4867
|
local @{$stash}{keys %$args} if $ts || $maybe; |
|
558
|
|
|
|
|
1978
|
|
142
|
958
|
100
|
|
|
|
2379
|
delete @{$stash}{qw(layout extends)} if $ts; |
|
26
|
|
|
|
|
78
|
|
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
# All other arguments just become part of the stash |
145
|
958
|
|
|
|
|
3558
|
@$stash{keys %$args} = values %$args; |
146
|
958
|
|
|
|
|
2695
|
my $renderer = $app->renderer; |
147
|
958
|
|
|
|
|
3356
|
my ($output, $format) = $renderer->render($self); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
# Maybe no 404 |
150
|
938
|
50
|
|
|
|
2940
|
return defined $output ? Mojo::ByteStream->new($output) : undef if $ts; |
|
|
100
|
|
|
|
|
|
151
|
913
|
100
|
|
|
|
7202
|
return $maybe ? undef : Carp::croak("Could not render a response") unless defined $output; |
|
|
100
|
|
|
|
|
|
152
|
|
|
|
|
|
|
|
153
|
732
|
|
|
|
|
2984
|
$plugins->emit_hook(after_render => $self, \$output, $format); |
154
|
732
|
|
|
|
|
3514
|
return $renderer->respond($self, $output, $format, $stash->{status}); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
956
|
|
|
956
|
1
|
2240
|
sub render_later { shift->stash('mojo.rendered' => 1) } |
158
|
|
|
|
|
|
|
|
159
|
537
|
|
|
537
|
1
|
1764
|
sub render_maybe { shift->render(@_, 'mojo.maybe' => 1) } |
160
|
|
|
|
|
|
|
|
161
|
26
|
|
|
26
|
1
|
183
|
sub render_to_string { shift->render(@_, 'mojo.string' => 1) } |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub rendered { |
164
|
1029
|
|
|
1029
|
1
|
2598
|
my ($self, $status) = @_; |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
# Make sure we have a status |
167
|
1029
|
100
|
|
|
|
2626
|
$self->res->code($status) if $status; |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Finish transaction |
170
|
1029
|
|
|
|
|
2403
|
my $stash = $self->stash; |
171
|
1029
|
100
|
66
|
|
|
5978
|
if (!$stash->{'mojo.finished'} && ++$stash->{'mojo.finished'}) { |
172
|
932
|
|
|
|
|
2406
|
my $res = $self->res; |
173
|
932
|
100
|
100
|
|
|
3892
|
$res->code(200) if !$status && !$res->code; |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# Disable auto rendering and stop timer |
176
|
932
|
|
|
|
|
2885
|
my $app = $self->render_later->app; |
177
|
|
|
|
|
|
|
$self->helpers->log->trace(sub { |
178
|
255
|
|
|
255
|
|
592
|
my $timing = $self->helpers->timing; |
179
|
255
|
|
50
|
|
|
901
|
my $elapsed = $timing->elapsed('mojo.timer') // 0; |
180
|
255
|
|
50
|
|
|
4258
|
my $rps = $timing->rps($elapsed) // '??'; |
181
|
255
|
|
|
|
|
875
|
my $code = $res->code; |
182
|
255
|
|
33
|
|
|
734
|
my $msg = $res->message || $res->default_message($code); |
183
|
255
|
|
|
|
|
2414
|
return "$code $msg (${elapsed}s, $rps/s)"; |
184
|
932
|
100
|
|
|
|
3744
|
}) unless $stash->{'mojo.static'}; |
185
|
|
|
|
|
|
|
|
186
|
932
|
|
|
|
|
8337
|
$app->plugins->emit_hook_reverse(after_dispatch => $self); |
187
|
932
|
|
|
|
|
2898
|
$app->sessions->store($self); |
188
|
|
|
|
|
|
|
} |
189
|
1029
|
|
|
|
|
2751
|
$self->tx->resume; |
190
|
1029
|
|
|
|
|
8496
|
return $self; |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
|
193
|
5419
|
|
33
|
5419
|
1
|
16339
|
sub req { (shift->tx || Carp::croak 'Transaction already destroyed')->req } |
194
|
3378
|
|
33
|
3378
|
1
|
8720
|
sub res { (shift->tx || Carp::croak 'Transaction already destroyed')->res } |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub send { |
197
|
93
|
|
|
93
|
1
|
747
|
my ($self, $msg, $cb) = @_; |
198
|
93
|
|
33
|
|
|
510
|
my $tx = $self->tx || Carp::croak 'Transaction already destroyed'; |
199
|
93
|
100
|
|
|
|
334
|
Carp::croak 'No WebSocket connection to send message to' unless $tx->is_websocket; |
200
|
92
|
100
|
|
2
|
|
399
|
$tx->send($msg, $cb ? sub { shift; $self->$cb(@_) } : ()); |
|
2
|
|
|
|
|
11
|
|
|
2
|
|
|
|
|
13
|
|
201
|
92
|
100
|
|
|
|
323
|
return $tx->established ? $self : $self->rendered(101); |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
sub session { |
205
|
229
|
|
|
229
|
1
|
541
|
my $self = shift; |
206
|
|
|
|
|
|
|
|
207
|
229
|
|
|
|
|
486
|
my $stash = $self->stash; |
208
|
229
|
100
|
|
|
|
830
|
$self->app->sessions->load($self) unless exists $stash->{'mojo.active_session'}; |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
# Hash |
211
|
229
|
|
100
|
|
|
922
|
my $session = $stash->{'mojo.session'} //= {}; |
212
|
229
|
100
|
|
|
|
944
|
return $session unless @_; |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# Get |
215
|
39
|
100
|
66
|
|
|
177
|
return $session->{$_[0]} unless @_ > 1 || ref $_[0]; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# Set |
218
|
21
|
50
|
|
|
|
552
|
my $values = ref $_[0] ? $_[0] : {@_}; |
219
|
21
|
|
|
|
|
94
|
@$session{keys %$values} = values %$values; |
220
|
|
|
|
|
|
|
|
221
|
21
|
|
|
|
|
74
|
return $self; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub signed_cookie { |
225
|
301
|
|
|
301
|
1
|
957
|
my ($self, $name, $value, $options) = @_; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# Request cookie |
228
|
301
|
100
|
|
|
|
1006
|
return $self->every_signed_cookie($name)->[-1] unless defined $value; |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# Response cookie |
231
|
68
|
|
|
|
|
348
|
my $sum = Digest::SHA::hmac_sha256_hex("$name=$value", $self->app->secrets->[0]); |
232
|
68
|
|
|
|
|
514
|
return $self->cookie($name, "$value--$sum", $options); |
233
|
|
|
|
|
|
|
} |
234
|
|
|
|
|
|
|
|
235
|
19538
|
|
|
19538
|
1
|
50179
|
sub stash { Mojo::Util::_stash(stash => @_) } |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
sub url_for { |
238
|
576
|
|
100
|
576
|
1
|
2324
|
my ($self, $target) = (shift, shift // ''); |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
# Absolute URL |
241
|
576
|
100
|
66
|
|
|
2217
|
return $target if Scalar::Util::blessed $target && $target->isa('Mojo::URL'); |
242
|
550
|
100
|
|
|
|
2636
|
return Mojo::URL->new($target) if $target =~ m!^(?:[^:/?#]+:|//|#)!; |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# Base |
245
|
504
|
|
|
|
|
1741
|
my $url = Mojo::URL->new; |
246
|
504
|
|
|
|
|
1747
|
my $req = $self->req; |
247
|
504
|
|
|
|
|
1626
|
my $base = $url->base($req->url->base->clone)->base->userinfo(undef); |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
# Relative URL |
250
|
504
|
|
|
|
|
1409
|
my $path = $url->path; |
251
|
504
|
100
|
|
|
|
2099
|
if ($target =~ m!^/!) { |
252
|
340
|
100
|
|
|
|
883
|
if (defined(my $prefix = $self->stash->{path})) { |
253
|
26
|
|
|
|
|
75
|
my $real = $req->url->path->to_route; |
254
|
26
|
|
|
|
|
609
|
$real =~ s!/?\Q$prefix\E$!$target!; |
255
|
26
|
|
|
|
|
113
|
$target = $real; |
256
|
|
|
|
|
|
|
} |
257
|
340
|
|
|
|
|
950
|
$url->parse($target); |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
# Route |
261
|
|
|
|
|
|
|
else { |
262
|
164
|
|
|
|
|
556
|
my $generated = $self->match->path_for($target, @_); |
263
|
164
|
100
|
|
|
|
1027
|
$path->parse($generated->{path}) if $generated->{path}; |
264
|
164
|
50
|
|
|
|
941
|
$base->scheme($base->protocol eq 'https' ? 'wss' : 'ws') if $generated->{websocket}; |
|
|
100
|
|
|
|
|
|
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Make path absolute |
268
|
504
|
|
|
|
|
1240
|
my $base_path = $base->path; |
269
|
504
|
|
|
|
|
881
|
unshift @{$path->parts}, @{$base_path->parts}; |
|
504
|
|
|
|
|
1216
|
|
|
504
|
|
|
|
|
1361
|
|
270
|
504
|
|
|
|
|
1517
|
$base_path->parts([])->trailing_slash(0); |
271
|
|
|
|
|
|
|
|
272
|
504
|
|
|
|
|
2282
|
return $url; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
sub url_for_asset { |
276
|
14
|
|
|
14
|
1
|
41
|
my ($self, $asset) = @_; |
277
|
14
|
|
|
|
|
45
|
return $self->url_for($self->app->static->asset_path($asset)); |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub write { |
281
|
21
|
|
|
21
|
1
|
116
|
my ($self, $chunk, $cb) = @_; |
282
|
21
|
100
|
|
4
|
|
67
|
$self->res->content->write($chunk, $cb ? sub { shift; $self->$cb(@_) } : ()); |
|
4
|
|
|
|
|
14
|
|
|
4
|
|
|
|
|
18
|
|
283
|
21
|
|
|
|
|
79
|
return $self->rendered; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub write_chunk { |
287
|
61
|
|
|
61
|
1
|
499
|
my ($self, $chunk, $cb) = @_; |
288
|
61
|
100
|
|
37
|
|
161
|
$self->res->content->write_chunk($chunk, $cb ? sub { shift; $self->$cb(@_) } : ()); |
|
37
|
|
|
|
|
74
|
|
|
37
|
|
|
|
|
118
|
|
289
|
61
|
|
|
|
|
163
|
return $self->rendered; |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
1; |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=encoding utf8 |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head1 NAME |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
Mojolicious::Controller - Controller base class |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=head1 SYNOPSIS |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Controller |
303
|
|
|
|
|
|
|
package MyApp::Controller::Foo; |
304
|
|
|
|
|
|
|
use Mojo::Base 'Mojolicious::Controller', -signatures; |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
# Action |
307
|
|
|
|
|
|
|
sub bar ($self) { |
308
|
|
|
|
|
|
|
my $name = $self->param('name'); |
309
|
|
|
|
|
|
|
$self->res->headers->cache_control('max-age=1, no-cache'); |
310
|
|
|
|
|
|
|
$self->render(json => {hello => $name}); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head1 DESCRIPTION |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
L is the base class for your L controllers. It is also the default controller |
316
|
|
|
|
|
|
|
class unless you set L. |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
L implements the following attributes. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
=head2 app |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
my $app = $c->app; |
325
|
|
|
|
|
|
|
$c = $c->app(Mojolicious->new); |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
A reference back to the application that dispatched to this controller, usually a L object. Note that this |
328
|
|
|
|
|
|
|
attribute is weakened. |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# Use application logger |
331
|
|
|
|
|
|
|
$c->app->log->debug('Hello Mojo'); |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# Generate path |
334
|
|
|
|
|
|
|
my $path = $c->app->home->child('templates', 'foo', 'bar.html.ep'); |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
=head2 match |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
my $m = $c->match; |
339
|
|
|
|
|
|
|
$c = $c->match(Mojolicious::Routes::Match->new); |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
Router results for the current request, defaults to a L object. |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# Introspect |
344
|
|
|
|
|
|
|
my $name = $c->match->endpoint->name; |
345
|
|
|
|
|
|
|
my $foo = $c->match->endpoint->pattern->defaults->{foo}; |
346
|
|
|
|
|
|
|
my $action = $c->match->stack->[-1]{action}; |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=head2 tx |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
my $tx = $c->tx; |
351
|
|
|
|
|
|
|
$c = $c->tx(Mojo::Transaction::HTTP->new); |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
The transaction that is currently being processed, usually a L or |
354
|
|
|
|
|
|
|
L object. Note that this attribute is weakened. So the object needs to be referenced |
355
|
|
|
|
|
|
|
elsewhere as well when you're performing non-blocking operations and the underlying connection might get closed early. |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
# Check peer information |
358
|
|
|
|
|
|
|
my $address = $c->tx->remote_address; |
359
|
|
|
|
|
|
|
my $port = $c->tx->remote_port; |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
# Increase size limit for WebSocket messages to 16MiB |
362
|
|
|
|
|
|
|
$c->tx->max_websocket_size(16777216) if $c->tx->is_websocket; |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
# Perform non-blocking operation without knowing the connection status |
365
|
|
|
|
|
|
|
my $tx = $c->tx; |
366
|
|
|
|
|
|
|
Mojo::IOLoop->timer(2 => sub { |
367
|
|
|
|
|
|
|
$c->app->log->debug($tx->is_finished ? 'Finished' : 'In progress'); |
368
|
|
|
|
|
|
|
}); |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=head1 METHODS |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
L inherits all methods from L and implements the following new ones. |
373
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=head2 continue |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
$c->continue; |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
Continue dispatch chain from an intermediate destination with L. |
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head2 cookie |
381
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
my $value = $c->cookie('foo'); |
383
|
|
|
|
|
|
|
$c = $c->cookie(foo => 'bar'); |
384
|
|
|
|
|
|
|
$c = $c->cookie(foo => 'bar', {path => '/'}); |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
Access request cookie values and create new response cookies. If there are multiple values sharing the same name, and |
387
|
|
|
|
|
|
|
you want to access more than just the last one, you can use L"every_cookie">. |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
# Create response cookie with domain and expiration date |
390
|
|
|
|
|
|
|
$c->cookie(user => 'sri', {domain => 'example.com', expires => time + 60}); |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
# Create secure response cookie |
393
|
|
|
|
|
|
|
$c->cookie(secret => 'I <3 Mojolicious', {secure => 1, httponly => 1}); |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head2 every_cookie |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
my $values = $c->every_cookie('foo'); |
398
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
Similar to L"cookie">, but returns all request cookie values sharing the same name as an array reference. |
400
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
$ Get first cookie value |
402
|
|
|
|
|
|
|
my $first = $c->every_cookie('foo')->[0]; |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
=head2 every_param |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
my $values = $c->every_param('foo'); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
Similar to L"param">, but returns all values sharing the same name as an array reference. |
409
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
# Get first value |
411
|
|
|
|
|
|
|
my $first = $c->every_param('foo')->[0]; |
412
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
=head2 every_signed_cookie |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
my $values = $c->every_signed_cookie('foo'); |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
Similar to L"signed_cookie">, but returns all signed request cookie values sharing the same name as an array |
418
|
|
|
|
|
|
|
reference. |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# Get first signed cookie value |
421
|
|
|
|
|
|
|
my $first = $c->every_signed_cookie('foo')->[0]; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=head2 finish |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
$c = $c->finish; |
426
|
|
|
|
|
|
|
$c = $c->finish(1000); |
427
|
|
|
|
|
|
|
$c = $c->finish(1003 => 'Cannot accept data!'); |
428
|
|
|
|
|
|
|
$c = $c->finish('Bye!'); |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
Close WebSocket connection or long poll stream gracefully. This method will automatically respond to WebSocket |
431
|
|
|
|
|
|
|
handshake requests with a C<101> response status, to establish the WebSocket connection. |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=head2 helpers |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
my $helpers = $c->helpers; |
436
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
Return a proxy object containing the current controller object and on which helpers provided by L"app"> can be |
438
|
|
|
|
|
|
|
called. This includes all helpers from L and L. |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
# Make sure to use the "title" helper and not the controller method |
441
|
|
|
|
|
|
|
$c->helpers->title('Welcome!'); |
442
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
# Use a nested helper instead of the "reply" controller method |
444
|
|
|
|
|
|
|
$c->helpers->reply->not_found; |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head2 on |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
my $cb = $c->on(finish => sub {...}); |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
Subscribe to events of L"tx">, which is usually a L or L |
451
|
|
|
|
|
|
|
object. This method will automatically respond to WebSocket handshake requests with a C<101> response status, to |
452
|
|
|
|
|
|
|
establish the WebSocket connection. |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
# Do something after the transaction has been finished |
455
|
|
|
|
|
|
|
$c->on(finish => sub ($c) { |
456
|
|
|
|
|
|
|
$c->app->log->debug('All data has been sent'); |
457
|
|
|
|
|
|
|
}); |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
# Receive WebSocket message |
460
|
|
|
|
|
|
|
$c->on(message => sub ($c, $msg) { |
461
|
|
|
|
|
|
|
$c->app->log->debug("Message: $msg"); |
462
|
|
|
|
|
|
|
}); |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# Receive JSON object via WebSocket message |
465
|
|
|
|
|
|
|
$c->on(json => sub ($c, $hash) { |
466
|
|
|
|
|
|
|
$c->app->log->debug("Test: $hash->{test}"); |
467
|
|
|
|
|
|
|
}); |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# Receive WebSocket "Binary" message |
470
|
|
|
|
|
|
|
$c->on(binary => sub ($c, $bytes) { |
471
|
|
|
|
|
|
|
my $len = length $bytes; |
472
|
|
|
|
|
|
|
$c->app->log->debug("Received $len bytes"); |
473
|
|
|
|
|
|
|
}); |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=head2 param |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
my $value = $c->param('foo'); |
478
|
|
|
|
|
|
|
$c = $c->param(foo => 'ba;r'); |
479
|
|
|
|
|
|
|
$c = $c->param(foo => 'ba;r', 'baz'); |
480
|
|
|
|
|
|
|
$c = $c->param(foo => ['ba;r', 'baz']); |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
Access route placeholder values that are not reserved stash values, file uploads as well as C and C |
483
|
|
|
|
|
|
|
parameters extracted from the query string and C or C message |
484
|
|
|
|
|
|
|
body, in that order. If there are multiple values sharing the same name, and you want to access more than just the last |
485
|
|
|
|
|
|
|
one, you can use L"every_param">. Parts of the request body need to be loaded into memory to parse C |
486
|
|
|
|
|
|
|
parameters, so you have to make sure it is not excessively large. There's a 16MiB limit for requests by default. |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
# Get first value |
489
|
|
|
|
|
|
|
my $first = $c->every_param('foo')->[0]; |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
For more control you can also access request information directly. |
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# Only GET parameters |
494
|
|
|
|
|
|
|
my $foo = $c->req->query_params->param('foo'); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
# Only POST parameters |
497
|
|
|
|
|
|
|
my $foo = $c->req->body_params->param('foo'); |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
# Only GET and POST parameters |
500
|
|
|
|
|
|
|
my $foo = $c->req->param('foo'); |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
# Only file uploads |
503
|
|
|
|
|
|
|
my $foo = $c->req->upload('foo'); |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
=head2 render |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
my $bool = $c->render; |
508
|
|
|
|
|
|
|
my $bool = $c->render(foo => 'bar', baz => 23); |
509
|
|
|
|
|
|
|
my $bool = $c->render(template => 'foo/index'); |
510
|
|
|
|
|
|
|
my $bool = $c->render(template => 'index', format => 'html'); |
511
|
|
|
|
|
|
|
my $bool = $c->render(data => $bytes); |
512
|
|
|
|
|
|
|
my $bool = $c->render(text => 'Hello!'); |
513
|
|
|
|
|
|
|
my $bool = $c->render(json => {foo => 'bar'}); |
514
|
|
|
|
|
|
|
my $bool = $c->render(handler => 'something'); |
515
|
|
|
|
|
|
|
my $bool = $c->render('foo/index'); |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
Render content with L and emit hooks L as well as |
518
|
|
|
|
|
|
|
L, or dies if no response could be generated. All additional key/value pairs get merged into |
519
|
|
|
|
|
|
|
the L"stash">. |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
# Render characters |
522
|
|
|
|
|
|
|
$c->render(text => 'I ♥ Mojolicious!'); |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# Render characters (alternative) |
525
|
|
|
|
|
|
|
$c->stash(text => 'I ♥ Mojolicious!')->render; |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Render binary data |
528
|
|
|
|
|
|
|
use Mojo::JSON qw(encode_json); |
529
|
|
|
|
|
|
|
$c->render(data => encode_json({test => 'I ♥ Mojolicious!'})); |
530
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
# Render JSON |
532
|
|
|
|
|
|
|
$c->render(json => {test => 'I ♥ Mojolicious!'}); |
533
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
# Render inline template |
535
|
|
|
|
|
|
|
$c->render(inline => '<%= 1 + 1 %>'); |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# Render template "foo/bar.html.ep" |
538
|
|
|
|
|
|
|
$c->render(template => 'foo/bar', format => 'html', handler => 'ep'); |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
# Render template "test.*.*" with arbitrary values "foo" and "bar" |
541
|
|
|
|
|
|
|
$c->render(template => 'test', foo => 'test', bar => 23); |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# Render template "test.xml.*" |
544
|
|
|
|
|
|
|
$c->render(template => 'test', format => 'xml'); |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# Render template "test.xml.*" (alternative) |
547
|
|
|
|
|
|
|
$c->render('test', format => 'xml'); |
548
|
|
|
|
|
|
|
|
549
|
|
|
|
|
|
|
=head2 render_later |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
$c = $c->render_later; |
552
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
Disable automatic rendering to delay response generation, only necessary if automatic rendering would result in a |
554
|
|
|
|
|
|
|
response. |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# Delayed rendering |
557
|
|
|
|
|
|
|
$c->render_later; |
558
|
|
|
|
|
|
|
Mojo::IOLoop->timer(2 => sub { $c->render(text => 'Delayed by 2 seconds!') }); |
559
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
=head2 render_maybe |
561
|
|
|
|
|
|
|
|
562
|
|
|
|
|
|
|
my $bool = $c->render_maybe; |
563
|
|
|
|
|
|
|
my $bool = $c->render_maybe(foo => 'bar', baz => 23); |
564
|
|
|
|
|
|
|
my $bool = $c->render_maybe('foo/index', format => 'html'); |
565
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
Try to render content, but do not call Lnot_found"> if no response |
567
|
|
|
|
|
|
|
could be generated, all arguments get localized automatically and are only available during this render operation, |
568
|
|
|
|
|
|
|
takes the same arguments as L"render">. |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
# Render template "index_local" only if it exists |
571
|
|
|
|
|
|
|
$c->render_maybe('index_local') or $c->render('index'); |
572
|
|
|
|
|
|
|
|
573
|
|
|
|
|
|
|
=head2 render_to_string |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
my $output = $c->render_to_string('foo/index', format => 'pdf'); |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
Try to render content and return it wrapped in a L object or return C, all arguments get |
578
|
|
|
|
|
|
|
localized automatically and are only available during this render operation, takes the same arguments as L"render">. |
579
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
# Render inline template |
581
|
|
|
|
|
|
|
my $two = $c->render_to_string(inline => '<%= 1 + 1 %>'); |
582
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=head2 rendered |
584
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
$c = $c->rendered; |
586
|
|
|
|
|
|
|
$c = $c->rendered(302); |
587
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
Finalize response and emit hook L, defaults to using a C<200> response code. |
589
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
# Custom response |
591
|
|
|
|
|
|
|
$c->res->headers->content_type('text/plain'); |
592
|
|
|
|
|
|
|
$c->res->body('Hello World!'); |
593
|
|
|
|
|
|
|
$c->rendered(200); |
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=head2 req |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
my $req = $c->req; |
598
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
Get L object from L"tx">. |
600
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
# Longer version |
602
|
|
|
|
|
|
|
my $req = $c->tx->req; |
603
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# Extract request information |
605
|
|
|
|
|
|
|
my $id = $c->req->request_id; |
606
|
|
|
|
|
|
|
my $method = $c->req->method; |
607
|
|
|
|
|
|
|
my $url = $c->req->url->to_abs; |
608
|
|
|
|
|
|
|
my $info = $c->req->url->to_abs->userinfo; |
609
|
|
|
|
|
|
|
my $host = $c->req->url->to_abs->host; |
610
|
|
|
|
|
|
|
my $agent = $c->req->headers->user_agent; |
611
|
|
|
|
|
|
|
my $custom = $c->req->headers->header('Custom-Header'); |
612
|
|
|
|
|
|
|
my $bytes = $c->req->body; |
613
|
|
|
|
|
|
|
my $str = $c->req->text; |
614
|
|
|
|
|
|
|
my $hash = $c->req->params->to_hash; |
615
|
|
|
|
|
|
|
my $all = $c->req->uploads; |
616
|
|
|
|
|
|
|
my $value = $c->req->json; |
617
|
|
|
|
|
|
|
my $foo = $c->req->json('/23/foo'); |
618
|
|
|
|
|
|
|
my $dom = $c->req->dom; |
619
|
|
|
|
|
|
|
my $bar = $c->req->dom('div.bar')->first->text; |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
=head2 res |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
my $res = $c->res; |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
Get L object from L"tx">. |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
# Longer version |
628
|
|
|
|
|
|
|
my $res = $c->tx->res; |
629
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
# Force file download by setting a response header |
631
|
|
|
|
|
|
|
$c->res->headers->content_disposition('attachment; filename=foo.png;'); |
632
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
# Use a custom response header |
634
|
|
|
|
|
|
|
$c->res->headers->header('Custom-Header' => 'whatever'); |
635
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
# Make sure response is cached correctly |
637
|
|
|
|
|
|
|
$c->res->headers->cache_control('public, max-age=300'); |
638
|
|
|
|
|
|
|
$c->res->headers->append(Vary => 'Accept-Encoding'); |
639
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
=head2 send |
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
$c = $c->send({binary => $bytes}); |
643
|
|
|
|
|
|
|
$c = $c->send({text => $bytes}); |
644
|
|
|
|
|
|
|
$c = $c->send({json => {test => [1, 2, 3]}}); |
645
|
|
|
|
|
|
|
$c = $c->send([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]); |
646
|
|
|
|
|
|
|
$c = $c->send($chars); |
647
|
|
|
|
|
|
|
$c = $c->send($chars => sub ($c) {...}); |
648
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
Send message or frame non-blocking via WebSocket, the optional drain callback will be executed once all data has been |
650
|
|
|
|
|
|
|
written. This method will automatically respond to WebSocket handshake requests with a C<101> response status, to |
651
|
|
|
|
|
|
|
establish the WebSocket connection. |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# Send "Text" message |
654
|
|
|
|
|
|
|
$c->send('I ♥ Mojolicious!'); |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
# Send JSON object as "Text" message |
657
|
|
|
|
|
|
|
$c->send({json => {test => 'I ♥ Mojolicious!'}}); |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# Send JSON object as "Binary" message |
660
|
|
|
|
|
|
|
use Mojo::JSON qw(encode_json); |
661
|
|
|
|
|
|
|
$c->send({binary => encode_json({test => 'I ♥ Mojolicious!'})}); |
662
|
|
|
|
|
|
|
|
663
|
|
|
|
|
|
|
# Send "Ping" frame |
664
|
|
|
|
|
|
|
use Mojo::WebSocket qw(WS_PING); |
665
|
|
|
|
|
|
|
$c->send([1, 0, 0, 0, WS_PING, 'Hello World!']); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
# Make sure the first message has been written before continuing |
668
|
|
|
|
|
|
|
$c->send('First message!' => sub ($c) { $c->send('Second message!') }); |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
For mostly idle WebSockets you might also want to increase the inactivity timeout with |
671
|
|
|
|
|
|
|
L, which usually defaults to C<30> seconds. |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
# Increase inactivity timeout for connection to 300 seconds |
674
|
|
|
|
|
|
|
$c->inactivity_timeout(300); |
675
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=head2 session |
677
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
my $session = $c->session; |
679
|
|
|
|
|
|
|
my $foo = $c->session('foo'); |
680
|
|
|
|
|
|
|
$c = $c->session({foo => 'bar'}); |
681
|
|
|
|
|
|
|
$c = $c->session(foo => 'bar'); |
682
|
|
|
|
|
|
|
|
683
|
|
|
|
|
|
|
Persistent data storage for the next few requests, all session data gets serialized with L and stored |
684
|
|
|
|
|
|
|
Base64 encoded in HMAC-SHA256 signed cookies, to prevent tampering. Note that cookies usually have a C<4096> byte |
685
|
|
|
|
|
|
|
(4KiB) limit, depending on browser. |
686
|
|
|
|
|
|
|
|
687
|
|
|
|
|
|
|
# Manipulate session |
688
|
|
|
|
|
|
|
$c->session->{foo} = 'bar'; |
689
|
|
|
|
|
|
|
my $foo = $c->session->{foo}; |
690
|
|
|
|
|
|
|
delete $c->session->{foo}; |
691
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
# Expiration date in seconds from now (persists between requests) |
693
|
|
|
|
|
|
|
$c->session(expiration => 604800); |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
# Expiration date as absolute epoch time (only valid for one request) |
696
|
|
|
|
|
|
|
$c->session(expires => time + 604800); |
697
|
|
|
|
|
|
|
|
698
|
|
|
|
|
|
|
# Delete whole session by setting an expiration date in the past |
699
|
|
|
|
|
|
|
$c->session(expires => 1); |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
=head2 signed_cookie |
702
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
my $value = $c->signed_cookie('foo'); |
704
|
|
|
|
|
|
|
$c = $c->signed_cookie(foo => 'bar'); |
705
|
|
|
|
|
|
|
$c = $c->signed_cookie(foo => 'bar', {path => '/'}); |
706
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
Access signed request cookie values and create new signed response cookies. If there are multiple values sharing the |
708
|
|
|
|
|
|
|
same name, and you want to access more than just the last one, you can use L"every_signed_cookie">. Cookies are |
709
|
|
|
|
|
|
|
cryptographically signed with HMAC-SHA256, to prevent tampering, and the ones failing signature verification will be |
710
|
|
|
|
|
|
|
automatically discarded. |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
=head2 stash |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
my $hash = $c->stash; |
715
|
|
|
|
|
|
|
my $foo = $c->stash('foo'); |
716
|
|
|
|
|
|
|
$c = $c->stash({foo => 'bar', baz => 23}); |
717
|
|
|
|
|
|
|
$c = $c->stash(foo => 'bar', baz => 23); |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
Non-persistent data storage and exchange for the current request, application wide default values can be set with |
720
|
|
|
|
|
|
|
L. Some stash values have a special meaning and are reserved, the full list is currently |
721
|
|
|
|
|
|
|
C, C, C, C, C, C, C, C, C, C, C, |
722
|
|
|
|
|
|
|
C, C, C, C, C and C. Note that all stash values with a C |
723
|
|
|
|
|
|
|
prefix are reserved for internal use. |
724
|
|
|
|
|
|
|
|
725
|
|
|
|
|
|
|
# Remove value |
726
|
|
|
|
|
|
|
my $foo = delete $c->stash->{foo}; |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
# Assign multiple values at once |
729
|
|
|
|
|
|
|
$c->stash(foo => 'test', bar => 23); |
730
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=head2 url_for |
732
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
my $url = $c->url_for; |
734
|
|
|
|
|
|
|
my $url = $c->url_for(name => 'sebastian'); |
735
|
|
|
|
|
|
|
my $url = $c->url_for({name => 'sebastian'}); |
736
|
|
|
|
|
|
|
my $url = $c->url_for('test', name => 'sebastian'); |
737
|
|
|
|
|
|
|
my $url = $c->url_for('test', {name => 'sebastian'}); |
738
|
|
|
|
|
|
|
my $url = $c->url_for('/index.html'); |
739
|
|
|
|
|
|
|
my $url = $c->url_for('//example.com/index.html'); |
740
|
|
|
|
|
|
|
my $url = $c->url_for('http://example.com/index.html'); |
741
|
|
|
|
|
|
|
my $url = $c->url_for('mailto:sri@example.com'); |
742
|
|
|
|
|
|
|
my $url = $c->url_for('#whatever'); |
743
|
|
|
|
|
|
|
|
744
|
|
|
|
|
|
|
Generate a portable L object with base for a path, URL or route. |
745
|
|
|
|
|
|
|
|
746
|
|
|
|
|
|
|
# Rebuild URL for the current route |
747
|
|
|
|
|
|
|
$c->url_for; |
748
|
|
|
|
|
|
|
|
749
|
|
|
|
|
|
|
# Rebuild URL for the current route, but replace the "name" placeholder value |
750
|
|
|
|
|
|
|
$c->url_for(name => 'sebastian'); |
751
|
|
|
|
|
|
|
|
752
|
|
|
|
|
|
|
# Absolute URL for the current route |
753
|
|
|
|
|
|
|
$c->url_for->to_abs; |
754
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
# Build URL for route "test" with two placeholder values |
756
|
|
|
|
|
|
|
$c->url_for('test', name => 'sebastian', foo => 'bar'); |
757
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
# "http://127.0.0.1:3000/index.html" if application was started with Morbo |
759
|
|
|
|
|
|
|
$c->url_for('/index.html')->to_abs; |
760
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
# "https://127.0.0.1:443/index.html" if application was started with Morbo |
762
|
|
|
|
|
|
|
$c->url_for('/index.html')->to_abs->scheme('https')->port(443); |
763
|
|
|
|
|
|
|
|
764
|
|
|
|
|
|
|
# "/index.html?foo=bar" if application is deployed under "/" |
765
|
|
|
|
|
|
|
$c->url_for('/index.html')->query(foo => 'bar'); |
766
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
# "/myapp/index.html?foo=bar" if application is deployed under "/myapp" |
768
|
|
|
|
|
|
|
$c->url_for('/index.html')->query(foo => 'bar'); |
769
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
You can also use the helper L to inherit query parameters from the |
771
|
|
|
|
|
|
|
current request. |
772
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
# "/list?q=mojo&page=2" if current request was for "/list?q=mojo&page=1" |
774
|
|
|
|
|
|
|
$c->url_with->query({page => 2}); |
775
|
|
|
|
|
|
|
|
776
|
|
|
|
|
|
|
|
777
|
|
|
|
|
|
|
=head2 url_for_asset |
778
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
my $url = $c->url_for_asset('/app.js'); |
780
|
|
|
|
|
|
|
|
781
|
|
|
|
|
|
|
Generate a portable L object with base for a static asset. |
782
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
=head2 write |
784
|
|
|
|
|
|
|
|
785
|
|
|
|
|
|
|
$c = $c->write; |
786
|
|
|
|
|
|
|
$c = $c->write(''); |
787
|
|
|
|
|
|
|
$c = $c->write($bytes); |
788
|
|
|
|
|
|
|
$c = $c->write($bytes => sub ($c) {...}); |
789
|
|
|
|
|
|
|
|
790
|
|
|
|
|
|
|
Write dynamic content non-blocking, the optional drain callback will be executed once all data has been written. |
791
|
|
|
|
|
|
|
Calling this method without a chunk of data will finalize the response headers and allow for dynamic content to be |
792
|
|
|
|
|
|
|
written later. |
793
|
|
|
|
|
|
|
|
794
|
|
|
|
|
|
|
# Keep connection alive (with Content-Length header) |
795
|
|
|
|
|
|
|
$c->res->headers->content_length(6); |
796
|
|
|
|
|
|
|
$c->write('Hel' => sub ($c) { $c->write('lo!') }); |
797
|
|
|
|
|
|
|
|
798
|
|
|
|
|
|
|
# Close connection when finished (without Content-Length header) |
799
|
|
|
|
|
|
|
$c->write('Hel' => sub ($c) { |
800
|
|
|
|
|
|
|
$c->write('lo!' => sub ($c) { $c->finish }); |
801
|
|
|
|
|
|
|
}); |
802
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
You can call L"finish"> or write an empty chunk of data at any time to end the stream. |
804
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
HTTP/1.1 200 OK |
806
|
|
|
|
|
|
|
Date: Sat, 13 Sep 2014 16:48:29 GMT |
807
|
|
|
|
|
|
|
Content-Length: 6 |
808
|
|
|
|
|
|
|
Server: Mojolicious (Perl) |
809
|
|
|
|
|
|
|
|
810
|
|
|
|
|
|
|
Hello! |
811
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
HTTP/1.1 200 OK |
813
|
|
|
|
|
|
|
Connection: close |
814
|
|
|
|
|
|
|
Date: Sat, 13 Sep 2014 16:48:29 GMT |
815
|
|
|
|
|
|
|
Server: Mojolicious (Perl) |
816
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
Hello! |
818
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
For Comet (long polling) you might also want to increase the inactivity timeout with |
820
|
|
|
|
|
|
|
L, which usually defaults to C<30> seconds. |
821
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
# Increase inactivity timeout for connection to 300 seconds |
823
|
|
|
|
|
|
|
$c->inactivity_timeout(300); |
824
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
=head2 write_chunk |
826
|
|
|
|
|
|
|
|
827
|
|
|
|
|
|
|
$c = $c->write_chunk; |
828
|
|
|
|
|
|
|
$c = $c->write_chunk(''); |
829
|
|
|
|
|
|
|
$c = $c->write_chunk($bytes); |
830
|
|
|
|
|
|
|
$c = $c->write_chunk($bytes => sub ($c) {...}); |
831
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
Write dynamic content non-blocking with chunked transfer encoding, the optional drain callback will be executed once |
833
|
|
|
|
|
|
|
all data has been written. Calling this method without a chunk of data will finalize the response headers and allow for |
834
|
|
|
|
|
|
|
dynamic content to be written later. |
835
|
|
|
|
|
|
|
|
836
|
|
|
|
|
|
|
# Make sure previous chunk has been written before continuing |
837
|
|
|
|
|
|
|
$c->write_chunk('H' => sub ($c) { |
838
|
|
|
|
|
|
|
$c->write_chunk('ell' => sub ($c) { $c->finish('o!') }); |
839
|
|
|
|
|
|
|
}); |
840
|
|
|
|
|
|
|
|
841
|
|
|
|
|
|
|
You can call L"finish"> or write an empty chunk of data at any time to end the stream. |
842
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
HTTP/1.1 200 OK |
844
|
|
|
|
|
|
|
Date: Sat, 13 Sep 2014 16:48:29 GMT |
845
|
|
|
|
|
|
|
Transfer-Encoding: chunked |
846
|
|
|
|
|
|
|
Server: Mojolicious (Perl) |
847
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
1 |
849
|
|
|
|
|
|
|
H |
850
|
|
|
|
|
|
|
3 |
851
|
|
|
|
|
|
|
ell |
852
|
|
|
|
|
|
|
2 |
853
|
|
|
|
|
|
|
o! |
854
|
|
|
|
|
|
|
0 |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
=head1 HELPERS |
857
|
|
|
|
|
|
|
|
858
|
|
|
|
|
|
|
In addition to the L"ATTRIBUTES"> and L"METHODS"> above you can also call helpers provided by L"app"> on |
859
|
|
|
|
|
|
|
L objects. This includes all helpers from L and |
860
|
|
|
|
|
|
|
L. |
861
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
# Call helpers |
863
|
|
|
|
|
|
|
$c->layout('green'); |
864
|
|
|
|
|
|
|
$c->title('Welcome!'); |
865
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
# Longer version |
867
|
|
|
|
|
|
|
$c->helpers->layout('green'); |
868
|
|
|
|
|
|
|
|
869
|
|
|
|
|
|
|
=head1 SEE ALSO |
870
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
L, L, L. |
872
|
|
|
|
|
|
|
|
873
|
|
|
|
|
|
|
=cut |