File Coverage

blib/lib/Mojolicious/Routes/Route.pm
Criterion Covered Total %
statement 147 150 98.0
branch 64 68 94.1
condition 33 37 89.1
subroutine 39 39 100.0
pod 27 28 96.4
total 310 322 96.2


line stmt bran cond sub pod time code
1             package Mojolicious::Routes::Route;
2 55     55   311 use Mojo::Base -base;
  55         131  
  55         362  
3              
4 55     55   292 use Carp qw(croak);
  55         87  
  55         3409  
5 55     55   270 use Mojo::DynamicMethods -dispatch;
  55         126  
  55         406  
6 55     55   269 use Mojo::Util;
  55         103  
  55         2118  
7 55     55   23721 use Mojolicious::Routes::Pattern;
  55         142  
  55         314  
8              
9             has [qw(inline partial)];
10             has 'children' => sub { [] };
11             has parent => undef, weak => 1;
12             has pattern => sub { Mojolicious::Routes::Pattern->new };
13              
14             # Reserved stash values
15             my %RESERVED = map { $_ => 1 } (
16             qw(action app cb controller data extends format handler inline json layout namespace path status template text),
17             qw(variant)
18             );
19              
20             sub BUILD_DYNAMIC {
21 3     3 0 9 my ($class, $method, $dyn_methods) = @_;
22              
23             return sub {
24 27     27   57 my $self = shift;
        27      
25 27         67 my $dynamic = $dyn_methods->{$self->root}{$method};
26 27 50       77 return $self->$dynamic(@_) if $dynamic;
27 0         0 my $package = ref($self);
28 0         0 croak qq{Can't locate object method "$method" via package "$package"};
29 3         18 };
30             }
31              
32             sub add_child {
33 1010     1010 1 1498 my ($self, $route) = @_;
34 1010         1152 push @{$self->children}, $route->remove->parent($self);
  1010         1926  
35 1010         1805 $route->pattern->types($self->root->types);
36 1010         1844 return $self;
37             }
38              
39 551 100   551 1 2806 sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
40              
41 2     2 1 7 sub delete { shift->_generate_route(DELETE => @_) }
42              
43 14     14 1 822 sub find { shift->_index->{shift()} }
44              
45 398     398 1 1009 sub get { shift->_generate_route(GET => @_) }
46              
47 1413     1413 1 2194 sub has_custom_name { !!shift->{custom} }
48              
49             sub has_websocket {
50 287     287 1 363 my $self = shift;
51 287 100       1464 return $self->{has_websocket} if exists $self->{has_websocket};
52 98         145 return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
  243         369  
  98         198  
53             }
54              
55 17799 100   17799 1 23837 sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
  17123         23280  
56              
57 220     220 1 871 sub is_reserved { !!$RESERVED{$_[1]} }
58              
59 2604     2604 1 6761 sub is_websocket { !!shift->{websocket} }
60              
61             sub methods {
62 3852     3852 1 4884 my $self = shift;
63 3852 100       8161 return $self->{methods} unless @_;
64 997 100       1169 my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
  997         2853  
65 997 100       1959 $self->{methods} = $methods if @$methods;
66 997         1227 return $self;
67             }
68              
69             sub name {
70 1702     1702 1 2162 my $self = shift;
71 1702 100       4769 return $self->{name} unless @_;
72 66         185 @$self{qw(name custom)} = (shift, 1);
73 66         157 return $self;
74             }
75              
76 2     2 1 5 sub options { shift->_generate_route(OPTIONS => @_) }
77              
78             sub parse {
79 1009     1009 1 1174 my $self = shift;
80 1009   100     1792 $self->{name} = $self->pattern->parse(@_)->unparsed // '';
81 1009         3747 $self->{name} =~ s/\W+//g;
82 1009         2256 return $self;
83             }
84              
85 3     3 1 9 sub patch { shift->_generate_route(PATCH => @_) }
86 24     24 1 67 sub post { shift->_generate_route(POST => @_) }
87 10     10 1 23 sub put { shift->_generate_route(PUT => @_) }
88 1     1 1 4 sub query { shift->_generate_route(QUERY => @_) }
89              
90             sub remove {
91 1012     1012 1 1254 my $self = shift;
92 1012 100       1726 return $self unless my $parent = $self->parent;
93 2         4 @{$parent->children} = grep { $_ ne $self } @{$parent->children};
  2         4  
  4         11  
  2         4  
94 2         4 return $self->parent(undef);
95             }
96              
97             sub render {
98 290     290 1 476 my ($self, $values) = @_;
99 290   100     407 my $path = join '', map { $_->pattern->render($values, !@{$_->children} && !$_->partial) } @{$self->_chain};
  667         1103  
  290         622  
100 290   100     1222 return $path || '/';
101             }
102              
103 1059     1059 1 1927 sub root { shift->_chain->[0] }
104              
105             sub requires {
106 3482     3482 1 4235 my $self = shift;
107              
108             # Routes with conditions can't be cached
109 3482 100       8184 return $self->{requires} unless @_;
110 1015 100       1860 my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
111 1015 100       2532 return $self unless @$conditions;
112 21         33 $self->{requires} = $conditions;
113 21         51 $self->root->cache->max_keys(0);
114              
115 21         56 return $self;
116             }
117              
118             sub suggested_method {
119 48     48 1 66 my $self = shift;
120              
121 48         76 my %via;
122 48         65 for my $route (@{$self->_chain}) {
  48         142  
123 98 100 100     117 next unless my @via = @{$route->methods // []};
  98         197  
124 31 100       79 %via = map { $_ => 1 } keys %via ? grep { $via{$_} } @via : @via;
  46         175  
  2         5  
125             }
126              
127 48 100 100     217 return 'POST' if $via{POST} && !$via{GET};
128 40 100 100     223 return $via{GET} ? 'GET' : (sort keys %via)[0] || 'GET';
129             }
130              
131             sub to {
132 1524     1524 1 1648 my $self = shift;
133              
134 1524         2266 my $pattern = $self->pattern;
135 1524 100       2188 return $pattern->defaults unless @_;
136 1522         3083 my ($shortcut, %defaults) = Mojo::Util::_options(@_);
137              
138 1522 100       2447 if ($shortcut) {
139              
140             # Application
141 379 100 100     2300 if (ref $shortcut || $shortcut =~ /^[\w:]+$/) { $defaults{app} = $shortcut }
  5 50       10  
142              
143             # Controller and action
144             elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
145 374 100       1090 $defaults{controller} = $1 if defined $1;
146 374 100       837 $defaults{action} = $2 if defined $2;
147             }
148             }
149              
150 1522         2326 @{$pattern->defaults}{keys %defaults} = values %defaults;
  1522         2184  
151              
152 1522         2904 return $self;
153             }
154              
155             sub to_string {
156 5   100 5 1 10 join '', map { $_->pattern->unparsed // '' } @{shift->_chain};
  12         20  
  5         12  
157             }
158              
159 17     17 1 36 sub under { shift->_generate_route(under => @_) }
160              
161             sub websocket {
162 36     36 1 102 my $route = shift->get(@_);
163 36         63 $route->{websocket} = 1;
164 36         74 return $route;
165             }
166              
167             sub _chain {
168 1500     1500   2715 my @chain = (my $parent = shift);
169 1500         2513 unshift @chain, $parent while $parent = $parent->parent;
170 1500         3813 return \@chain;
171             }
172              
173             sub _generate_route {
174 1008     1008   1985 my ($self, $methods, @args) = @_;
175              
176 1008         1221 my (@conditions, @constraints, %defaults, $name, $pattern);
177 1008         2100 while (defined(my $arg = shift @args)) {
178              
179             # First scalar is the pattern
180 1531 100 100     5062 if (!ref $arg && !$pattern) { $pattern = $arg }
  957 100 100     2040  
    100          
    100          
    100          
    50          
181              
182             # Scalar
183 16         46 elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
184              
185             # Last scalar is the route name
186 50         108 elsif (!ref $arg) { $name = $arg }
187              
188             # Callback
189 312         765 elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
190              
191             # Constraints
192 76         185 elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
193              
194             # Defaults
195 120         435 elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
196             }
197              
198 1008         1946 my $route = $self->_route($pattern, @constraints)->requires(\@conditions)->to(\%defaults);
199 1008 100       2838 $methods eq 'under' ? $route->inline(1) : $route->methods($methods);
200              
201 1008 100       3321 return defined $name ? $route->name($name) : $route;
202             }
203              
204             sub _index {
205 30     30   44 my $self = shift;
206              
207 30         53 my (%auto, %custom);
208 30         42 my @children = (@{$self->children});
  30         80  
209 30         120 while (my $child = shift @children) {
210 1294 100 66     1533 if ($child->has_custom_name) { $custom{$child->name} ||= $child }
  89         140  
211 1205   66     1311 else { $auto{$child->name} ||= $child }
212 1294         1327 push @children, @{$child->children};
  1294         1566  
213             }
214              
215 30         630 return {%auto, %custom};
216             }
217              
218             sub _route {
219 1008     1008   1135 my $self = shift;
220              
221 1008         2487 my $route = $self->add_child(__PACKAGE__->new->parse(@_))->children->[-1];
222 1008         1598 my $new_pattern = $route->pattern;
223 0         0 croak qq{Route pattern "@{[$new_pattern->unparsed]}" contains a reserved stash value}
224 1008 50       1136 if grep { $self->is_reserved($_) } @{$new_pattern->placeholders};
  103         309  
  1008         1654  
225              
226 1008         1704 my $old_pattern = $self->pattern;
227 1008         1651 my $constraints = $old_pattern->constraints;
228 1008 100 66     1743 $new_pattern->constraints->{format} //= $constraints->{format} if exists $constraints->{format};
229 1008         1611 my $defaults = $old_pattern->defaults;
230 1008 100 66     1637 $new_pattern->defaults->{format} //= $defaults->{format} if exists $defaults->{format};
231              
232 1008         2117 return $route;
233             }
234              
235             1;
236              
237             =encoding utf8
238              
239             =head1 NAME
240              
241             Mojolicious::Routes::Route - Route
242              
243             =head1 SYNOPSIS
244              
245             use Mojolicious::Routes::Route;
246              
247             my $r = Mojolicious::Routes::Route->new;
248              
249             =head1 DESCRIPTION
250              
251             L is the route container used by L.
252              
253             =head1 ATTRIBUTES
254              
255             L implements the following attributes.
256              
257             =head2 children
258              
259             my $children = $r->children;
260             $r = $r->children([Mojolicious::Routes::Route->new]);
261              
262             The children of this route, used for nesting routes.
263              
264             =head2 inline
265              
266             my $bool = $r->inline;
267             $r = $r->inline($bool);
268              
269             Allow L semantics for this route.
270              
271             =head2 parent
272              
273             my $parent = $r->parent;
274             $r = $r->parent(Mojolicious::Routes::Route->new);
275              
276             The parent of this route, usually a L object. Note that this attribute is weakened.
277              
278             =head2 partial
279              
280             my $bool = $r->partial;
281             $r = $r->partial($bool);
282              
283             Route has no specific end, remaining characters will be captured in C.
284              
285             =head2 pattern
286              
287             my $pattern = $r->pattern;
288             $r = $r->pattern(Mojolicious::Routes::Pattern->new);
289              
290             Pattern for this route, defaults to a L object.
291              
292             =head1 METHODS
293              
294             L inherits all methods from L and implements the following new ones.
295              
296             =head2 add_child
297              
298             $r = $r->add_child(Mojolicious::Routes::Route->new);
299              
300             Add a child to this route, it will be automatically removed from its current parent if necessary.
301              
302             # Reattach route
303             $r->add_child($r->find('foo'));
304              
305             =head2 any
306              
307             my $route = $r->any;
308             my $route = $r->any('/:foo');
309             my $route = $r->any('/:foo' => sub ($c) {...});
310             my $route = $r->any('/:foo' => sub ($c) {...} => 'name');
311             my $route = $r->any('/:foo' => {foo => 'bar'} => sub ($c) {...});
312             my $route = $r->any('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
313             my $route = $r->any('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
314             my $route = $r->any(['GET', 'POST'] => '/:foo' => sub ($c) {...});
315             my $route = $r->any(['GET', 'POST'] => '/:foo' => [foo => qr/\w+/]);
316              
317             Generate L object matching any of the listed HTTP request methods or all.
318              
319             # Route with pattern and destination
320             $r->any('/user')->to('user#whatever');
321              
322             All arguments are optional, but some have to appear in a certain order, like the two supported array reference values,
323             which contain the HTTP methods to match and restrictive placeholders.
324              
325             # Route with HTTP methods, pattern, restrictive placeholders and destination
326             $r->any(['DELETE', 'PUT'] => '/:foo' => [foo => qr/\w+/])->to('foo#bar');
327              
328             There are also two supported string values, containing the route pattern and the route name, defaulting to the pattern
329             C and a name based on the pattern.
330              
331             # Route with pattern, name and destination
332             $r->any('/:foo' => 'foo_route')->to('foo#bar');
333              
334             An arbitrary number of key/value pairs in between the route pattern and name can be used to specify route conditions.
335              
336             # Route with pattern, condition and destination
337             $r->any('/' => (agent => qr/Firefox/))->to('foo#bar');
338              
339             A hash reference is used to specify optional placeholders and default values for the stash.
340              
341             # Route with pattern, optional placeholder and destination
342             $r->any('/:foo' => {foo => 'bar'})->to('foo#bar');
343              
344             And a code reference can be used to specify a C value to be merged into the default values for the stash.
345              
346             # Route with pattern and a closure as destination
347             $r->any('/:foo' => sub ($c) {
348             $c->render(text => 'Hello World!');
349             });
350              
351             See L and L for more information.
352              
353             =head2 delete
354              
355             my $route = $r->delete;
356             my $route = $r->delete('/:foo');
357             my $route = $r->delete('/:foo' => sub ($c) {...});
358             my $route = $r->delete('/:foo' => sub ($c) {...} => 'name');
359             my $route = $r->delete('/:foo' => {foo => 'bar'} => sub ($c) {...});
360             my $route = $r->delete('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
361             my $route = $r->delete('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
362              
363             Generate L object matching only C requests, takes the same arguments as L
364             (except for the HTTP methods to match, which are implied). See L and
365             L for more information.
366              
367             # Route with destination
368             $r->delete('/user')->to('user#remove');
369              
370             =head2 find
371              
372             my $route = $r->find('foo');
373              
374             Find child route by name, custom names have precedence over automatically generated ones.
375              
376             # Change default parameters of a named route
377             $r->find('show_user')->to(foo => 'bar');
378              
379             =head2 get
380              
381             my $route = $r->get;
382             my $route = $r->get('/:foo');
383             my $route = $r->get('/:foo' => sub ($c) {...});
384             my $route = $r->get('/:foo' => sub ($c) {...} => 'name');
385             my $route = $r->get('/:foo' => {foo => 'bar'} => sub ($c) {...});
386             my $route = $r->get('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
387             my $route = $r->get('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
388              
389             Generate L object matching only C requests, takes the same arguments as L
390             (except for the HTTP methods to match, which are implied). See L and
391             L for more information.
392              
393             # Route with destination
394             $r->get('/user')->to('user#show');
395              
396             =head2 has_custom_name
397              
398             my $bool = $r->has_custom_name;
399              
400             Check if this route has a custom name.
401              
402             =head2 has_websocket
403              
404             my $bool = $r->has_websocket;
405              
406             Check if this route has a WebSocket ancestor and cache the result for future checks.
407              
408             =head2 is_endpoint
409              
410             my $bool = $r->is_endpoint;
411              
412             Check if this route qualifies as an endpoint.
413              
414             =head2 is_reserved
415              
416             my $bool = $r->is_reserved('controller');
417              
418             Check if string is a reserved stash value.
419              
420             =head2 is_websocket
421              
422             my $bool = $r->is_websocket;
423              
424             Check if this route is a WebSocket.
425              
426             =head2 methods
427              
428             my $methods = $r->methods;
429             $r = $r->methods('GET');
430             $r = $r->methods('GET', 'POST');
431             $r = $r->methods(['GET', 'POST']);
432              
433             Restrict HTTP methods this route is allowed to handle, defaults to no restrictions.
434              
435             # Route with two methods and destination
436             $r->any('/foo')->methods('GET', 'POST')->to('foo#bar');
437              
438             =head2 name
439              
440             my $name = $r->name;
441             $r = $r->name('foo');
442              
443             The name of this route, defaults to an automatically generated name based on the route pattern. Note that the name
444             C is reserved for referring to the current route.
445              
446             # Route with destination and custom name
447             $r->get('/user')->to('user#show')->name('show_user');
448              
449             =head2 options
450              
451             my $route = $r->options;
452             my $route = $r->options('/:foo');
453             my $route = $r->options('/:foo' => sub ($c) {...});
454             my $route = $r->options('/:foo' => sub ($c) {...} => 'name');
455             my $route = $r->options('/:foo' => {foo => 'bar'} => sub ($c) {...});
456             my $route = $r->options('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
457             my $route = $r->options('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
458              
459             Generate L object matching only C requests, takes the same arguments as L
460             (except for the HTTP methods to match, which are implied). See L and
461             L for more information.
462              
463             # Route with destination
464             $r->options('/user')->to('user#overview');
465              
466             =head2 parse
467              
468             $r = $r->parse('/user/:id');
469             $r = $r->parse('/user/:id', id => qr/\d+/);
470             $r = $r->parse(format => ['json', 'yaml']);
471              
472             Parse pattern.
473              
474             =head2 patch
475              
476             my $route = $r->patch;
477             my $route = $r->patch('/:foo');
478             my $route = $r->patch('/:foo' => sub ($c) {...});
479             my $route = $r->patch('/:foo' => sub ($c) {...} => 'name');
480             my $route = $r->patch('/:foo' => {foo => 'bar'} => sub ($c) {...});
481             my $route = $r->patch('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
482             my $route = $r->patch('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
483              
484             Generate L object matching only C requests, takes the same arguments as L
485             (except for the HTTP methods to match, which are implied). See L and
486             L for more information.
487              
488             # Route with destination
489             $r->patch('/user')->to('user#update');
490              
491             =head2 post
492              
493             my $route = $r->post;
494             my $route = $r->post('/:foo');
495             my $route = $r->post('/:foo' => sub ($c) {...});
496             my $route = $r->post('/:foo' => sub ($c) {...} => 'name');
497             my $route = $r->post('/:foo' => {foo => 'bar'} => sub ($c) {...});
498             my $route = $r->post('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
499             my $route = $r->post('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
500              
501             Generate L object matching only C requests, takes the same arguments as L
502             (except for the HTTP methods to match, which are implied). See L and
503             L for more information.
504              
505             # Route with destination
506             $r->post('/user')->to('user#create');
507              
508             =head2 put
509              
510             my $route = $r->put;
511             my $route = $r->put('/:foo');
512             my $route = $r->put('/:foo' => sub ($c) {...});
513             my $route = $r->put('/:foo' => sub ($c) {...} => 'name');
514             my $route = $r->put('/:foo' => {foo => 'bar'} => sub ($c) {...});
515             my $route = $r->put('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
516             my $route = $r->put('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
517              
518             Generate L object matching only C requests, takes the same arguments as L
519             (except for the HTTP methods to match, which are implied). See L and
520             L for more information.
521              
522             # Route with destination
523             $r->put('/user')->to('user#replace');
524              
525             =head2 query
526              
527             my $route = $r->query;
528             my $route = $r->query('/:foo');
529             my $route = $r->query('/:foo' => sub ($c) {...});
530             my $route = $r->query('/:foo' => sub ($c) {...} => 'name');
531             my $route = $r->query('/:foo' => {foo => 'bar'} => sub ($c) {...});
532             my $route = $r->query('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
533             my $route = $r->query('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
534              
535             Generate L object matching only C requests, takes the same arguments as L
536             (except for the HTTP methods to match, which are implied). See L and
537             L for more information.
538              
539             # Route with destination
540             $r->query('/user')->to('user#search');
541              
542             =head2 remove
543              
544             $r = $r->remove;
545              
546             Remove route from parent.
547              
548             # Remove route completely
549             $r->find('foo')->remove;
550              
551             # Reattach route to new parent
552             $r->any('/foo')->add_child($r->find('bar')->remove);
553              
554             =head2 render
555              
556             my $path = $r->render({foo => 'bar'});
557              
558             Render route with parameters into a path.
559              
560             =head2 root
561              
562             my $root = $r->root;
563              
564             The L object this route is a descendant of.
565              
566             =head2 requires
567              
568             my $requires = $r->requires;
569             $r = $r->requires(foo => 1);
570             $r = $r->requires(foo => 1, bar => {baz => 'yada'});
571             $r = $r->requires([foo => 1, bar => {baz => 'yada'}]);
572              
573             Activate conditions for this route. Note that this automatically disables the routing cache, since conditions are too
574             complex for caching.
575              
576             # Route with condition and destination
577             $r->get('/foo')->requires(host => qr/mojolicious\.org/)->to('foo#bar');
578              
579             =head2 suggested_method
580              
581             my $method = $r->suggested_method;
582              
583             Suggested HTTP method for reaching this route, C and C are preferred.
584              
585             =head2 to
586              
587             my $defaults = $r->to;
588             $r = $r->to(action => 'foo');
589             $r = $r->to({action => 'foo'});
590             $r = $r->to('controller#action');
591             $r = $r->to('controller#action', foo => 'bar');
592             $r = $r->to('controller#action', {foo => 'bar'});
593             $r = $r->to(Mojolicious->new);
594             $r = $r->to(Mojolicious->new, foo => 'bar');
595             $r = $r->to(Mojolicious->new, {foo => 'bar'});
596             $r = $r->to('MyApp');
597             $r = $r->to('MyApp', foo => 'bar');
598             $r = $r->to('MyApp', {foo => 'bar'});
599              
600             Set default parameters for this route.
601              
602             =head2 to_string
603              
604             my $str = $r->to_string;
605              
606             Stringify the whole route.
607              
608             =head2 under
609              
610             my $route = $r->under(sub ($c) {...});
611             my $route = $r->under('/:foo' => sub ($c) {...});
612             my $route = $r->under('/:foo' => {foo => 'bar'});
613             my $route = $r->under('/:foo' => [foo => qr/\w+/]);
614             my $route = $r->under('/:foo' => (agent => qr/Firefox/));
615             my $route = $r->under([format => ['json', 'yaml']]);
616              
617             Generate L object for a nested route with its own intermediate destination, takes the same
618             arguments as L (except for the HTTP methods to match, which are not available). See
619             L and L for more information.
620              
621             # Longer version
622             $r->any('/:foo' => sub ($c) {...})->inline(1);
623              
624             # Intermediate destination and prefix shared between two routes
625             my $auth = $r->under('/user')->to('user#auth');
626             $auth->get('/show')->to('#show');
627             $auth->post('/create')->to('#create');
628              
629             =head2 websocket
630              
631             my $route = $r->websocket;
632             my $route = $r->websocket('/:foo');
633             my $route = $r->websocket('/:foo' => sub ($c) {...});
634             my $route = $r->websocket('/:foo' => sub ($c) {...} => 'name');
635             my $route = $r->websocket('/:foo' => {foo => 'bar'} => sub ($c) {...});
636             my $route = $r->websocket('/:foo' => [foo => qr/\w+/] => sub ($c) {...});
637             my $route = $r->websocket('/:foo' => (agent => qr/Firefox/) => sub ($c) {...});
638              
639             Generate L object matching only WebSocket handshakes, takes the same arguments as L
640             (except for the HTTP methods to match, which are implied). See L and
641             L for more information.
642              
643             # Route with destination
644             $r->websocket('/echo')->to('example#echo');
645              
646             =head1 SHORTCUTS
647              
648             In addition to the L and L above you can also call shortcuts provided by L on
649             L objects.
650              
651             # Add a "firefox" shortcut
652             $r->root->add_shortcut(firefox => sub ($r, $path) {
653             $r->get($path, agent => qr/Firefox/);
654             });
655              
656             # Use "firefox" shortcut to generate routes
657             $r->firefox('/welcome')->to('firefox#welcome');
658             $r->firefox('/bye')->to('firefox#bye');
659              
660             =head1 SEE ALSO
661              
662             L, L, L.
663              
664             =cut