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