@@ -1,4 +1,45 @@
+5.75 2015-01-26
+ - Added healthy method to Mojo::Server::Prefork.
+ - Improved all built-in web servers to die if group or user assignment
+ failed.
+ - Improved Hypnotoad to wait for new workers to be ready before stopping the
+ old ones during hot deployment.
+ - Improved commands and log messages to use less punctuation.
+ - Fixed bug in Mojo::IOLoop where the callback passed to next_tick would
+ receive the wrong invocant.
+ - Fixed race condition and memory leak in Mojo::Server::Prefork.
+
+5.74 2015-01-25
+ - Improved parser errors to be more consistent with connection errors in
+ Mojo::Message::Request and Mojo::Message::Response.
+ - Fixed "0" value bug in Mojo::Parameters.
+ - Fixed bug where placeholder default values would not always have
+ precedence.
+ - Fixed proxy detection in get command.
+
+5.73 2015-01-24
+ - Deprecated Mojolicious::Routes::Route::bridge in favor of
+ Mojolicious::Routes::Route::under.
+ - Deprecated Mojolicious::Controller::render_exception in favor of
+ reply->exception helper.
+ - Deprecated Mojolicious::Controller::render_not_found in favor of
+ reply->not_found helper.
+ - Removed deprecated object-oriented Mojo::JSON API.
+ - Removed deprecated stringification support from Mojo::Collection.
+ - Removed deprecated support for data arguments from Mojo::JSON::Pointer.
+ - Removed deprecated AUTOLOAD and pluck methods from Mojo::Collection.
+ - Removed deprecated AUTOLOAD and val methods from Mojo::DOM.
+ - Moved tutorial from Mojolicious::Lite to Mojolicious::Guides::Tutorial.
+ - Added term_escape method to Mojo::ByteStream.
+ - Added term_escape function to Mojo::Util.
+ - Improved get command to use the user agent of the application.
+ - Improved diagnostics information for MOJO_DAEMON_DEBUG,
+ MOJO_USERAGENT_DEBUG and MOJO_WEBSOCKET_DEBUG environment variables.
+ - Fixed tag helpers to generate correct HTML5. (batman, sri)
+ - Fixed JSON Pointer escaping bug.
+ - Fixed portability bug in monkey_patch tests.
+
5.72 2015-01-11
- Added EXPERIMENTAL support for case-insensitive attribute selectors like
[foo="bar" i] to Mojo::DOM::CSS.
@@ -96,6 +96,7 @@ lib/Mojolicious/Guides/FAQ.pod
lib/Mojolicious/Guides/Growing.pod
lib/Mojolicious/Guides/Rendering.pod
lib/Mojolicious/Guides/Routing.pod
+lib/Mojolicious/Guides/Tutorial.pod
lib/Mojolicious/Lite.pm
lib/Mojolicious/Plugin.pm
lib/Mojolicious/Plugin/Charset.pm
@@ -54,5 +54,5 @@
},
"x_IRC" : "irc://irc.perl.org/#mojo"
},
- "version" : "5.72"
+ "version" : "5.75"
}
@@ -29,4 +29,4 @@ resources:
homepage: http://mojolicio.us
license: http://www.opensource.org/licenses/artistic-license-2.0
repository: https://github.com/kraih/mojo.git
-version: '5.72'
+version: '5.75'
@@ -54,7 +54,7 @@ app->start;
code into a file and start it with `morbo`.
$ morbo hello.pl
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
$ curl http://127.0.0.1:3000/
I ♥ Mojolicious!
@@ -37,7 +37,7 @@ Mojo::IOLoop->server(
}
# Start forwarding data in both directions
- say "Forwarding to $address:$port.";
+ say "Forwarding to $address:$port";
Mojo::IOLoop->stream($client)
->write("HTTP/1.1 200 OK\x0d\x0a"
. "Connection: keep-alive\x0d\x0a\x0d\x0a");
@@ -33,7 +33,7 @@ Mojo::IOLoop->server(
print <<'EOF';
Starting server on port 8080.
-Try something like "wrk -c 100 -d 10s http://127.0.0.1:8080/" for testing.
+For testing use something like "wrk -c 100 -d 10s http://127.0.0.1:8080/".
On a MacBook Air this results in about 18k req/s.
EOF
@@ -12,8 +12,8 @@ our @EXPORT_OK = ('b');
my @UTILS = (
qw(b64_decode b64_encode camelize decamelize hmac_sha1_sum html_unescape),
qw(md5_bytes md5_sum punycode_decode punycode_encode quote sha1_bytes),
- qw(sha1_sum slurp spurt squish trim unindent unquote url_escape),
- qw(url_unescape xml_escape xor_encode)
+ qw(sha1_sum slurp spurt squish term_escape trim unindent unquote),
+ qw(url_escape url_unescape xml_escape xor_encode)
);
for my $name (@UTILS) {
my $sub = Mojo::Util->can($name);
@@ -124,7 +124,8 @@ Base64 decode bytestream with L<Mojo::Util/"b64_decode">.
Base64 encode bytestream with L<Mojo::Util/"b64_encode">.
- b('foo bar baz')->b64_encode('')->say;
+ # "Zm9vIGJhciBiYXo="
+ b('foo bar baz')->b64_encode('');
=head2 camelize
@@ -149,18 +150,20 @@ Decamelize bytestream with L<Mojo::Util/"decamelize">.
$stream = $stream->decode;
$stream = $stream->decode('iso-8859-1');
-Decode bytestream with L<Mojo::Util/"decode">, defaults to C<UTF-8>.
+Decode bytestream with L<Mojo::Util/"decode">, defaults to using C<UTF-8>.
- $stream->decode('UTF-16LE')->unquote->trim->say;
+ # "♥"
+ b('%E2%99%A5')->url_unescape->decode;
=head2 encode
$stream = $stream->encode;
$stream = $stream->encode('iso-8859-1');
-Encode bytestream with L<Mojo::Util/"encode">, defaults to C<UTF-8>.
+Encode bytestream with L<Mojo::Util/"encode">, defaults to using C<UTF-8>.
- $stream->trim->quote->encode->say;
+ # "%E2%99%A5"
+ b('♥')->encode->url_escape;
=head2 hmac_sha1_sum
@@ -168,7 +171,8 @@ Encode bytestream with L<Mojo::Util/"encode">, defaults to C<UTF-8>.
Generate HMAC-SHA1 checksum for bytestream with L<Mojo::Util/"hmac_sha1_sum">.
- b('foo bar baz')->hmac_sha1_sum('secr3t')->quote->say;
+ # "7fbdc89263974a89210ea71f171c77d3f8c21471"
+ b('foo bar baz')->hmac_sha1_sum('secr3t');
=head2 html_unescape
@@ -176,7 +180,8 @@ Generate HMAC-SHA1 checksum for bytestream with L<Mojo::Util/"hmac_sha1_sum">.
Unescape all HTML entities in bytestream with L<Mojo::Util/"html_unescape">.
- b('<html>')->html_unescape->url_escape->say;
+ # "%3Chtml%3E"
+ b('<html>')->html_unescape->url_escape;
=head2 md5_bytes
@@ -219,7 +224,7 @@ Quote bytestream with L<Mojo::Util/"quote">.
$stream = $stream->say;
$stream = $stream->say(*STDERR);
-Print bytestream to handle and append a newline, defaults to C<STDOUT>.
+Print bytestream to handle and append a newline, defaults to using C<STDOUT>.
=head2 secure_compare
@@ -227,8 +232,6 @@ Print bytestream to handle and append a newline, defaults to C<STDOUT>.
Compare bytestream with L<Mojo::Util/"secure_compare">.
- say 'Match!' if b('foo')->secure_compare('foo');
-
=head2 sha1_bytes
$stream = $stream->sha1_bytes;
@@ -253,6 +256,7 @@ Size of bytestream.
Read all data at once from file into bytestream with L<Mojo::Util/"slurp">.
+ # Read file and print lines in random order
b('/home/sri/myapp.pl')->slurp->split("\n")->shuffle->join("\n")->say;
=head2 spurt
@@ -261,6 +265,7 @@ Read all data at once from file into bytestream with L<Mojo::Util/"slurp">.
Write all data from bytestream at once to file with L<Mojo::Util/"spurt">.
+ # Remove unnecessary whitespace from file
b('/home/sri/foo.txt')->slurp->squish->spurt('/home/sri/bar.txt');
=head2 split
@@ -270,7 +275,8 @@ Write all data from bytestream at once to file with L<Mojo::Util/"spurt">.
Turn bytestream into L<Mojo::Collection> object containing L<Mojo::ByteStream>
objects.
- b('a,b,c')->split(',')->quote->join(',')->say;
+ # "One,Two,Three"
+ b("one,two,three")->split(',')->map('camelize')->join(',');
=head2 squish
@@ -286,6 +292,16 @@ L<Mojo::Util/"squish">.
Alias for L<Mojo::Base/"tap">.
+=head2 term_escape
+
+ $stream = $stream->term_escape;
+
+Escape POSIX control characters in bytestream with
+L<Mojo::Util/"term_escape">.
+
+ # Print binary checksum to terminal
+ b('foo')->sha1_bytes->term_escape->say;
+
=head2 to_string
my $str = $stream->to_string;
@@ -319,7 +335,8 @@ Unquote bytestream with L<Mojo::Util/"unquote">.
Percent encode all unsafe characters in bytestream with
L<Mojo::Util/"url_escape">.
- b('foo bar baz')->url_escape->say;
+ # "%E2%98%83"
+ b('☃')->encode->url_escape;
=head2 url_unescape
@@ -328,7 +345,8 @@ L<Mojo::Util/"url_escape">.
Decode percent encoded characters in bytestream with
L<Mojo::Util/"url_unescape">.
- b('%3Chtml%3E')->url_unescape->xml_escape->say;
+ # "<html>"
+ b('%3Chtml%3E')->url_unescape->xml_escape;
=head2 xml_escape
@@ -343,6 +361,9 @@ bytestream with L<Mojo::Util/"xml_escape">.
XOR encode bytestream with L<Mojo::Util/"xor_encode">.
+ # "%04%0E%15B%03%1B%10"
+ b('foo bar')->xor_encode('baz')->url_escape;
+
=head1 OPERATORS
L<Mojo::ByteStream> overloads the following operators.
@@ -5,33 +5,10 @@ use Carp 'croak';
use Exporter 'import';
use List::Util;
use Mojo::ByteStream;
-use Mojo::Util 'deprecated';
use Scalar::Util 'blessed';
-# DEPRECATED in Tiger Face!
-use overload '""' => sub {
- deprecated 'Stringification support in Mojo::Collection is DEPRECATED'
- . ' in favor of Mojo::Collection::join';
- shift->join("\n");
-};
-use overload bool => sub {1}, fallback => 1;
-
our @EXPORT_OK = ('c');
-# DEPRECATED in Tiger Face!
-sub AUTOLOAD {
- my $self = shift;
- my ($package, $method) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
- deprecated "Mojo::Collection::AUTOLOAD ($method) is DEPRECATED"
- . ' in favor of Mojo::Collection::map';
- croak "Undefined subroutine &${package}::$method called"
- unless blessed $self && $self->isa(__PACKAGE__);
- return $self->map($method, @_);
-}
-
-# DEPRECATED in Tiger Face!
-sub DESTROY { }
-
sub c { __PACKAGE__->new(@_) }
sub compact {
@@ -77,14 +54,6 @@ sub new {
return bless [@_], ref $class || $class;
}
-# DEPRECATED in Tiger Face!
-sub pluck {
- deprecated
- 'Mojo::Collection::pluck is DEPRECATED in favor of Mojo::Collection::map';
- my ($self, $key) = (shift, shift);
- return $self->new(map { ref eq 'HASH' ? $_->{$key} : $_->$key(@_) } @$self);
-}
-
sub reduce {
my $self = shift;
@_ = (@_, @$self);
@@ -270,7 +270,7 @@ sub _unescape {
$value =~ s/\\\n//g;
# Unescape Unicode characters
- $value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack('U', hex $1)/ge;
+ $value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack 'U', hex $1/ge;
# Remove backslash
$value =~ s/\\//g;
@@ -143,7 +143,7 @@ sub parse {
# Raw text elements
next if $xml || !$RAW{$start} && !$RCDATA{$start};
- next unless $html =~ m!\G(.*?)<\s*/\s*$start\s*>!gcsi;
+ next unless $html =~ m!\G(.*?)<\s*/\s*\Q$start\E\s*>!gcsi;
_node($current, 'raw', $RCDATA{$start} ? html_unescape $1 : $1);
_end($start, 0, \$current);
}
@@ -217,43 +217,30 @@ sub _render {
# Processing instruction
return '<?' . $tree->[1] . '?>' if $type eq 'pi';
- # Start tag
- my $result = '';
- if ($type eq 'tag') {
-
- # Open tag
- my $tag = $tree->[1];
- $result .= "<$tag";
-
- # Attributes
- my @attrs;
- for my $key (sort keys %{$tree->[2]}) {
+ # Root
+ return join '', map { _render($_, $xml) } @$tree[1 .. $#$tree]
+ if $type eq 'root';
- # No value
- push @attrs, $key and next unless defined(my $value = $tree->[2]{$key});
-
- # Key and value
- push @attrs, $key . '="' . xml_escape($value) . '"';
- }
- $result .= join ' ', '', @attrs if @attrs;
-
- # Element without end tag
- return $xml ? "$result />" : $EMPTY{$tag} ? "$result>" : "$result></$tag>"
- unless $tree->[4];
+ # Start tag
+ my $tag = $tree->[1];
+ my $result = "<$tag";
- # Close tag
- $result .= '>';
+ # Attributes
+ for my $key (sort keys %{$tree->[2]}) {
+ $result .= " $key" and next unless defined(my $value = $tree->[2]{$key});
+ $result .= " $key" . '="' . xml_escape($value) . '"';
}
- # Render whole tree
+ # No children
+ return $xml ? "$result />" : $EMPTY{$tag} ? "$result>" : "$result></$tag>"
+ unless $tree->[4];
+
+ # Children
no warnings 'recursion';
- $result .= _render($tree->[$_], $xml)
- for ($type eq 'root' ? 1 : 4) .. $#$tree;
+ $result .= '>' . join '', map { _render($_, $xml) } @$tree[4 .. $#$tree];
# End tag
- $result .= '</' . $tree->[1] . '>' if $type eq 'tag';
-
- return $result;
+ return "$result</$tag>";
}
sub _start {
@@ -16,24 +16,6 @@ use Mojo::DOM::HTML;
use Mojo::Util qw(deprecated squish);
use Scalar::Util qw(blessed weaken);
-# DEPRECATED in Tiger Face!
-sub AUTOLOAD {
- my $self = shift;
-
- my ($package, $method) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
- deprecated "Mojo::DOM::AUTOLOAD ($method) is DEPRECATED"
- . ' in favor of Mojo::DOM::children';
- croak "Undefined subroutine &${package}::$method called"
- unless blessed $self && $self->isa(__PACKAGE__);
-
- my $children = $self->children($method);
- return @$children > 1 ? $children : $children->[0] if @$children;
- croak qq{Can't locate object method "$method" via package "$package"};
-}
-
-# DEPRECATED in Tiger Face!
-sub DESTROY { }
-
sub all_contents { $_[0]->_collect(_all(_nodes($_[0]->tree))) }
sub all_text { shift->_all_text(1, @_) }
@@ -72,8 +54,8 @@ sub children { _select($_[0]->_collect(_nodes($_[0]->tree, 1)), $_[1]) }
sub content {
my $self = shift;
- my $node = $self->node;
- if ($node eq 'root' || $node eq 'tag') {
+ my $type = $self->node;
+ if ($type eq 'root' || $type eq 'tag') {
return $self->_content(0, 1, @_) if @_;
my $html = Mojo::DOM::HTML->new(xml => $self->xml);
return join '', map { $html->tree($_)->render } _nodes($self->tree);
@@ -100,11 +82,11 @@ sub namespace {
# Extract namespace prefix and search parents
my $ns = $tree->[1] =~ /^(.*?):/ ? "xmlns:$1" : undef;
- for my $n ($tree, $self->_ancestors) {
+ for my $node ($tree, $self->_ancestors) {
# Namespace for prefix
- my $attrs = $n->[2];
- if ($ns) { /^\Q$ns\E$/ and return $attrs->{$_} for keys %$attrs }
+ my $attrs = $node->[2];
+ if ($ns) { $_ eq $ns and return $attrs->{$_} for keys %$attrs }
# Namespace attribute
elsif (defined $attrs->{xmlns}) { return $attrs->{xmlns} }
@@ -184,27 +166,6 @@ sub type {
return $self;
}
-# DEPRECATED in Tiger Face!
-sub val {
- deprecated 'Mojo::DOM::val is DEPRECATED';
- my $self = shift;
-
- # "option"
- my $type = $self->type;
- return Mojo::Collection->new($self->{value} // $self->text)
- if $type eq 'option';
-
- # "select"
- return $self->find('option[selected]')->map('val')->flatten
- if $type eq 'select';
-
- # "textarea"
- return Mojo::Collection->new($self->text) if $type eq 'textarea';
-
- # "input" or "button"
- return Mojo::Collection->new($self->{value} // ());
-}
-
sub wrap { shift->_wrap(0, @_) }
sub wrap_content { shift->_wrap(1, @_) }
@@ -288,12 +249,11 @@ sub _link {
my ($children, $parent) = @_;
# Link parent to children
- my @new;
- for my $n (@$children[1 .. $#$children]) {
- push @new, $n;
- my $offset = $n->[0] eq 'tag' ? 3 : 2;
- $n->[$offset] = $parent;
- weaken $n->[$offset];
+ my @new = @$children[1 .. $#$children];
+ for my $node (@new) {
+ my $offset = $node->[0] eq 'tag' ? 3 : 2;
+ $node->[$offset] = $parent;
+ weaken $node->[$offset];
}
return @new;
@@ -359,27 +319,27 @@ sub _text {
}
my $text = '';
- for my $n (@$nodes) {
- my $type = $n->[0];
-
- # Nested tag
- my $content = '';
- if ($type eq 'tag' && $recurse) {
- no warnings 'recursion';
- $content = _text([_nodes($n)], 1, $n->[1] eq 'pre' ? 0 : $trim);
- }
+ for my $node (@$nodes) {
+ my $type = $node->[0];
# Text
- elsif ($type eq 'text') { $content = $trim ? squish($n->[1]) : $n->[1] }
+ my $chunk = '';
+ if ($type eq 'text') { $chunk = $trim ? squish($node->[1]) : $node->[1] }
# CDATA or raw text
- elsif ($type eq 'cdata' || $type eq 'raw') { $content = $n->[1] }
+ elsif ($type eq 'cdata' || $type eq 'raw') { $chunk = $node->[1] }
+
+ # Nested tag
+ elsif ($type eq 'tag' && $recurse) {
+ no warnings 'recursion';
+ $chunk = _text([_nodes($node)], 1, $node->[1] eq 'pre' ? 0 : $trim);
+ }
# Add leading whitespace if punctuation allows it
- $content = " $content" if $text =~ /\S\z/ && $content =~ /^[^.!?,;:\s]+/;
+ $chunk = " $chunk" if $text =~ /\S\z/ && $chunk =~ /^[^.!?,;:\s]+/;
# Trim whitespace blocks
- $text .= $content if $content =~ /\S+/ || !$trim;
+ $text .= $chunk if $chunk =~ /\S+/ || !$trim;
}
return $text;
@@ -92,7 +92,7 @@ sub _connect {
->watch($handle, 0, 1);
}
-sub _port { $_[0]->{socks_port} || $_[0]->{port} || ($_[0]->{tls} ? 443 : 80) }
+sub _port { $_[0]{socks_port} || $_[0]{port} || ($_[0]{tls} ? 443 : 80) }
sub _ready {
my ($self, $args) = @_;
@@ -236,6 +236,9 @@ Data shared between all L</"steps">.
# Remove value
my $foo = delete $delay->data->{foo};
+ # Assign multiple values at once
+ $delay->data(foo => 'test', bar => 23);
+
=head2 pass
$delay = $delay->pass;
@@ -86,8 +86,14 @@ sub delay {
}
sub is_running { _instance(shift)->reactor->is_running }
-sub next_tick { _instance(shift)->reactor->next_tick(@_) }
-sub one_tick { _instance(shift)->reactor->one_tick }
+
+sub next_tick {
+ my ($self, $cb) = (_instance(shift), @_);
+ weaken $self;
+ return $self->reactor->next_tick(sub { $self->$cb });
+}
+
+sub one_tick { _instance(shift)->reactor->one_tick }
sub recurring { shift->_timer(recurring => @_) }
@@ -169,7 +175,7 @@ sub _accepting {
# Check if multi-accept is desirable
my $multi = $self->multi_accept;
$_->multi_accept($max < $multi ? 1 : $multi)->start for values %$acceptors;
- $self->{accepting}++;
+ $self->{accepting} = 1;
}
sub _id {
@@ -1,8 +1,6 @@
package Mojo::JSON::Pointer;
use Mojo::Base -base;
-use Mojo::Util 'deprecated';
-
has 'data';
sub contains { shift->_pointer(1, @_) }
@@ -12,17 +10,12 @@ sub new { @_ > 1 ? shift->SUPER::new(data => shift) : shift->SUPER::new }
sub _pointer {
my ($self, $contains, $pointer) = @_;
- my $data = $self->data;
-
- # DEPRECATED in Tiger Face!
- deprecated 'Support for data arguments in Mojo::JSON::Pointer is DEPRECATED'
- and (($pointer, $data) = ($_[3], $pointer))
- if defined $_[3];
+ my $data = $self->data;
return $data unless $pointer =~ s!^/!!;
for my $p ($pointer eq '' ? ($pointer) : (split '/', $pointer)) {
- $p =~ s/~0/~/g;
$p =~ s!~1!/!g;
+ $p =~ s/~0/~/g;
# Hash
if (ref $data eq 'HASH' && exists $data->{$p}) { $data = $data->{$p} }
@@ -1,20 +1,16 @@
package Mojo::JSON;
-use Mojo::Base -base;
+use Mojo::Base -strict;
use B;
use Carp 'croak';
use Exporter 'import';
-use Mojo::Util 'deprecated';
+use Mojo::Util;
use Scalar::Util 'blessed';
-# DEPRECATED in Tiger Face!
-has 'error';
-
our @EXPORT_OK = qw(decode_json encode_json false from_json j to_json true);
-# Literal names
-my $FALSE = bless \(my $false = 0), 'Mojo::JSON::_Bool';
-my $TRUE = bless \(my $true = 1), 'Mojo::JSON::_Bool';
+# Booleans
+my ($FALSE, $TRUE) = map { bless \(my $dummy = $_), 'Mojo::JSON::_Bool' } 0, 1;
# Escaped special character map (with u2028 and u2029)
my %ESCAPE = (
@@ -32,20 +28,11 @@ my %ESCAPE = (
my %REVERSE = map { $ESCAPE{$_} => "\\$_" } keys %ESCAPE;
for (0x00 .. 0x1f) { $REVERSE{pack 'C', $_} //= sprintf '\u%.4X', $_ }
-# DEPRECATED in Tiger Face!
-sub decode {
- shift->error(my $err = _decode(\my $value, pop));
- return defined $err ? undef : $value;
-}
-
sub decode_json {
my $err = _decode(\my $value, shift);
return defined $err ? croak $err : $value;
}
-# DEPRECATED in Tiger Face!
-sub encode { encode_json($_[1]) }
-
sub encode_json { Mojo::Util::encode 'UTF-8', _encode_value(shift) }
sub false () {$FALSE}
@@ -60,12 +47,6 @@ sub j {
return eval { decode_json($_[0]) };
}
-# DEPRECATED in Tiger Face!
-sub new {
- deprecated 'Object-oriented Mojo::JSON API is DEPRECATED';
- return shift->SUPER::new(@_);
-}
-
sub to_json { _encode_value(shift) }
sub true () {$TRUE}
@@ -20,7 +20,7 @@ has level => 'debug';
has max_history_size => 10;
has 'path';
-# Supported log level
+# Supported log levels
my $LEVEL = {debug => 1, info => 2, warn => 3, error => 4, fatal => 5};
sub append {
@@ -94,11 +94,11 @@ Mojo::Log - Simple logger
my $log = Mojo::Log->new(path => '/var/log/mojo.log', level => 'warn');
# Log messages
- $log->debug('Why is this not working?');
- $log->info('FYI: it happened again.');
- $log->warn('This might be a problem.');
- $log->error('Garden variety error.');
- $log->fatal('Boom!');
+ $log->debug('Not sure what is happening here');
+ $log->info('FYI: it happened again');
+ $log->warn('This might be a problem');
+ $log->error('Garden variety error');
+ $log->fatal('Boom');
=head1 DESCRIPTION
@@ -137,7 +137,7 @@ A callback for formatting log messages.
$log->format(sub {
my ($time, $level, @lines) = @_;
- return "[Thu May 15 17:47:04 2014] [info] I ♥ Mojolicious.\n";
+ return "[Thu May 15 17:47:04 2014] [info] I ♥ Mojolicious\n";
});
=head2 handle
@@ -151,7 +151,7 @@ L</"path"> or C<STDERR>.
=head2 history
my $history = $log->history;
- $log = $log->history([[time, 'debug', 'That went wrong.']]);
+ $log = $log->history([[time, 'debug', 'That went wrong']]);
The last few logged messages.
@@ -186,35 +186,35 @@ the following new ones.
=head2 append
- $log->append("[Thu May 15 17:47:04 2014] [info] I ♥ Mojolicious.\n");
+ $log->append("[Thu May 15 17:47:04 2014] [info] I ♥ Mojolicious\n");
Append message to L</"handle">.
=head2 debug
- $log = $log->debug('You screwed up, but that is ok.');
- $log = $log->debug('All', 'cool!');
+ $log = $log->debug('You screwed up, but that is ok');
+ $log = $log->debug('All', 'cool');
Log debug message.
=head2 error
- $log = $log->error('You really screwed up this time.');
- $log = $log->error('Wow', 'seriously!');
+ $log = $log->error('You really screwed up this time');
+ $log = $log->error('Wow', 'seriously');
Log error message.
=head2 fatal
$log = $log->fatal('Its over...');
- $log = $log->fatal('Bye', 'bye!');
+ $log = $log->fatal('Bye', 'bye');
Log fatal message.
=head2 info
- $log = $log->info('You are bad, but you prolly know already.');
- $log = $log->info('Ok', 'then!');
+ $log = $log->info('You are bad, but you prolly know already');
+ $log = $log->info('Ok', 'then');
Log info message.
@@ -256,8 +256,8 @@ Check for warn log level.
=head2 log
- $log = $log->log(debug => 'This should work.');
- $log = $log->log(debug => 'This', 'too!');
+ $log = $log->log(debug => 'This should work');
+ $log = $log->log(debug => 'This', 'too');
Emit L</"message"> event.
@@ -271,7 +271,7 @@ default logger.
=head2 warn
$log = $log->warn('Dont do that Dave...');
- $log = $log->warn('No', 'really!');
+ $log = $log->warn('No', 'really');
Log warn message.
@@ -60,7 +60,7 @@ sub extract_start_line {
return undef unless $$bufref =~ s/^\s*(.*?)\x0d?\x0a//;
# We have a (hopefully) full request-line
- return !$self->error({message => 'Bad request start-line', advice => 400})
+ return !$self->error({message => 'Bad request start-line'})
unless $1 =~ $START_LINE_RE;
my $url = $self->method($1)->version($3)->url;
return !!($1 eq 'CONNECT' ? $url->authority($2) : $url->parse($2));
@@ -156,7 +156,7 @@ sub parse {
# Check start-line size
my $len = index $self->{buffer}, "\x0a";
$len = length $self->{buffer} if $len < 0;
- return $self->_limit('Maximum start-line size exceeded', 431)
+ return $self->_limit('Maximum start-line size exceeded')
if $len > $self->max_line_size;
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer});
@@ -169,15 +169,15 @@ sub parse {
# Check message size
my $max = $self->max_message_size;
- return $self->_limit('Maximum message size exceeded', 413)
+ return $self->_limit('Maximum message size exceeded')
if $max && $max < $self->{raw_size};
# Check header size
- return $self->_limit('Maximum header size exceeded', 431)
+ return $self->_limit('Maximum header size exceeded')
if $self->headers->is_limit_exceeded;
# Check buffer size
- return $self->_limit('Maximum buffer size exceeded', 400)
+ return $self->_limit('Maximum buffer size exceeded')
if $self->content->is_limit_exceeded;
return $self->emit('progress')->content->is_finished ? $self->finish : $self;
@@ -253,11 +253,7 @@ sub _cache {
return $all ? $objects : $objects->[-1];
}
-sub _limit {
- my ($self, $msg, $code) = @_;
- $self->{limit} = 1;
- return $self->error({message => $msg, advice => $code});
-}
+sub _limit { ++$_[0]{limit} and return $_[0]->error({message => $_[1]}) }
sub _parse_formdata {
my ($self, $upload) = @_;
@@ -499,17 +495,14 @@ make sure it is not excessively large, there's a 10MB limit by default.
=head2 error
my $err = $msg->error;
- $msg = $msg->error({message => 'Parser error', advice => 500});
+ $msg = $msg->error({message => 'Parser error'});
Get or set message error, an C<undef> return value indicates that there is no
error.
- # Connection error
+ # Connection or parser error
$msg->error({message => 'Connection refused'});
- # Parser error
- $msg->error({message => 'Maximum message size exceeded', advice => 413});
-
# 4xx/5xx response
$msg->error({message => 'Internal Server Error', code => 500});
@@ -38,7 +38,17 @@ sub clone {
return $clone;
}
-sub every_param { shift->_param(@_) }
+sub every_param {
+ my ($self, $name) = @_;
+
+ my @values;
+ my $params = $self->params;
+ for (my $i = 0; $i < @$params; $i += 2) {
+ push @values, $params->[$i + 1] if $params->[$i] eq $name;
+ }
+
+ return \@values;
+}
sub merge {
my $self = shift;
@@ -56,14 +66,14 @@ sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub param {
my ($self, $name) = (shift, shift);
- # List names
- return sort keys %{$self->to_hash} unless $name;
-
# Multiple names
return map { $self->param($_) } @$name if ref $name eq 'ARRAY';
+ # List names
+ return sort keys %{$self->to_hash} unless defined $name;
+
# Last value
- return $self->_param($name)->[-1] unless @_;
+ return $self->every_param($name)->[-1] unless @_;
# Replace values
$self->remove($name);
@@ -177,18 +187,6 @@ sub to_string {
return join '&', @pairs;
}
-sub _param {
- my ($self, $name) = @_;
-
- my @values;
- my $params = $self->params;
- for (my $i = 0; $i < @$params; $i += 2) {
- push @values, $params->[$i + 1] if $params->[$i] eq $name;
- }
-
- return \@values;
-}
-
1;
=encoding utf8
@@ -3,6 +3,7 @@ use Mojo::Base 'Mojo::Server';
use Mojo::IOLoop;
use Mojo::URL;
+use Mojo::Util 'term_escape';
use Scalar::Util 'weaken';
use constant DEBUG => $ENV{MOJO_DAEMON_DEBUG} || 0;
@@ -165,15 +166,15 @@ sub _listen {
sub { $self && $self->app->log->error(pop) && $self->_close($id) });
$stream->on(read => sub { $self->_read($id => pop) });
$stream->on(timeout =>
- sub { $self->app->log->debug('Inactivity timeout.') if $c->{tx} });
+ sub { $self->app->log->debug('Inactivity timeout') if $c->{tx} });
}
);
return if $self->silent;
- $self->app->log->info(qq{Listening at "$url".});
+ $self->app->log->info(qq{Listening at "$url"});
$query->params([]);
$url->host('127.0.0.1') if $url->host eq '*';
- say "Server available at $url.";
+ say "Server available at $url";
}
sub _read {
@@ -182,7 +183,7 @@ sub _read {
# Make sure we have a transaction and parse chunk
return unless my $c = $self->{connections}{$id};
my $tx = $c->{tx} ||= $self->_build_tx($id, $c);
- warn "-- Server <<< Client (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG;
+ warn term_escape "-- Server <<< Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;
$tx->server_read($chunk);
# Last keep-alive request or corrupted connection
@@ -200,6 +201,8 @@ sub _remove {
$self->_close($id);
}
+sub _url { shift->req->url->to_abs }
+
sub _write {
my ($self, $id) = @_;
@@ -209,7 +212,7 @@ sub _write {
return if !$tx->is_writing || $c->{writing}++;
my $chunk = $tx->server_write;
delete $c->{writing};
- warn "-- Server >>> Client (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG;
+ warn term_escape "-- Server >>> Client (@{[_url($tx)]})\n$chunk\n" if DEBUG;
my $stream = $self->ioloop->stream($id)->write($chunk);
# Finish or continue writing
@@ -57,7 +57,7 @@ sub run {
$self->configure('hypnotoad');
weaken $self;
$prefork->on(wait => sub { $self->_manage });
- $prefork->on(reap => sub { $self->_reap(pop) });
+ $prefork->on(reap => sub { $self->_cleanup(pop) });
$prefork->on(finish => sub { $self->{finished} = 1 });
# Testing
@@ -78,6 +78,15 @@ sub run {
$prefork->cleanup(1)->run;
}
+sub _cleanup {
+ my ($self, $pid) = @_;
+
+ # Clean up failed upgrade
+ return unless ($self->{new} || '') eq $pid;
+ $self->prefork->app->log->error('Zero downtime software upgrade failed');
+ delete @$self{qw(new upgrade)};
+}
+
sub _exit { say shift and exit 0 }
sub _hot_deploy {
@@ -94,9 +103,11 @@ sub _manage {
my $self = shift;
# Upgraded
- my $log = $self->prefork->app->log;
+ my $prefork = $self->prefork;
+ my $log = $prefork->app->log;
if ($ENV{HYPNOTOAD_PID} && $ENV{HYPNOTOAD_PID} ne $$) {
- $log->info("Upgrade successful, stopping $ENV{HYPNOTOAD_PID}.");
+ return unless $prefork->healthy == $prefork->workers;
+ $log->info("Upgrade successful, stopping $ENV{HYPNOTOAD_PID}");
kill 'QUIT', $ENV{HYPNOTOAD_PID};
}
$ENV{HYPNOTOAD_PID} = $$ unless ($ENV{HYPNOTOAD_PID} // '') eq $$;
@@ -106,7 +117,7 @@ sub _manage {
# Fresh start
unless ($self->{new}) {
- $log->info('Starting zero downtime software upgrade.');
+ $log->info('Starting zero downtime software upgrade');
die "Can't fork: $!" unless defined(my $pid = $self->{new} = fork);
exec $^X, $ENV{HYPNOTOAD_EXE} or die "Can't exec: $!" unless $pid;
}
@@ -117,15 +128,6 @@ sub _manage {
}
}
-sub _reap {
- my ($self, $pid) = @_;
-
- # Clean up failed upgrade
- return unless ($self->{new} || '') eq $pid;
- $self->prefork->app->log->error('Zero downtime software upgrade failed.');
- delete @$self{qw(new upgrade)};
-}
-
sub _stop {
_exit('Hypnotoad server not running.')
unless my $pid = shift->prefork->check_pid;
@@ -157,12 +159,12 @@ keep-alive, multiple event loop and hot deployment support that just works.
Note that the server uses signals for process management, so you should avoid
modifying signal handlers in your applications.
-To start applications with it you can use the L<hypnotoad> script, for
-L<Mojolicious> and L<Mojolicious::Lite> applications it will default to
-C<production> mode.
+To start applications with it you can use the L<hypnotoad> script, which
+listens on port C<8080>, automatically daemonizes the server process and
+defaults to C<production> mode for L<Mojolicious> and L<Mojolicious::Lite>
+applications.
$ hypnotoad ./myapp.pl
- Server available at http://127.0.0.1:8080.
You can run the same command again for automatic hot deployment.
@@ -188,11 +190,11 @@ with the following signals.
=head2 INT, TERM
-Shutdown server immediately.
+Shut down server immediately.
=head2 QUIT
-Shutdown server gracefully.
+Shut down server gracefully.
=head2 TTIN
@@ -23,7 +23,6 @@ sub check {
elsif (-r $watch) { push @files, $watch }
}
- # Check files
$self->_check($_) and return $_ for @files;
return undef;
}
@@ -32,10 +31,10 @@ sub run {
my ($self, $app) = @_;
# Clean manager environment
- local $SIG{CHLD} = sub { $self->_reap };
+ local $SIG{CHLD} = sub { $self->_reap if $self->{worker} };
local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = sub {
$self->{finished} = 1;
- kill 'TERM', $self->{running} if $self->{running};
+ kill 'TERM', $self->{worker} if $self->{worker};
};
unshift @{$self->watch}, $app;
$self->{modified} = 1;
@@ -43,7 +42,7 @@ sub run {
# Prepare and cache listen sockets for smooth restarting
my $daemon = Mojo::Server::Daemon->new(silent => 1)->start->stop;
- $self->_manage while !$self->{finished} || $self->{running};
+ $self->_manage while !$self->{finished} || $self->{worker};
exit 0;
}
@@ -64,28 +63,28 @@ sub _manage {
if (defined(my $file = $self->check)) {
say qq{File "$file" changed, restarting.} if $ENV{MORBO_VERBOSE};
- kill 'TERM', $self->{running} if $self->{running};
+ kill 'TERM', $self->{worker} if $self->{worker};
$self->{modified} = 1;
}
+ # Windows workaround
+ delete $self->{worker} if $self->{worker} && !kill 0, $self->{worker};
+
$self->_reap;
- delete $self->{running} if $self->{running} && !kill 0, $self->{running};
- $self->_spawn if !$self->{running} && delete $self->{modified};
+ $self->_spawn if !$self->{worker} && delete $self->{modified};
sleep 1;
}
-sub _reap { delete $_[0]{running} while (waitpid -1, WNOHANG) > 0 }
+sub _reap { delete $_[0]{worker} while (waitpid -1, WNOHANG) > 0 }
sub _spawn {
my $self = shift;
- # Fork
+ # Manager
my $manager = $$;
$ENV{MORBO_REV}++;
- die "Can't fork: $!" unless defined(my $pid = fork);
-
- # Manager
- return $self->{running} = $pid if $pid;
+ die "Can't fork: $!" unless defined(my $pid = $self->{worker} = fork);
+ return if $pid;
# Worker
$SIG{CHLD} = 'DEFAULT';
@@ -128,7 +127,7 @@ applications.
To start applications with it you can use the L<morbo> script.
$ morbo ./myapp.pl
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
For better scalability (epoll, kqueue) and to provide non-blocking name
resolution, SOCKS5 as well as TLS support, the optional modules L<EV> (4.0+),
@@ -51,13 +51,17 @@ sub ensure_pid_file {
return if -e (my $file = $self->pid_file);
# Create PID file
- $self->app->log->info(qq{Creating process id file "$file".});
+ $self->app->log->info(qq{Creating process id file "$file"});
die qq{Can't create process id file "$file": $!}
unless open my $handle, '>', $file;
chmod 0644, $handle;
print $handle $$;
}
+sub healthy {
+ scalar grep { $_->{healthy} } values %{shift->{pool}};
+}
+
sub run {
my $self = shift;
@@ -78,8 +82,7 @@ sub run {
local $SIG{INT} = local $SIG{TERM} = sub { $self->_term };
local $SIG{CHLD} = sub {
while ((my $pid = waitpid -1, WNOHANG) > 0) {
- $self->app->log->debug("Worker $pid stopped.")
- if delete $self->emit(reap => $pid)->{pool}{$pid};
+ $self->emit(reap => $pid)->_stopped($pid);
}
};
local $SIG{QUIT} = sub { $self->_term(1) };
@@ -91,33 +94,17 @@ sub run {
};
# Preload application before starting workers
- $self->start->app->log->info("Manager $$ started.");
+ $self->start->app->log->info("Manager $$ started");
$self->{running} = 1;
$self->_manage while $self->{running};
}
-sub _heartbeat {
- my $self = shift;
-
- # Poll for heartbeats
- my $poll = $self->{poll};
- $poll->poll(1);
- return unless $poll->handles(POLLIN | POLLPRI);
- return unless $self->{reader}->sysread(my $chunk, 4194304);
-
- # Update heartbeats (and stop gracefully if necessary)
- my $time = steady_time;
- while ($chunk =~ /(\d+):(\d)\n/g) {
- next unless my $w = $self->{pool}{$1};
- $self->emit(heartbeat => $1) and $w->{time} = $time;
- $w->{graceful} ||= $time if $2;
- }
-}
+sub _heartbeat { shift->{writer}->syswrite("$$:$_[0]\n") or exit 0 }
sub _manage {
my $self = shift;
- # Spawn more workers and check PID file
+ # Spawn more workers if necessary and check PID file
if (!$self->{finished}) {
$self->_spawn while keys %{$self->{pool}} < $self->workers;
$self->ensure_pid_file;
@@ -127,31 +114,32 @@ sub _manage {
elsif (!keys %{$self->{pool}}) { return delete $self->{running} }
# Wait for heartbeats
- $self->emit('wait')->_heartbeat;
+ $self->_wait;
my $interval = $self->heartbeat_interval;
my $ht = $self->heartbeat_timeout;
my $gt = $self->graceful_timeout;
- my $time = steady_time;
my $log = $self->app->log;
+ my $time = steady_time;
for my $pid (keys %{$self->{pool}}) {
next unless my $w = $self->{pool}{$pid};
# No heartbeat (graceful stop)
- $log->error("Worker $pid has no heartbeat, restarting.")
+ $log->error("Worker $pid has no heartbeat, restarting")
and $w->{graceful} = $time
if !$w->{graceful} && ($w->{time} + $interval + $ht <= $time);
# Graceful stop with timeout
my $graceful = $w->{graceful} ||= $self->{graceful} ? $time : undef;
- $log->debug("Trying to stop worker $pid gracefully.")
- and kill 'QUIT', $pid
+ $log->debug("Stopping worker $pid gracefully")
+ and (kill 'QUIT', $pid or $self->_stopped($pid))
if $graceful && !$w->{quit}++;
$w->{force} = 1 if $graceful && $graceful + $gt <= $time;
# Normal stop
- $log->debug("Stopping worker $pid.") and kill 'KILL', $pid
+ $log->debug("Stopping worker $pid")
+ and (kill 'KILL', $pid or $self->_stopped($pid))
if $w->{force} || ($self->{finished} && !$graceful);
}
}
@@ -165,60 +153,82 @@ sub _spawn {
if $pid;
# Prepare lock file
- my $file = $self->{lock_file};
+ my $file = $self->cleanup(0)->{lock_file};
$self->app->log->error(qq{Can't open lock file "$file": $!})
unless open my $handle, '>', $file;
# Change user/group
- $self->setuidgid->cleanup(0);
+ $self->setuidgid;
# Accept mutex
+ weaken $self;
my $loop = $self->ioloop->lock(
sub {
- # Blocking ("ualarm" can't be imported on Windows)
- my $lock;
- if ($_[0]) {
- eval {
- local $SIG{ALRM} = sub { die "alarm\n" };
- my $old = Time::HiRes::ualarm $self->lock_timeout * 1000000;
- $lock = flock $handle, LOCK_EX;
- Time::HiRes::ualarm $old;
- 1;
- } or $lock = $@ eq "alarm\n" ? 0 : die $@;
- }
-
# Non-blocking
- else { $lock = flock $handle, LOCK_EX | LOCK_NB }
+ return flock $handle, LOCK_EX | LOCK_NB unless shift;
+ # Blocking ("ualarm" can't be imported on Windows)
+ my $lock;
+ eval {
+ local $SIG{ALRM} = sub { die "alarm\n" };
+ my $old = Time::HiRes::ualarm $self->lock_timeout * 1000000;
+ $lock = flock $handle, LOCK_EX;
+ Time::HiRes::ualarm $old;
+ 1;
+ } or $lock = $@ eq "alarm\n" ? 0 : die $@;
return $lock;
}
);
$loop->unlock(sub { flock $handle, LOCK_UN });
# Heartbeat messages
- weaken $self;
- $loop->recurring(
- $self->heartbeat_interval => sub {
- my $graceful = shift->max_connections ? 0 : 1;
- $self->{writer}->syswrite("$$:$graceful\n") or exit 0;
- }
- );
+ my $cb = sub { $self->_heartbeat(shift->max_connections ? 0 : 1) };
+ $loop->next_tick($cb);
+ $loop->recurring($self->heartbeat_interval => $cb);
# Clean worker environment
$SIG{$_} = 'DEFAULT' for qw(INT TERM CHLD TTIN TTOU);
$SIG{QUIT} = sub { $loop->max_connections(0) };
delete @$self{qw(poll reader)};
- $self->app->log->debug("Worker $$ started.");
+ $self->app->log->debug("Worker $$ started");
$loop->start;
exit 0;
}
+sub _stopped {
+ my ($self, $pid) = @_;
+
+ return unless my $w = delete $self->{pool}{$pid};
+
+ my $log = $self->app->log;
+ $log->debug("Worker $pid stopped");
+ $log->error("Worker $pid stopped too early, shutting down") and $self->_term
+ unless $w->{healthy};
+}
+
sub _term {
my ($self, $graceful) = @_;
- $self->emit(finish => $graceful)->{finished} = 1;
- $self->{graceful} = 1 if $graceful;
+ @{$self->emit(finish => $graceful)}{qw(finished graceful)} = (1, $graceful);
+}
+
+sub _wait {
+ my $self = shift;
+
+ # Poll for heartbeats
+ my $poll = $self->emit('wait')->{poll};
+ $poll->poll(1);
+ return unless $poll->handles(POLLIN | POLLPRI);
+ return unless $self->{reader}->sysread(my $chunk, 4194304);
+
+ # Update heartbeats (and stop gracefully if necessary)
+ my $time = steady_time;
+ while ($chunk =~ /(\d+):(\d)\n/g) {
+ next unless my $w = $self->{pool}{$1};
+ @$w{qw(healthy time)} = (1, $time) and $self->emit(heartbeat => $1);
+ $w->{graceful} ||= $time if $2;
+ }
}
1;
@@ -277,11 +287,11 @@ the following signals.
=head2 INT, TERM
-Shutdown server immediately.
+Shut down server immediately.
=head2 QUIT
-Shutdown server gracefully.
+Shut down server gracefully.
=head2 TTIN
@@ -499,6 +509,12 @@ is not running.
Ensure L</"pid_file"> exists.
+=head2 healthy
+
+ my $healthy = $prefork->healthy;
+
+Number of currently active worker processes with a heartbeat.
+
=head2 run
$prefork->run;
@@ -76,23 +76,23 @@ sub setuidgid {
# Group (make sure secondary groups are reassigned too)
if (my $group = $self->group) {
- return $self->_log(qq{Group "$group" does not exist.})
+ $self->_error(qq{Group "$group" does not exist})
unless defined(my $gid = getgrnam $group);
- return $self->_log(qq{Can't switch to group "$group": $!})
+ $self->_error(qq{Can't switch to group "$group": $!})
unless ($( = $) = "$gid $gid") && $) eq "$gid $gid" && $( eq "$gid $gid";
}
# User
return $self unless my $user = $self->user;
- return $self->_log(qq{User "$user" does not exist.})
+ $self->_error(qq{User "$user" does not exist})
unless defined(my $uid = getpwnam $user);
- return $self->_log(qq{Can't switch to user "$user": $!})
+ $self->_error(qq{Can't switch to user "$user": $!})
unless POSIX::setuid($uid);
return $self;
}
-sub _log { $_[0]->app->log->error($_[1]) and return $_[0] }
+sub _error { $_[0]->app->log->error($_[1]) and croak $_[1] }
1;
@@ -306,12 +306,12 @@ Mojo::Template - Perl-ish templates!
say $output;
# More advanced
- my $output = $mt->render(<<'EOF', 23, 'foo bar');
- % my ($num, $text) = @_;
+ my $output = $mt->render(<<'EOF', 23, 'More advanced');
+ % my ($num, $title) = @_;
%= 5 * 5
<!DOCTYPE html>
<html>
- <head><title>More advanced</title></head>
+ <head><title><%= $title %></title></head>
<body>
test 123
foo <% my $i = $num + 2; %>
@@ -5,7 +5,7 @@ use Compress::Raw::Zlib 'Z_SYNC_FLUSH';
use Config;
use Mojo::JSON qw(encode_json j);
use Mojo::Transaction::HTTP;
-use Mojo::Util qw(b64_encode decode encode sha1_bytes xor_encode);
+use Mojo::Util qw(b64_encode decode dumper encode sha1_bytes xor_encode);
use constant DEBUG => $ENV{MOJO_WEBSOCKET_DEBUG} || 0;
@@ -45,19 +45,19 @@ sub build_frame {
my $len = length $payload;
my $masked = $self->masked;
if ($len < 126) {
- warn "-- Small payload ($len)\n$payload\n" if DEBUG;
+ warn "-- Small payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'C', $masked ? ($len | 128) : $len;
}
# Extended payload (16-bit)
elsif ($len < 65536) {
- warn "-- Extended 16-bit payload ($len)\n$payload\n" if DEBUG;
+ warn "-- Extended 16-bit payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'Cn', $masked ? (126 | 128) : 126, $len;
}
# Extended payload (64-bit with 32-bit fallback)
else {
- warn "-- Extended 64-bit payload ($len)\n$payload\n" if DEBUG;
+ warn "-- Extended 64-bit payload ($len)\n@{[dumper $payload]}" if DEBUG;
$frame .= pack 'C', $masked ? (127 | 128) : 127;
$frame .= MODERN ? pack('Q>', $len) : pack('NN', 0, $len & 0xffffffff);
}
@@ -202,7 +202,7 @@ sub parse_frame {
# Payload
my $payload = $len ? substr($$buffer, 0, $len, '') : '';
$payload = xor_encode($payload, substr($payload, 0, 4, '') x 128) if $masked;
- warn "$payload\n" if DEBUG;
+ warn dumper $payload if DEBUG;
return [$fin, $rsv1, $rsv2, $rsv3, $op, $payload];
}
@@ -214,8 +214,11 @@ Connection identifier or socket.
my $err = $tx->error;
-Return transaction error or C<undef> if there is no error, commonly used
-together with L</"success">.
+Get request or response error and return C<undef> if there is no error,
+commonly used together with L</"success">.
+
+ # Longer version
+ my $err = $tx->req->error || $tx->res->error;
# Check for different kinds of errors
if (my $err = $tx->error) {
@@ -4,7 +4,7 @@ use Mojo::Base 'Mojo::EventEmitter';
# "Fry: Since when is the Internet about robbing people of their privacy?
# Bender: August 6, 1991."
use Mojo::IOLoop;
-use Mojo::Util 'monkey_patch';
+use Mojo::Util qw(monkey_patch term_escape);
use Mojo::UserAgent::CookieJar;
use Mojo::UserAgent::Proxy;
use Mojo::UserAgent::Server;
@@ -50,12 +50,12 @@ sub start {
# Non-blocking
if ($cb) {
- warn "-- Non-blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG;
+ warn "-- Non-blocking request (@{[_url($tx)]})\n" if DEBUG;
return $self->_start(1, $tx, $cb);
}
# Blocking
- warn "-- Blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG;
+ warn "-- Blocking request (@{[_url($tx)]})\n" if DEBUG;
$self->_start(0, $tx => sub { shift->ioloop->stop; $tx = shift });
$self->ioloop->start;
@@ -187,7 +187,7 @@ sub _connection {
my ($proto, $host, $port) = $self->transactor->endpoint($tx);
$id ||= $self->_dequeue($nb, "$proto:$host:$port", 1);
if ($id && !ref $id) {
- warn "-- Reusing connection ($proto:$host:$port)\n" if DEBUG;
+ warn "-- Reusing connection ($proto://$host:$port)\n" if DEBUG;
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $tx};
$tx->kept_alive(1) unless $tx->connection;
$self->_connected($id);
@@ -198,7 +198,7 @@ sub _connection {
if (my $id = $self->_connect_proxy($nb, $tx, $cb)) { return $id }
# Connect
- warn "-- Connect ($proto:$host:$port)\n" if DEBUG;
+ warn "-- Connect ($proto://$host:$port)\n" if DEBUG;
$id = $self->_connect($nb, 1, $tx, $id, \&_connected);
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $tx};
@@ -279,7 +279,7 @@ sub _read {
return $self->_remove($id) unless my $tx = $c->{tx};
# Process incoming data
- warn "-- Client <<< Server (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG;
+ warn term_escape "-- Client <<< Server (@{[_url($tx)]})\n$chunk\n" if DEBUG;
$tx->client_read($chunk);
if ($tx->is_finished) { $self->_finish($id) }
elsif ($tx->is_writing) { $self->_write($id) }
@@ -329,6 +329,8 @@ sub _start {
return $id;
}
+sub _url { shift->req->url->to_abs }
+
sub _write {
my ($self, $id) = @_;
@@ -338,7 +340,7 @@ sub _write {
return if !$tx->is_writing || $c->{writing}++;
my $chunk = $tx->client_write;
delete $c->{writing};
- warn "-- Client >>> Server (@{[$tx->req->url->to_abs]})\n$chunk\n" if DEBUG;
+ warn term_escape "-- Client >>> Server (@{[_url($tx)]})\n$chunk\n" if DEBUG;
my $stream = $self->_loop($c->{nb})->stream($id)->write($chunk);
$self->_finish($id) if $tx->is_finished;
@@ -56,8 +56,8 @@ our @EXPORT_OK = (
qw(decode deprecated dumper encode hmac_sha1_sum html_unescape md5_bytes),
qw(md5_sum monkey_patch punycode_decode punycode_encode quote),
qw(secure_compare sha1_bytes sha1_sum slurp split_header spurt squish),
- qw(steady_time tablify trim unindent unquote url_escape url_unescape),
- qw(xml_escape xor_encode xss_escape)
+ qw(steady_time tablify term_escape trim unindent unquote url_escape),
+ qw(url_unescape xml_escape xor_encode xss_escape)
);
sub b64_decode { decode_base64 $_[0] }
@@ -76,7 +76,7 @@ sub camelize {
sub class_to_file {
my $class = shift;
$class =~ s/::|'//g;
- $class =~ s/([A-Z])([A-Z]*)/$1.lc($2)/ge;
+ $class =~ s/([A-Z])([A-Z]*)/$1 . lc $2/ge;
return decamelize($class);
}
@@ -299,6 +299,12 @@ sub tablify {
return join '', map { sprintf "$format\n", @$_ } @$rows;
}
+sub term_escape {
+ my $str = shift;
+ $str =~ s/([\x00-\x09\x0b-\x1f\x7f\x80-\x9f])/sprintf '\\x%02x', ord $1/ge;
+ return $str;
+}
+
sub trim {
my $str = shift;
$str =~ s/^\s+//;
@@ -323,14 +329,14 @@ sub unquote {
sub url_escape {
my ($str, $pattern) = @_;
- if ($pattern) { $str =~ s/([$pattern])/sprintf('%%%02X',ord($1))/ge }
- else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf('%%%02X',ord($1))/ge }
+ if ($pattern) { $str =~ s/([$pattern])/sprintf '%%%02X', ord $1/ge }
+ else { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }
return $str;
}
sub url_unescape {
my $str = shift;
- $str =~ s/%([0-9a-fA-F]{2})/chr(hex($1))/ge;
+ $str =~ s/%([0-9a-fA-F]{2})/chr hex $1/ge;
return $str;
}
@@ -558,12 +564,18 @@ Encode characters to bytes.
Generate HMAC-SHA1 checksum for bytes.
+ # "11cedfd5ec11adc0ec234466d8a0f2a83736aa68"
+ hmac_sha1_sum 'foo', 'passw0rd';
+
=head2 html_unescape
my $str = html_unescape $escaped;
Unescape all HTML entities in string.
+ # "<div>"
+ html_unescape '<div>';
+
=head2 md5_bytes
my $checksum = md5_bytes $bytes;
@@ -576,6 +588,9 @@ Generate binary MD5 checksum for bytes.
Generate MD5 checksum for bytes.
+ # "acbd18db4cc2f85cedef654fccc4a4d8"
+ md5_sum 'foo';
+
=head2 monkey_patch
monkey_patch $package, foo => sub {...};
@@ -595,6 +610,9 @@ Monkey patch functions into package.
Punycode decode string as described in
L<RFC 3492|http://tools.ietf.org/html/rfc3492>.
+ # "bücher"
+ punycode_decode 'bcher-kva';
+
=head2 punycode_encode
my $punycode = punycode_encode $str;
@@ -602,6 +620,9 @@ L<RFC 3492|http://tools.ietf.org/html/rfc3492>.
Punycode encode string as described in
L<RFC 3492|http://tools.ietf.org/html/rfc3492>.
+ # "bcher-kva"
+ punycode_encode 'bücher';
+
=head2 quote
my $quoted = quote $str;
@@ -626,6 +647,9 @@ Generate binary SHA1 checksum for bytes.
Generate SHA1 checksum for bytes.
+ # "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+ sha1_sum 'foo';
+
=head2 slurp
my $bytes = slurp '/etc/passwd';
@@ -660,6 +684,9 @@ Write all data at once to file.
Trim whitespace characters from both ends of string and then change all
consecutive groups of whitespace into one space each.
+ # "foo bar"
+ squish ' foo bar ';
+
=head2 steady_time
my $time = steady_time;
@@ -677,18 +704,33 @@ Row-oriented generator for text tables.
# "foo bar\nyada yada\nbaz yada\n"
tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']];
+=head2 term_escape
+
+ my $escaped = term_escape $str;
+
+Escape all POSIX control characters except for C<\n>.
+
+ # "foo\\x09bar\\x0d\n"
+ term_escape "foo\tbar\r\n";
+
=head2 trim
my $trimmed = trim $str;
Trim whitespace characters from both ends of string.
+ # "foo bar"
+ trim ' foo bar ';
+
=head2 unindent
my $unindented = unindent $str;
Unindent multiline string.
+ # "foo\nbar\nbaz\n"
+ unindent " foo\n bar\n baz\n";
+
=head2 unquote
my $str = unquote $quoted;
@@ -704,6 +746,9 @@ Percent encode unsafe characters in string as described in
L<RFC 3986|http://tools.ietf.org/html/rfc3986>, the pattern used defaults to
C<^A-Za-z0-9\-._~>.
+ # "foo%3Bbar"
+ url_unescape 'foo;bar';
+
=head2 url_unescape
my $str = url_unescape $escaped;
@@ -711,12 +756,18 @@ C<^A-Za-z0-9\-._~>.
Decode percent encoded characters in string as described in
L<RFC 3986|http://tools.ietf.org/html/rfc3986>.
+ # "foo;bar"
+ url_unescape 'foo%3Bbar';
+
=head2 xml_escape
my $escaped = xml_escape $str;
Escape unsafe characters C<&>, C<E<lt>>, C<E<gt>>, C<"> and C<'> in string.
+ # "<div>"
+ xml_escape '<div>';
+
=head2 xor_encode
my $encoded = xor_encode $str, $key;
@@ -87,7 +87,7 @@ which stringifies to the actual path.
The logging layer of your application, defaults to a L<Mojo::Log> object.
# Log debug message
- $app->log->debug('It works!');
+ $app->log->debug('It works');
=head2 ua
@@ -124,6 +124,9 @@ Application configuration.
# Remove value
my $foo = delete $app->config->{foo};
+ # Assign multiple values at once
+ $app->config(foo => 'test', bar => 23);
+
=head2 handler
$app->handler(Mojo::Transaction::HTTP->new);
@@ -4,7 +4,7 @@ use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::Server::CGI;
-has description => 'Start application with CGI.';
+has description => 'Start application with CGI';
has usage => sub { shift->extract_usage };
sub run {
@@ -26,7 +26,7 @@ Mojolicious::Command::cgi - CGI command
Usage: APPLICATION cgi [OPTIONS]
Options:
- --nph Enable non-parsed-header mode.
+ --nph Enable non-parsed-header mode
=head1 DESCRIPTION
@@ -47,14 +47,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $cgi->description;
- $cgi = $cgi->description('Foo!');
+ $cgi = $cgi->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $cgi->usage;
- $cgi = $cgi->usage('Foo!');
+ $cgi = $cgi->usage('Foo');
Usage information for this command, used for the help screen.
@@ -5,7 +5,7 @@ use File::Basename 'basename';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::UserAgent;
-has description => 'Upload distribution to CPAN.';
+has description => 'Upload distribution to CPAN';
has usage => sub { shift->extract_usage };
sub run {
@@ -53,8 +53,8 @@ Mojolicious::Command::cpanify - Cpanify command
mojo cpanify -u sri -p secr3t Mojolicious-Plugin-MyPlugin-0.01.tar.gz
Options:
- -p, --password <password> PAUSE password.
- -u, --user <name> PAUSE username.
+ -p, --password <password> PAUSE password
+ -u, --user <name> PAUSE username
=head1 DESCRIPTION
@@ -74,14 +74,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $cpanify->description;
- $cpanify = $cpanify->description('Foo!');
+ $cpanify = $cpanify->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $cpanify->usage;
- $cpanify = $cpanify->usage('Foo!');
+ $cpanify = $cpanify->usage('Foo');
Usage information for this command, used for the help screen.
@@ -4,7 +4,7 @@ use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::Server::Daemon;
-has description => 'Start application with HTTP and WebSocket server.';
+has description => 'Start application with HTTP and WebSocket server';
has usage => sub { shift->extract_usage };
sub run {
@@ -43,20 +43,20 @@ Mojolicious::Command::daemon - Daemon command
./myapp.pl daemon -l 'https://*:443?cert=./server.crt&key=./server.key'
Options:
- -b, --backlog <size> Listen backlog size, defaults to SOMAXCONN.
+ -b, --backlog <size> Listen backlog size, defaults to SOMAXCONN
-c, --clients <number> Maximum number of concurrent clients,
- defaults to 1000.
- -g, --group <name> Group name for process.
+ defaults to 1000
+ -g, --group <name> Group name for process
-i, --inactivity <seconds> Inactivity timeout, defaults to the value of
- MOJO_INACTIVITY_TIMEOUT or 15.
+ MOJO_INACTIVITY_TIMEOUT or 15
-l, --listen <location> One or more locations you want to listen on,
defaults to the value of MOJO_LISTEN or
- "http://*:3000".
+ "http://*:3000"
-p, --proxy Activate reverse proxy support, defaults to
- the value of MOJO_REVERSE_PROXY.
+ the value of MOJO_REVERSE_PROXY
-r, --requests <number> Maximum number of requests per keep-alive
- connection, defaults to 25.
- -u, --user <name> Username for process.
+ connection, defaults to 25
+ -u, --user <name> Username for process
=head1 DESCRIPTION
@@ -77,14 +77,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $daemon->description;
- $daemon = $daemon->description('Foo!');
+ $daemon = $daemon->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $daemon->usage;
- $daemon = $daemon->usage('Foo!');
+ $daemon = $daemon->usage('Foo');
Usage information for this command, used for the help screen.
@@ -3,7 +3,7 @@ use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
-has description => 'Run code against application.';
+has description => 'Run code against application';
has usage => sub { shift->extract_usage };
sub run {
@@ -37,8 +37,8 @@ Mojolicious::Command::eval - Eval command
./myapp.pl eval -V 'app->renderer->paths'
Options:
- -v, --verbose Print return value to STDOUT.
- -V Print returned data structure to STDOUT.
+ -v, --verbose Print return value to STDOUT
+ -V Print returned data structure to STDOUT
=head1 DESCRIPTION
@@ -58,14 +58,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $eval->description;
- $eval = $eval->description('Foo!');
+ $eval = $eval->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $eval->usage;
- $eval = $eval->usage('Foo!');
+ $eval = $eval->usage('Foo');
Usage information for this command, used for the help screen.
@@ -3,7 +3,7 @@ use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(class_to_file class_to_path);
-has description => 'Generate Mojolicious application directory structure.';
+has description => 'Generate Mojolicious application directory structure';
has usage => sub { shift->extract_usage };
sub run {
@@ -77,14 +77,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $app->description;
- $app = $app->description('Foo!');
+ $app = $app->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $app->usage;
- $app = $app->usage('Foo!');
+ $app = $app->usage('Foo');
Usage information for this command, used for the help screen.
@@ -1,7 +1,7 @@
package Mojolicious::Command::generate::lite_app;
use Mojo::Base 'Mojolicious::Command';
-has description => 'Generate Mojolicious::Lite application.';
+has description => 'Generate Mojolicious::Lite application';
has usage => sub { shift->extract_usage };
sub run {
@@ -42,14 +42,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $app->description;
- $app = $app->description('Foo!');
+ $app = $app->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $app->usage;
- $app = $app->usage('Foo!');
+ $app = $app->usage('Foo');
Usage information for this command, used for the help screen.
@@ -81,7 +81,7 @@ plugin 'PODRenderer';
get '/' => sub {
my $c = shift;
- $c->render('index');
+ $c->render(template => 'index');
};
app->start;
@@ -3,7 +3,7 @@ use Mojo::Base 'Mojolicious::Command';
use Mojolicious;
-has description => 'Generate "Makefile.PL".';
+has description => 'Generate "Makefile.PL"';
has usage => sub { shift->extract_usage };
sub run { shift->render_to_rel_file('makefile', 'Makefile.PL') }
@@ -39,14 +39,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $makefile->description;
- $makefile = $makefile->description('Foo!');
+ $makefile = $makefile->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $makefile->usage;
- $makefile = $makefile->usage('Foo!');
+ $makefile = $makefile->usage('Foo');
Usage information for this command, used for the help screen.
@@ -4,7 +4,7 @@ use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(camelize class_to_path);
use Mojolicious;
-has description => 'Generate Mojolicious plugin directory structure.';
+has description => 'Generate Mojolicious plugin directory structure';
has usage => sub { shift->extract_usage };
sub run {
@@ -56,14 +56,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $plugin->description;
- $plugin = $plugin->description('Foo!');
+ $plugin = $plugin->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $plugin->usage;
- $plugin = $plugin->usage('Foo!');
+ $plugin = $plugin->usage('Foo');
Usage information for this command, used for the help screen.
@@ -1,7 +1,7 @@
package Mojolicious::Command::generate;
use Mojo::Base 'Mojolicious::Commands';
-has description => 'Generate files and directories from templates.';
+has description => 'Generate files and directories from templates';
has hint => <<EOF;
See 'APPLICATION generate help GENERATOR' for more information on a specific
@@ -42,21 +42,21 @@ L<Mojolicious::Commands> and implements the following new ones.
=head2 description
my $description = $generator->description;
- $generator = $generator->description('Foo!');
+ $generator = $generator->description('Foo');
Short description of this command, used for the command list.
=head2 hint
my $hint = $generator->hint;
- $generator = $generator->hint('Foo!');
+ $generator = $generator->hint('Foo');
Short hint shown after listing available generator commands.
=head2 message
my $msg = $generator->message;
- $generator = $generator->message('Bar!');
+ $generator = $generator->message('Bar');
Short usage message shown before listing available generator commands.
@@ -6,11 +6,10 @@ use Mojo::DOM;
use Mojo::IOLoop;
use Mojo::JSON qw(encode_json j);
use Mojo::JSON::Pointer;
-use Mojo::UserAgent;
use Mojo::Util qw(decode encode);
use Scalar::Util 'weaken';
-has description => 'Perform HTTP request.';
+has description => 'Perform HTTP request';
has usage => sub { shift->extract_usage };
sub run {
@@ -32,8 +31,9 @@ sub run {
my %headers = map { /^\s*([^:]+)\s*:\s*(.+)$/ ? ($1, $2) : () } @headers;
# Detect proxy for absolute URLs
- my $ua = Mojo::UserAgent->new(ioloop => Mojo::IOLoop->singleton);
- $url !~ m!^/! ? $ua->proxy->detect : $ua->server->app($self->app);
+ my $ua = $self->app->ua->ioloop(Mojo::IOLoop->singleton);
+ $ua->server->ioloop(Mojo::IOLoop->singleton);
+ $ua->proxy->detect unless $url =~ m!^/!;
$ua->max_redirects(10) if $redirect;
my $buffer = '';
@@ -143,12 +143,12 @@ Mojolicious::Command::get - Get command
Options:
-C, --charset <charset> Charset of HTML/XML content, defaults to auto
- detection.
- -c, --content <content> Content to send with request.
- -H, --header <name:value> Additional HTTP header.
- -M, --method <method> HTTP method to use, defaults to "GET".
- -r, --redirect Follow up to 10 redirects.
- -v, --verbose Print request and response headers to STDERR.
+ detection
+ -c, --content <content> Content to send with request
+ -H, --header <name:value> Additional HTTP header
+ -M, --method <method> HTTP method to use, defaults to "GET"
+ -r, --redirect Follow up to 10 redirects
+ -v, --verbose Print request and response headers to STDERR
=head1 DESCRIPTION
@@ -169,14 +169,14 @@ applications.
=head2 description
my $description = $get->description;
- $get = $get->description('Foo!');
+ $get = $get->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $get->usage;
- $get = $get->usage('Foo!');
+ $get = $get->usage('Foo');
Usage information for this command, used for the help screen.
@@ -4,7 +4,7 @@ use Mojo::Base 'Mojolicious::Command';
use Mojo::Loader;
use Mojo::Util 'encode';
-has description => 'Inflate embedded files to real files.';
+has description => 'Inflate embedded files to real files';
has usage => sub { shift->extract_usage };
sub run {
@@ -60,14 +60,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $inflate->description;
- $inflate = $inflate->description('Foo!');
+ $inflate = $inflate->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $inflate->usage;
- $inflate = $inflate->usage('Foo!');
+ $inflate = $inflate->usage('Foo');
Usage information for this command, used for the help screen.
@@ -5,7 +5,7 @@ use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::Server::Prefork;
has description =>
- 'Start application with preforking HTTP and WebSocket server.';
+ 'Start application with preforking HTTP and WebSocket server';
has usage => sub { shift->extract_usage };
sub run {
@@ -55,37 +55,37 @@ Mojolicious::Command::prefork - Prefork command
Options:
-A, --accepts <number> Number of connections for workers to
- accept, defaults to 1000.
- -a, --accept-interval <seconds> Accept interval, defaults to 0.025.
+ accept, defaults to 1000
+ -a, --accept-interval <seconds> Accept interval, defaults to 0.025
-b, --backlog <size> Listen backlog size, defaults to
- SOMAXCONN.
+ SOMAXCONN
-c, --clients <number> Maximum number of concurrent clients,
- defaults to 1000.
+ defaults to 1000
-G, --graceful-timeout <seconds> Graceful timeout, defaults to 20.
- -g, --group <name> Group name for process.
- --heartbeat-interval <seconds> Heartbeat interval, defaults to 5.
- -H, --heartbeat-timeout <seconds> Heartbeat timeout, defaults to 20.
+ -g, --group <name> Group name for process
+ --heartbeat-interval <seconds> Heartbeat interval, defaults to 5
+ -H, --heartbeat-timeout <seconds> Heartbeat timeout, defaults to 20
-i, --inactivity <seconds> Inactivity timeout, defaults to the
value of MOJO_INACTIVITY_TIMEOUT or
- 15.
+ 15
--lock-file <path> Path to lock file, defaults to a
- random file.
- -L, --lock-timeout <seconds> Lock timeout, defaults to 1.
+ random file
+ -L, --lock-timeout <seconds> Lock timeout, defaults to 1
-l, --listen <location> One or more locations you want to
listen on, defaults to the value of
- MOJO_LISTEN or "http://*:3000".
+ MOJO_LISTEN or "http://*:3000"
--multi-accept <number> Number of connection to accept at
- once, defaults to 50.
+ once, defaults to 50
-P, --pid-file <path> Path to process id file, defaults to
- a random file.
+ a random file
-p, --proxy Activate reverse proxy support,
defaults to the value of
- MOJO_REVERSE_PROXY.
+ MOJO_REVERSE_PROXY
-r, --requests <number> Maximum number of requests per
keep-alive connection, defaults to
- 25.
- -u, --user <name> Username for process.
- -w, --workers <number> Number of workers, defaults to 4.
+ 25
+ -u, --user <name> Username for process
+ -w, --workers <number> Number of workers, defaults to 4
=head1 DESCRIPTION
@@ -106,14 +106,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $prefork->description;
- $prefork = $prefork->description('Foo!');
+ $prefork = $prefork->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $prefork->usage;
- $prefork = $prefork->usage('Foo!');
+ $prefork = $prefork->usage('Foo');
Usage information for this command, used for the help screen.
@@ -3,7 +3,7 @@ use Mojo::Base 'Mojolicious::Command';
use Mojo::Server::PSGI;
-has description => 'Start application with PSGI.';
+has description => 'Start application with PSGI';
has usage => sub { shift->extract_usage };
sub run { Mojo::Server::PSGI->new(app => shift->app)->to_psgi_app }
@@ -39,14 +39,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $psgi->description;
- $psgi = $psgi->description('Foo!');
+ $psgi = $psgi->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $psgi->usage;
- $psgi = $psgi->usage('Foo!');
+ $psgi = $psgi->usage('Foo');
Usage information for this command, used for the help screen.
@@ -5,7 +5,7 @@ use re 'regexp_pattern';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
use Mojo::Util qw(encode tablify);
-has description => 'Show available routes.';
+has description => 'Show available routes';
has usage => sub { shift->extract_usage };
sub run {
@@ -68,7 +68,7 @@ Mojolicious::Command::routes - Routes command
Options:
-v, --verbose Print additional details about routes, flags indicate
- C=Conditions, D=Detour, U=Under and W=WebSocket.
+ C=Conditions, D=Detour, U=Under and W=WebSocket
=head1 DESCRIPTION
@@ -88,14 +88,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $routes->description;
- $routes = $routes->description('Foo!');
+ $routes = $routes->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $routes->usage;
- $routes = $routes->usage('Foo!');
+ $routes = $routes->usage('Foo');
Usage information for this command, used for the help screen.
@@ -3,7 +3,7 @@ use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
-has description => 'Run tests.';
+has description => 'Run tests';
has usage => sub { shift->extract_usage };
sub run {
@@ -40,7 +40,7 @@ Mojolicious::Command::test - Test command
./myapp.pl test -v t/foo/*.t
Options:
- -v, --verbose Print verbose debug information to STDERR.
+ -v, --verbose Print verbose debug information to STDERR
=head1 DESCRIPTION
@@ -60,14 +60,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $test->description;
- $test = $test->description('Foo!');
+ $test = $test->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $test->usage;
- $test = $test->usage('Foo!');
+ $test = $test->usage('Foo');
Usage information for this command, used for the help screen.
@@ -5,7 +5,7 @@ use Mojo::IOLoop::Client;
use Mojo::UserAgent;
use Mojolicious;
-has description => 'Show versions of available modules.';
+has description => 'Show versions of available modules';
has usage => sub { shift->extract_usage };
sub run {
@@ -39,7 +39,7 @@ EOF
my $msg = 'This version is up to date, have fun!';
$msg = 'Thanks for testing a development release, you are awesome!'
if $latest < $Mojolicious::VERSION;
- $msg = "You might want to update your Mojolicious to $latest."
+ $msg = "You might want to update your Mojolicious to $latest!"
if $latest > $Mojolicious::VERSION;
say $msg;
}
@@ -75,14 +75,14 @@ L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $v->description;
- $v = $v->description('Foo!');
+ $v = $v->description('Foo');
Short description of this command, used for the command list.
=head2 usage
my $usage = $v->usage;
- $v = $v->usage('Foo!');
+ $v = $v->usage('Foo');
Usage information for this command, used for the help screen.
@@ -13,7 +13,7 @@ use Mojo::Util qw(spurt unindent);
use Pod::Usage 'pod2usage';
has app => sub { Mojo::Server->new->build_app('Mojo::HelloWorld') };
-has description => 'No description.';
+has description => 'No description';
has 'quiet';
has usage => "Usage: APPLICATION\n";
@@ -99,14 +99,14 @@ Mojolicious::Command - Command base class
use Mojo::Base 'Mojolicious::Command';
# Short description
- has description => 'My first Mojo command.';
+ has description => 'My first Mojo command';
# Short usage message
has usage => <<EOF;
Usage: APPLICATION mycommand [OPTIONS]
Options:
- -s, --something Does something.
+ -s, --something Does something
EOF
sub run {
@@ -139,7 +139,7 @@ Application for command, defaults to a L<Mojo::HelloWorld> object.
=head2 description
my $description = $command->description;
- $command = $command->description('Foo!');
+ $command = $command->description('Foo');
Short description of command, used for the command list.
@@ -153,7 +153,7 @@ Limited command output.
=head2 usage
my $usage = $command->usage;
- $command = $command->usage('Foo!');
+ $command = $command->usage('Foo');
Usage information for command, used for the help screen.
@@ -13,7 +13,6 @@ has message => sub { shift->extract_usage . "\nCommands:\n" };
has namespaces => sub { ['Mojolicious::Command'] };
sub detect {
- my ($self, $guess) = @_;
# PSGI (Plack only for now)
return 'psgi' if defined $ENV{PLACK_ENV};
@@ -22,24 +21,9 @@ sub detect {
return 'cgi' if defined $ENV{PATH_INFO} || defined $ENV{GATEWAY_INTERFACE};
# Nothing
- return $guess;
+ return undef;
}
-# Command line options for MOJO_HELP, MOJO_HOME and MOJO_MODE
-sub _args {
- return if __PACKAGE__->detect;
-
- my $save
- = Getopt::Long::Configure(qw(no_auto_abbrev no_ignore_case pass_through));
- GetOptionsFromArray shift,
- 'h|help' => \$ENV{MOJO_HELP},
- 'home=s' => \$ENV{MOJO_HOME},
- 'm|mode=s' => \$ENV{MOJO_MODE};
- Getopt::Long::Configure($save);
-}
-
-BEGIN { _args([@ARGV]) }
-
sub run {
my ($self, $name, @args) = @_;
@@ -47,14 +31,14 @@ sub run {
return $self->app if defined $ENV{MOJO_APP_LOADER};
# Try to detect environment
- $name = $self->detect($name) unless $ENV{MOJO_NO_DETECT};
+ if (!$ENV{MOJO_NO_DETECT} && (my $env = $self->detect)) { $name = $env }
# Run command
if ($name && $name =~ /^\w+$/ && ($name ne 'help' || $args[0])) {
# Help
$name = shift @args if my $help = $name eq 'help';
- $help = $ENV{MOJO_HELP} = $ENV{MOJO_HELP} ? 1 : $help;
+ $help = $ENV{MOJO_HELP} ||= $help;
# Remove options shared by all commands before loading the command
_args(\@args);
@@ -91,6 +75,22 @@ sub run {
sub start_app { shift; Mojo::Server->new->build_app(shift)->start(@_) }
+# Command line options for MOJO_HELP, MOJO_HOME and MOJO_MODE
+sub _args {
+ return if __PACKAGE__->detect;
+
+ my $save
+ = Getopt::Long::Configure(qw(no_auto_abbrev no_ignore_case pass_through));
+ GetOptionsFromArray shift,
+ 'h|help' => \$ENV{MOJO_HELP},
+ 'home=s' => \$ENV{MOJO_HOME},
+ 'm|mode=s' => \$ENV{MOJO_MODE};
+ Getopt::Long::Configure($save);
+}
+
+# Do not remove options from @ARGV
+BEGIN { _args([@ARGV]) }
+
sub _command {
my ($module, $fatal) = @_;
return $module->isa('Mojolicious::Command') ? $module : undef
@@ -114,11 +114,11 @@ Mojolicious::Commands - Command line interface
work without commands.
Options (for all commands):
- -h, --help Get more information on a specific command.
+ -h, --help Get more information on a specific command
--home <path> Path to your applications home directory, defaults to
- the value of MOJO_HOME or auto detection.
+ the value of MOJO_HOME or auto detection
-m, --mode <name> Operating mode for your application, defaults to the
- value of MOJO_MODE/PLACK_ENV or "development".
+ value of MOJO_MODE/PLACK_ENV or "development"
=head1 DESCRIPTION
@@ -270,7 +270,7 @@ and implements the following new ones.
=head2 hint
my $hint = $commands->hint;
- $commands = $commands->hint('Foo!');
+ $commands = $commands->hint('Foo');
Short hint shown after listing available commands.
@@ -299,9 +299,8 @@ implements the following new ones.
=head2 detect
my $env = $commands->detect;
- my $env = $commands->detect($guess);
-Try to detect environment.
+Try to detect environment or return C<undef> if none could be detected.
=head2 run
@@ -316,9 +315,11 @@ disabled with the C<MOJO_NO_DETECT> environment variable.
Mojolicious::Commands->start_app('MyApp');
Mojolicious::Commands->start_app(MyApp => @ARGV);
-Load application from class and start the command line interface for it.
+Load application from class and start the command line interface for it. Note
+that the options C<-h>/C<--help>, C<--home> and C<-m>/C<--mode>, which are
+shared by all commands, will be parsed from C<@ARGV> during compile time.
- # Always start daemon for application and ignore @ARGV
+ # Always start daemon for application
Mojolicious::Commands->start_app('MyApp', 'daemon', '-l', 'http://*:8080');
=head1 SEE ALSO
@@ -49,7 +49,7 @@ sub cookie {
# Cookie too big
my $cookie = {name => $name, value => shift, %{shift || {}}};
- $self->app->log->error(qq{Cookie "$name" is bigger than 4096 bytes.})
+ $self->app->log->error(qq{Cookie "$name" is bigger than 4096 bytes})
if length $cookie->{value} > 4096;
$self->res->cookies($cookie);
@@ -65,9 +65,48 @@ sub every_cookie {
[map { $_->value } @{shift->req->every_cookie(shift)}];
}
-sub every_param { _param(@_) }
+sub every_param {
+ my ($self, $name) = @_;
+
+ # Captured unreserved values
+ my $captures = $self->stash->{'mojo.captures'} ||= {};
+ if (!$RESERVED{$name} && exists $captures->{$name}) {
+ my $value = $captures->{$name};
+ return ref $value eq 'ARRAY' ? $value : [$value];
+ }
+
+ # Uploads or param values
+ my $req = $self->req;
+ my $uploads = $req->every_upload($name);
+ return @$uploads ? $uploads : $req->every_param($name);
+}
+
+sub every_signed_cookie {
+ my ($self, $name) = @_;
-sub every_signed_cookie { _signed_cookie(@_) }
+ my $secrets = $self->stash->{'mojo.secrets'};
+ my @results;
+ for my $value (@{$self->every_cookie($name)}) {
+
+ # Check signature with rotating secrets
+ if ($value =~ s/--([^\-]+)$//) {
+ my $signature = $1;
+
+ my $valid;
+ for my $secret (@$secrets) {
+ my $check = Mojo::Util::hmac_sha1_sum($value, $secret);
+ ++$valid and last if Mojo::Util::secure_compare($signature, $check);
+ }
+ if ($valid) { push @results, $value }
+
+ else { $self->app->log->debug(qq{Cookie "$name" has a bad signature}) }
+ }
+
+ else { $self->app->log->debug(qq{Cookie "$name" is not signed}) }
+ }
+
+ return \@results;
+}
sub finish {
my $self = shift;
@@ -116,17 +155,17 @@ sub param {
# List names
my $captures = $self->stash->{'mojo.captures'} ||= {};
- my $req = $self->req;
unless (defined $name) {
+ my $req = $self->req;
+ my @keys = $req->param;
+ push @keys, map { $_->name } @{$req->uploads};
+ push @keys, grep { !$RESERVED{$_} } keys %$captures;
my %seen;
- my @keys = grep { !$seen{$_}++ } $req->param;
- push @keys, grep { !$seen{$_}++ } map { $_->name } @{$req->uploads};
- push @keys, grep { !$RESERVED{$_} && !$seen{$_}++ } keys %$captures;
- return sort @keys;
+ return sort grep { !$seen{$_}++ } @keys;
}
# Value
- return _param($self, $name)->[-1] unless @_;
+ return $self->every_param($name)->[-1] unless @_;
# Override values
$captures->{$name} = @_ > 1 ? [@_] : $_[0];
@@ -158,7 +197,8 @@ sub render {
return defined $output ? Mojo::ByteStream->new($output) : undef if $ts;
# Maybe
- return $maybe ? undef : !$self->render_not_found unless defined $output;
+ return $maybe ? undef : !$self->helpers->reply->not_found
+ unless defined $output;
# Prepare response
$plugins->emit_hook(after_render => $self, \$output, $format);
@@ -168,14 +208,10 @@ sub render {
return !!$self->rendered($self->stash->{status});
}
-sub render_exception { shift->helpers->reply->exception(@_) }
-
sub render_later { shift->stash('mojo.rendered' => 1) }
sub render_maybe { shift->render(@_, 'mojo.maybe' => 1) }
-sub render_not_found { shift->helpers->reply->not_found }
-
sub render_to_string { shift->render(@_, 'mojo.to_string' => 1) }
sub rendered {
@@ -197,7 +233,7 @@ sub rendered {
my $rps = $elapsed == 0 ? '??' : sprintf '%.3f', 1 / $elapsed;
my $code = $res->code;
my $msg = $res->message || $res->default_message($code);
- $app->log->debug("$code $msg (${elapsed}s, $rps/s).");
+ $app->log->debug("$code $msg (${elapsed}s, $rps/s)");
}
$app->plugins->emit_hook_reverse(after_dispatch => $self);
@@ -272,7 +308,7 @@ sub signed_cookie {
return map { $self->signed_cookie($_) } @$name if ref $name eq 'ARRAY';
# Request cookie
- return _signed_cookie($self, $name)->[-1] unless defined $value;
+ return $self->every_signed_cookie($name)->[-1] unless defined $value;
# Response cookie
my $checksum
@@ -352,48 +388,6 @@ sub write_chunk {
return $self->rendered;
}
-sub _param {
- my ($self, $name) = @_;
-
- # Captured unreserved values
- my $captures = $self->stash->{'mojo.captures'} ||= {};
- if (!$RESERVED{$name} && defined(my $value = $captures->{$name})) {
- return ref $value eq 'ARRAY' ? $value : [$value];
- }
-
- # Uploads or param values
- my $req = $self->req;
- my $uploads = $req->every_upload($name);
- return @$uploads ? $uploads : $req->every_param($name);
-}
-
-sub _signed_cookie {
- my ($self, $name) = @_;
-
- my $secrets = $self->stash->{'mojo.secrets'};
- my @results;
- for my $value (@{$self->every_cookie($name)}) {
-
- # Check signature with rotating secrets
- if ($value =~ s/--([^\-]+)$//) {
- my $signature = $1;
-
- my $valid;
- for my $secret (@$secrets) {
- my $check = Mojo::Util::hmac_sha1_sum($value, $secret);
- ++$valid and last if Mojo::Util::secure_compare($signature, $check);
- }
- if ($valid) { push @results, $value }
-
- else { $self->app->log->debug(qq{Cookie "$name" has bad signature.}) }
- }
-
- else { $self->app->log->debug(qq{Cookie "$name" not signed.}) }
- }
-
- return \@results;
-}
-
1;
=encoding utf8
@@ -436,7 +430,7 @@ A reference back to the application that dispatched to this controller,
defaults to a L<Mojolicious> object.
# Use application logger
- $c->app->log->debug('Hello Mojo!');
+ $c->app->log->debug('Hello Mojo');
# Generate path
my $path = $c->app->home->rel_file('templates/foo/bar.html.ep');
@@ -471,7 +465,7 @@ underlying connection might get closed early.
# Perform non-blocking operation without knowing the connection status
my $tx = $c->tx;
Mojo::IOLoop->timer(2 => sub {
- $c->app->log->debug($tx->is_finished ? 'Finished.' : 'In progress.');
+ $c->app->log->debug($tx->is_finished ? 'Finished' : 'In progress');
});
=head1 METHODS
@@ -562,6 +556,9 @@ L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
# Make sure to use the "title" helper and not the controller method
$c->helpers->title('Welcome!');
+ # Use a nested helper instead of the "reply" controller method
+ $c->helpers->reply->not_found;
+
=head2 on
my $cb = $c->on(finish => sub {...});
@@ -574,7 +571,7 @@ status.
# Do something after the transaction has been finished
$c->on(finish => sub {
my $c = shift;
- $c->app->log->debug('We are done!');
+ $c->app->log->debug('We are done');
});
# Receive WebSocket message
@@ -593,7 +590,7 @@ status.
$c->on(binary => sub {
my ($c, $bytes) = @_;
my $len = length $bytes;
- $c->app->log->debug("Received $len bytes.");
+ $c->app->log->debug("Received $len bytes");
});
=head2 param
@@ -672,22 +669,21 @@ L</"stash">.
# Render JSON
$c->render(json => {test => 'I ♥ Mojolicious!'});
+ # Render inline template
+ $c->render(inline => '<%= 1 + 1 %>');
+
# Render template "foo/bar.html.ep"
$c->render(template => 'foo/bar', format => 'html', handler => 'ep');
# Render template "foo/bar.*.*"
$c->render(template => 'foo/bar');
+ # Render template "test.*.*" with arbitrary values "foo" and "bar"
+ $c->render(template => 'test', foo => 'test', bar => 23);
+
# Render template "test.xml.*"
$c->render('test', format => 'xml');
-=head2 render_exception
-
- $c = $c->render_exception('Oops!');
- $c = $c->render_exception(Mojo::Exception->new('Oops!'));
-
-Alias for L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>exception">.
-
=head2 render_later
$c = $c->render_later;
@@ -714,12 +710,6 @@ could be generated, takes the same arguments as L</"render">.
# Render template "index_local" only if it exists
$c->render_maybe('index_local') or $c->render('index');
-=head2 render_not_found
-
- $c = $c->render_not_found;
-
-Alias for L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>not_found">.
-
=head2 render_to_string
my $output = $c->render_to_string('foo/index', format => 'pdf');
@@ -729,6 +719,9 @@ return C<undef>, all arguments get localized automatically and are only
available during this render operation, takes the same arguments as
L</"render">.
+ # Render inline template
+ my $two = $c->render_to_string(inline => '<%= 1 + 1 %>');
+
=head2 rendered
$c = $c->rendered;
@@ -779,6 +772,10 @@ Get L<Mojo::Message::Response> object from L</"tx">.
# Force file download by setting a custom response header
$c->res->headers->content_disposition('attachment; filename=foo.png;');
+ # Make sure response is cached correctly
+ $c->res->headers->cache_control('public, max-age=300');
+ $c->res->headers->append(Vary => 'Accept-Encoding');
+
=head2 respond_to
$c = $c->respond_to(
@@ -897,6 +894,9 @@ reserved for internal use.
# Remove value
my $foo = delete $c->stash->{foo};
+ # Assign multiple values at once
+ $c->stash(foo => 'test', bar => 23);
+
=head2 url_for
my $url = $c->url_for;
@@ -24,7 +24,7 @@ in the construction of more advanced web servers, but is solid and fast enough
for small to mid sized applications.
$ ./script/my_app daemon
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
It is available to every application through the command
L<Mojolicious::Command::daemon>, which has many configuration options and is
@@ -40,14 +40,14 @@ works, but you can specify all listen locations supported by
L<Mojo::Server::Daemon/"listen">.
$ ./script/my_app daemon -l https://[::]:3000
- Server available at https://[::]:3000.
+ Server available at https://[::]:3000
On UNIX platforms you can also add preforking and switch to a multi-process
architecture with L<Mojolicious::Command::prefork>, to take advantage of
multiple CPU cores and copy-on-write memory management.
$ ./script/my_app prefork
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
Since all built-in web servers are based on the L<Mojo::IOLoop> event loop,
they scale best with non-blocking operations. But if your application for some
@@ -57,7 +57,7 @@ concurrent connections each worker is allowed to handle (often as low as
C<1>).
$ ./script/my_app prefork -m production -w 10 -c 1
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
Your application is preloaded in the manager process during startup, to run
code whenever a new worker process has been forked you can use
@@ -75,7 +75,7 @@ L<Mojo::IOLoop/"next_tick">.
=head2 Morbo
-After reading the L<Mojolicious::Lite> tutorial, you should already be
+After reading the L<Mojolicious::Guides::Tutorial>, you should already be
familiar with L<Mojo::Server::Morbo>.
Mojo::Server::Morbo
@@ -86,7 +86,7 @@ server whenever a file in your project changes, and should therefore only be
used during development.
$ morbo ./script/my_app
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
=head2 Hypnotoad
@@ -106,11 +106,11 @@ to L<Mojo::Server::Daemon>, but optimized specifically for production
environments out of the box.
$ hypnotoad ./script/my_app
- Server available at http://127.0.0.1:8080.
-It automatically sets the operating mode to C<production> and you can tweak
-many configuration settings right from within your application with
-L<Mojo/"config">, for a full list see L<Mojo::Server::Hypnotoad/"SETTINGS">.
+It automatically listens on port C<8080>, sets the operating mode to
+C<production> and you can tweak many configuration settings right from within
+your application with L<Mojo/"config">, for a full list see
+L<Mojo::Server::Hypnotoad/"SETTINGS">.
use Mojolicious::Lite;
@@ -154,13 +154,13 @@ C<SO_REUSEPORT> socket option, there is also another method available that
works with all built-in web servers.
$ ./script/my_app prefork -P /tmp/first.pid -l http://*:8080?reuse=1
- Server available at http://127.0.0.1:8080.
+ Server available at http://127.0.0.1:8080
All you have to do is start a second web server listening to the same port and
stop the first web server gracefully afterwards.
$ ./script/my_app prefork -P /tmp/second.pid -l http://*:8080?reuse=1
- Server available at http://127.0.0.1:8080.
+ Server available at http://127.0.0.1:8080
$ kill -s TERM `cat /tmp/first.pid`
Just remember that both web servers need to be started with the C<reuse>
@@ -583,7 +583,7 @@ L<Mojolicious::Controller/"send">.
my $c = shift;
# Opened
- $c->app->log->debug('WebSocket opened.');
+ $c->app->log->debug('WebSocket opened');
# Increase inactivity timeout for connection a bit
$c->inactivity_timeout(300);
@@ -597,7 +597,7 @@ L<Mojolicious::Controller/"send">.
# Closed
$c->on(finish => sub {
my ($c, $code, $reason) = @_;
- $c->app->log->debug("WebSocket closed with status $code.");
+ $c->app->log->debug("WebSocket closed with status $code");
});
};
@@ -755,7 +755,7 @@ which can be combined to solve some of hardest problems in web development.
my ($single, $bytes) = @_;
# Log size of every chunk we receive
- app->log->debug(length($bytes) . ' bytes uploaded.');
+ app->log->debug(length($bytes) . ' bytes uploaded');
});
});
});
@@ -1253,7 +1253,7 @@ the helper L<Mojolicious::Plugin::DefaultHelpers/"config">
plugin 'Config';
my $name = app->config('name');
- app->log->debug("Welcome to $name.");
+ app->log->debug("Welcome to $name");
get '/' => 'with_config';
@@ -1324,7 +1324,7 @@ that they will be picked up automatically by the command line interface?
package Mojolicious::Command::spy;
use Mojo::Base 'Mojolicious::Command';
- has description => 'Spy on application.';
+ has description => 'Spy on application';
has usage => "Usage: APPLICATION spy [TARGET]\n";
sub run {
@@ -1365,8 +1365,8 @@ L<Mojolicious::Commands/"namespaces">.
1;
-Some options like C<-m> for the operating mode of your application are shared
-by all commands automatically.
+The options C<-h>/C<--help>, C<--home> and C<-m>/C<--mode> are handled
+automatically by L<Mojolicious::Commands> and are shared by all commands.
$ ./myapp.pl spy -m production mode
production
@@ -78,7 +78,7 @@ Mojolicious uses many environment variables both internally and externally,
notably (but not exclusively) those starting with the prefix C<MOJO_*>. The
test suite expects a clean environment; testing with a non-standard
environment is unsupported and is unlikely to succeed. Therefore when
-installing or upgrading Mojolicious and when running its tests, we highly
+installing or upgrading L<Mojolicious> and when running its tests, we highly
recommend using an environment which does not set these variables.
=head2 What is the difference between blocking and non-blocking operations?
@@ -198,7 +198,7 @@ The built-in development web server makes working on your application a lot of
fun thanks to automatic reloading.
$ morbo ./myapp.pl
- Server available at http://127.0.0.1:3000.
+ Server available at http://127.0.0.1:3000
Just save your changes and they will be automatically in effect the next time
you refresh your browser.
@@ -858,7 +858,7 @@ to make styling with CSS easier.
<label class="field-with-error" for="user">
Username (required, only characters e-t)
</label>
- <input class="field-with-error" type="text" name="user" value="sri" />
+ <input class="field-with-error" type="text" name="user" value="sri">
For a full list of available checks see also
L<Mojolicious::Validator/"CHECKS">.
@@ -989,7 +989,7 @@ L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>static">.
Most response content, static as well as dynamic, gets served through
L<Mojo::Asset::File> and L<Mojo::Asset::Memory> objects. For somewhat static
-content, like cached JSON data or temporary file, you can create your own and
+content, like cached JSON data or temporary files, you can create your own and
use the helper L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>asset"> to
serve them while allowing content negotiation to be performed with C<Range>,
C<If-Modified-Since> and C<If-None-Match> headers.
@@ -1046,8 +1046,8 @@ helper to the application you can use L<Mojolicious/"helper">.
get '/' => sub {
my $c = shift;
- $c->debug('It works.');
- $c->render(text => 'Hello.');
+ $c->debug('It works!');
+ $c->render(text => 'Hello!');
};
app->start;
@@ -1080,8 +1080,9 @@ plugins, even if you plan to release them to CPAN.
$ mkdir templates
$ echo '%= javascript "/alertassets.js"' > templates/alertassets.html.ep
-Just append their respective directories to the list of search paths when
-C<register> is called.
+Just give them reasonably unique names, ideally based on the name of your
+plugin, and append their respective directories to the list of search paths
+when C<register> is called.
package Mojolicious::Plugin::AlertAssets;
use Mojo::Base 'Mojolicious::Plugin';
@@ -23,7 +23,8 @@ requests with code generating the appropriate response.
This black box is usually called a dispatcher. There are many implementations
using different strategies to establish these connections, but pretty much all
-are based around mapping the requests path to some kind of response generator.
+are based around mapping the path part of the request URL to some kind of
+response generator.
/user/show/2 -> $c->render(text => 'Daniel');
/user/show/3 -> $c->render(text => 'Sara');
@@ -0,0 +1,951 @@
+
+=encoding utf8
+
+=head1 NAME
+
+Mojolicious::Guides::Tutorial - Tutorial
+
+=head1 TUTORIAL
+
+A quick example driven introduction to the wonders of L<Mojolicious::Lite>.
+Almost everything you'll learn here also applies to full L<Mojolicious>
+applications.
+
+=head2 Hello World
+
+A simple Hello World application can look like this, L<strict>, L<warnings>,
+L<utf8> and Perl 5.10 features are automatically enabled and a few functions
+imported when you use L<Mojolicious::Lite>, turning your script into a full
+featured web application.
+
+ #!/usr/bin/env perl
+ use Mojolicious::Lite;
+
+ get '/' => sub {
+ my $c = shift;
+ $c->render(text => 'Hello World!');
+ };
+
+ app->start;
+
+There is also a helper command to generate a small example application.
+
+ $ mojo generate lite_app myapp.pl
+
+=head2 Commands
+
+All the normal L<Mojolicious::Commands> are available from the command line.
+Note that CGI and L<PSGI> environments can usually be auto detected and will
+just work without commands.
+
+ $ ./myapp.pl daemon
+ Server available at http://127.0.0.1:3000
+
+ $ ./myapp.pl daemon -l http://*:8080
+ Server available at http://127.0.0.1:8080
+
+ $ ./myapp.pl cgi
+ ...CGI output...
+
+ $ ./myapp.pl get /
+ Hello World!
+
+ $ ./myapp.pl
+ ...List of available commands (or automatically detected environment)...
+
+The C<app-E<gt>start> call that starts the L<Mojolicious> command system
+should usually be the last expression in your application and can be
+customized to override normal C<@ARGV> use.
+
+ app->start('daemon');
+
+=head2 Reloading
+
+Your application will automatically reload itself if you start it with the
+C<morbo> development web server, so you don't have to restart the server after
+every change.
+
+ $ morbo ./myapp.pl
+ Server available at http://127.0.0.1:3000
+
+For more information about how to deploy your application see also
+L<Mojolicious::Guides::Cookbook/"DEPLOYMENT">.
+
+=head2 Routes
+
+Routes are basically just fancy paths that can contain different kinds of
+placeholders and usually lead to an action, if they match the path part of the
+request URL. The first argument passed to all actions C<$c> is a
+L<Mojolicious::Controller> object containing both the HTTP request and
+response.
+
+ use Mojolicious::Lite;
+
+ # Route leading to an action that renders some text
+ get '/foo' => sub {
+ my $c = shift;
+ $c->render(text => 'Hello World!');
+ };
+
+ app->start;
+
+Response content is often generated by actions with
+L<Mojolicious::Controller/"render">, but more about that later.
+
+=head2 GET/POST parameters
+
+All C<GET> and C<POST> parameters sent with the request are accessible via
+L<Mojolicious::Controller/"param">.
+
+ use Mojolicious::Lite;
+
+ # /foo?user=sri
+ get '/foo' => sub {
+ my $c = shift;
+ my $user = $c->param('user');
+ $c->render(text => "Hello $user.");
+ };
+
+ app->start;
+
+=head2 Stash and templates
+
+The L<Mojolicious::Controller/"stash"> is used to pass data to templates,
+which can be inlined in the C<DATA> section. A few stash values like
+C<template> and C<text> are reserved and will be used by
+L<Mojolicious::Controller/"render"> to decide how a response should be
+generated.
+
+ use Mojolicious::Lite;
+
+ # Route leading to an action that renders a template
+ get '/foo' => sub {
+ my $c = shift;
+ $c->stash(one => 23);
+ $c->render(template => 'magic', two => 24);
+ };
+
+ app->start;
+ __DATA__
+
+ @@ magic.html.ep
+ The magic numbers are <%= $one %> and <%= $two %>.
+
+For more information about templates see also
+L<Mojolicious::Guides::Rendering/"Embedded Perl">.
+
+=head2 HTTP
+
+L<Mojolicious::Controller/"req"> and L<Mojolicious::Controller/"res"> give you
+full access to all HTTP features and information.
+
+ use Mojolicious::Lite;
+
+ # Access request information
+ get '/agent' => sub {
+ my $c = shift;
+ my $host = $c->req->url->to_abs->host;
+ my $ua = $c->req->headers->user_agent;
+ $c->render(text => "Request by $ua reached $host.");
+ };
+
+ # Echo the request body and send custom header with response
+ post '/echo' => sub {
+ my $c = shift;
+ $c->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
+ $c->render(data => $c->req->body);
+ };
+
+ app->start;
+
+You can test the more advanced examples right from the command line with
+L<Mojolicious::Command::get>.
+
+ $ ./myapp.pl get -v -M POST -c 'test' /echo
+
+=head2 Built-in C<exception> and C<not_found> pages
+
+During development you will encounter these pages whenever you make a mistake,
+they are gorgeous and contain a lot of valuable information that will aid you
+in debugging your application.
+
+ use Mojolicious::Lite;
+
+ # Not found (404)
+ get '/missing' => sub { shift->render(template => 'does_not_exist') };
+
+ # Exception (500)
+ get '/dies' => sub { die 'Intentional error' };
+
+ app->start;
+
+You can even use CSS selectors with L<Mojolicious::Command::get> to extract
+only the information you're actually interested in.
+
+ $ ./myapp.pl get /dies '#error'
+
+=head2 Route names
+
+All routes can have a name associated with them, this allows automatic
+template detection and backreferencing with
+L<Mojolicious::Controller/"url_for">, on which many methods and helpers like
+L<Mojolicious::Plugin::TagHelpers/"link_to"> rely.
+
+ use Mojolicious::Lite;
+
+ # Render the template "index.html.ep"
+ get '/' => sub {
+ my $c = shift;
+ $c->render;
+ } => 'index';
+
+ # Render the template "hello.html.ep"
+ get '/hello';
+
+ app->start;
+ __DATA__
+
+ @@ index.html.ep
+ <%= link_to Hello => 'hello' %>.
+ <%= link_to Reload => 'index' %>.
+
+ @@ hello.html.ep
+ Hello World!
+
+Nameless routes get an automatically generated one assigned that is simply
+equal to the route itself without non-word characters.
+
+=head2 Layouts
+
+Templates can have layouts too, you just select one with the helper
+L<Mojolicious::Plugin::DefaultHelpers/"layout"> and place the result of the
+current template with the helper
+L<Mojolicious::Plugin::DefaultHelpers/"content">.
+
+ use Mojolicious::Lite;
+
+ get '/with_layout';
+
+ app->start;
+ __DATA__
+
+ @@ with_layout.html.ep
+ % title 'Green';
+ % layout 'green';
+ Hello World!
+
+ @@ layouts/green.html.ep
+ <!DOCTYPE html>
+ <html>
+ <head><title><%= title %></title></head>
+ <body><%= content %></body>
+ </html>
+
+The stash or helpers like L<Mojolicious::Plugin::DefaultHelpers/"title"> can
+be used to pass additional data to the layout.
+
+=head2 Blocks
+
+Template blocks can be used like normal Perl functions and are always
+delimited by the C<begin> and C<end> keywords, they are the foundation for
+many helpers.
+
+ use Mojolicious::Lite;
+
+ get '/with_block' => 'block';
+
+ app->start;
+ __DATA__
+
+ @@ block.html.ep
+ % my $link = begin
+ % my ($url, $name) = @_;
+ Try <%= link_to $url => begin %><%= $name %><% end %>.
+ % end
+ <!DOCTYPE html>
+ <html>
+ <head><title>Sebastians frameworks</title></head>
+ <body>
+ %= $link->('http://mojolicio.us', 'Mojolicious')
+ %= $link->('http://catalystframework.org', 'Catalyst')
+ </body>
+ </html>
+
+=head2 Helpers
+
+Helpers are little functions you can reuse throughout your whole application,
+from actions to templates.
+
+ use Mojolicious::Lite;
+
+ # A helper to identify visitors
+ helper whois => sub {
+ my $c = shift;
+ my $agent = $c->req->headers->user_agent || 'Anonymous';
+ my $ip = $c->tx->remote_address;
+ return "$agent ($ip)";
+ };
+
+ # Use helper in action and template
+ get '/secret' => sub {
+ my $c = shift;
+ my $user = $c->whois;
+ $c->app->log->debug("Request from $user");
+ };
+
+ app->start;
+ __DATA__
+
+ @@ secret.html.ep
+ We know who you are <%= whois %>.
+
+A list of all built-in ones can be found in
+L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
+
+=head2 Placeholders
+
+Route placeholders allow capturing parts of a request path until a C</> or
+C<.> separator occurs, results are accessible via
+L<Mojolicious::Controller/"stash"> and L<Mojolicious::Controller/"param">.
+
+ use Mojolicious::Lite;
+
+ # /foo/test
+ # /foo/test123
+ get '/foo/:bar' => sub {
+ my $c = shift;
+ my $bar = $c->stash('bar');
+ $c->render(text => "Our :bar placeholder matched $bar");
+ };
+
+ # /testsomething/foo
+ # /test123something/foo
+ get '/(:bar)something/foo' => sub {
+ my $c = shift;
+ my $bar = $c->param('bar');
+ $c->render(text => "Our :bar placeholder matched $bar");
+ };
+
+ app->start;
+
+=head2 Relaxed Placeholders
+
+Relaxed placeholders allow matching of everything until a C</> occurs.
+
+ use Mojolicious::Lite;
+
+ # /test/hello
+ # /test123/hello
+ # /test.123/hello
+ get '/#you/hello' => 'groovy';
+
+ app->start;
+ __DATA__
+
+ @@ groovy.html.ep
+ Your name is <%= $you %>.
+
+=head2 Wildcard placeholders
+
+Wildcard placeholders allow matching absolutely everything, including C</> and
+C<.>.
+
+ use Mojolicious::Lite;
+
+ # /hello/test
+ # /hello/test123
+ # /hello/test.123/test/123
+ get '/hello/*you' => 'groovy';
+
+ app->start;
+ __DATA__
+
+ @@ groovy.html.ep
+ Your name is <%= $you %>.
+
+=head2 HTTP methods
+
+Routes can be restricted to specific request methods with different keywords.
+
+ use Mojolicious::Lite;
+
+ # GET /hello
+ get '/hello' => sub {
+ my $c = shift;
+ $c->render(text => 'Hello World!');
+ };
+
+ # PUT /hello
+ put '/hello' => sub {
+ my $c = shift;
+ my $size = length $c->req->body;
+ $c->render(text => "You uploaded $size bytes to /hello.");
+ };
+
+ # GET|POST|PATCH /bye
+ any [qw(GET POST PATCH)] => '/bye' => sub {
+ my $c = shift;
+ $c->render(text => 'Bye World!');
+ };
+
+ # * /whatever
+ any '/whatever' => sub {
+ my $c = shift;
+ my $method = $c->req->method;
+ $c->render(text => "You called /whatever with $method.");
+ };
+
+ app->start;
+
+=head2 Optional placeholders
+
+All placeholders require a value, but by assigning them default values you can
+make capturing optional.
+
+ use Mojolicious::Lite;
+
+ # /hello
+ # /hello/Sara
+ get '/hello/:name' => {name => 'Sebastian', day => 'Monday'} => sub {
+ my $c = shift;
+ $c->render(template => 'groovy', format => 'txt');
+ };
+
+ app->start;
+ __DATA__
+
+ @@ groovy.txt.ep
+ My name is <%= $name %> and it is <%= $day %>.
+
+Default values that don't belong to a placeholder simply get merged into the
+stash all the time.
+
+=head2 Restrictive placeholders
+
+The easiest way to make placeholders more restrictive are alternatives, you
+just make a list of possible values.
+
+ use Mojolicious::Lite;
+
+ # /test
+ # /123
+ any '/:foo' => [foo => [qw(test 123)]] => sub {
+ my $c = shift;
+ my $foo = $c->param('foo');
+ $c->render(text => "Our :foo placeholder matched $foo");
+ };
+
+ app->start;
+
+All placeholders get compiled to a regular expression internally, this process
+can also be easily customized.
+
+ use Mojolicious::Lite;
+
+ # /1
+ # /123
+ any '/:bar' => [bar => qr/\d+/] => sub {
+ my $c = shift;
+ my $bar = $c->param('bar');
+ $c->render(text => "Our :bar placeholder matched $bar");
+ };
+
+ app->start;
+
+Just make sure not to use C<^> and C<$> or capturing groups C<(...)>, because
+placeholders become part of a larger regular expression internally, C<(?:...)>
+is fine though.
+
+=head2 Under
+
+Authentication and code shared between multiple routes can be realized easily
+with routes generated by L<Mojolicious::Lite/"under">. All following routes
+are only evaluated if the callback returned a true value.
+
+ use Mojolicious::Lite;
+
+ # Authenticate based on name parameter
+ under sub {
+ my $c = shift;
+
+ # Authenticated
+ my $name = $c->param('name') || '';
+ return 1 if $name eq 'Bender';
+
+ # Not authenticated
+ $c->render(template => 'denied');
+ return undef;
+ };
+
+ # Only reached when authenticated
+ get '/' => 'index';
+
+ app->start;
+ __DATA__
+
+ @@ denied.html.ep
+ You are not Bender, permission denied.
+
+ @@ index.html.ep
+ Hi Bender.
+
+Prefixing multiple routes is another good use for it.
+
+ use Mojolicious::Lite;
+
+ # /foo
+ under '/foo';
+
+ # /foo/bar
+ get '/bar' => {text => 'foo bar'};
+
+ # /foo/baz
+ get '/baz' => {text => 'foo baz'};
+
+ # / (reset)
+ under '/' => {msg => 'whatever'};
+
+ # /bar
+ get '/bar' => {inline => '<%= $msg %> works'};
+
+ app->start;
+
+You can also group related routes with L<Mojolicious::Lite/"group">, which
+allows nesting of routes generated with L<Mojolicious::Lite/"under">.
+
+ use Mojolicious::Lite;
+
+ # Global logic shared by all routes
+ under sub {
+ my $c = shift;
+ return 1 if $c->req->headers->header('X-Bender');
+ $c->render(text => "You're not Bender.");
+ return undef;
+ };
+
+ # Admin section
+ group {
+
+ # Local logic shared only by routes in this group
+ under '/admin' => sub {
+ my $c = shift;
+ return 1 if $c->req->headers->header('X-Awesome');
+ $c->render(text => "You're not awesome enough.");
+ return undef;
+ };
+
+ # GET /admin/dashboard
+ get '/dashboard' => {text => 'Nothing to see here yet.'};
+ };
+
+ # GET /welcome
+ get '/welcome' => {text => 'Hi Bender.'};
+
+ app->start;
+
+=head2 Formats
+
+Formats can be automatically detected from file extensions, they are used to
+find the right template and generate the correct C<Content-Type> header.
+
+ use Mojolicious::Lite;
+
+ # /detection
+ # /detection.html
+ # /detection.txt
+ get '/detection' => sub {
+ my $c = shift;
+ $c->render(template => 'detected');
+ };
+
+ app->start;
+ __DATA__
+
+ @@ detected.html.ep
+ <!DOCTYPE html>
+ <html>
+ <head><title>Detected</title></head>
+ <body>HTML was detected.</body>
+ </html>
+
+ @@ detected.txt.ep
+ TXT was detected.
+
+The default format is C<html>, restrictive placeholders can be used to limit
+possible values.
+
+ use Mojolicious::Lite;
+
+ # /hello.json
+ # /hello.txt
+ get '/hello' => [format => [qw(json txt)]] => sub {
+ my $c = shift;
+ return $c->render(json => {hello => 'world'})
+ if $c->stash('format') eq 'json';
+ $c->render(text => 'hello world');
+ };
+
+ app->start;
+
+Or you can just disable format detection.
+
+ use Mojolicious::Lite;
+
+ # /hello
+ get '/hello' => [format => 0] => {text => 'No format detection.'};
+
+ # Disable detection and allow the following routes selective re-enabling
+ under [format => 0];
+
+ # /foo
+ get '/foo' => {text => 'No format detection again.'};
+
+ # /bar.txt
+ get '/bar' => [format => 'txt'] => {text => ' Just one format.'};
+
+ app->start;
+
+=head2 Content negotiation
+
+For resources with different representations and that require truly RESTful
+content negotiation you can also use L<Mojolicious::Controller/"respond_to">.
+
+ use Mojolicious::Lite;
+
+ # /hello (Accept: application/json)
+ # /hello (Accept: application/xml)
+ # /hello.json
+ # /hello.xml
+ # /hello?format=json
+ # /hello?format=xml
+ get '/hello' => sub {
+ my $c = shift;
+ $c->respond_to(
+ json => {json => {hello => 'world'}},
+ xml => {text => '<hello>world</hello>'},
+ any => {data => '', status => 204}
+ );
+ };
+
+ app->start;
+
+MIME type mappings can be extended or changed easily with
+L<Mojolicious/"types">.
+
+ app->types->type(rdf => 'application/rdf+xml');
+
+=head2 Static files
+
+Similar to templates, but with only a single file extension and optional
+Base64 encoding, static files can be inlined in the C<DATA> section and are
+served automatically.
+
+ use Mojolicious::Lite;
+
+ app->start;
+ __DATA__
+
+ @@ something.js
+ alert('hello!');
+
+ @@ test.txt (base64)
+ dGVzdCAxMjMKbGFsYWxh
+
+External static files are not limited to a single file extension and will be
+served automatically from a C<public> directory if it exists.
+
+ $ mkdir public
+ $ mv something.js public/something.js
+ $ mv mojolicious.tar.gz public/mojolicious.tar.gz
+
+Both have a higher precedence than routes for C<GET> and C<HEAD> requests.
+Content negotiation with C<Range>, C<If-None-Match> and C<If-Modified-Since>
+headers is supported as well and can be tested very easily with
+L<Mojolicious::Command::get>.
+
+ $ ./myapp.pl get /something.js -v -H 'Range: bytes=2-4'
+
+=head2 External templates
+
+External templates will be searched by the renderer in a C<templates>
+directory if it exists and have a higher precedence than those in the C<DATA>
+section.
+
+ use Mojolicious::Lite;
+
+ # Render template "templates/foo/bar.html.ep"
+ any '/external' => sub {
+ my $c = shift;
+ $c->render(template => 'foo/bar');
+ };
+
+ app->start;
+
+=head2 Conditions
+
+Conditions such as C<agent> and C<host> from
+L<Mojolicious::Plugin::HeaderCondition> allow even more powerful route
+constructs.
+
+ use Mojolicious::Lite;
+
+ # Firefox
+ get '/foo' => (agent => qr/Firefox/) => sub {
+ my $c = shift;
+ $c->render(text => 'Congratulations, you are using a cool browser.');
+ };
+
+ # Internet Explorer
+ get '/foo' => (agent => qr/Internet Explorer/) => sub {
+ my $c = shift;
+ $c->render(text => 'Dude, you really need to upgrade to Firefox.');
+ };
+
+ # http://mojolicio.us/bar
+ get '/bar' => (host => 'mojolicio.us') => sub {
+ my $c = shift;
+ $c->render(text => 'Hello Mojolicious.');
+ };
+
+ app->start;
+
+=head2 Sessions
+
+Signed cookie based sessions just work out of the box as soon as you start
+using them through the helper
+L<Mojolicious::Plugin::DefaultHelpers/"session">, just be aware that all
+session data gets serialized with L<Mojo::JSON>.
+
+ use Mojolicious::Lite;
+
+ # Access session data in action and template
+ get '/counter' => sub {
+ my $c = shift;
+ $c->session->{counter}++;
+ };
+
+ app->start;
+ __DATA__
+
+ @@ counter.html.ep
+ Counter: <%= session 'counter' %>
+
+Note that you should use custom L<Mojolicious/"secrets"> to make signed
+cookies really secure.
+
+ app->secrets(['My secret passphrase here']);
+
+=head2 File uploads
+
+All files uploaded via C<multipart/form-data> request are automatically
+available as L<Mojo::Upload> objects. And you don't have to worry about memory
+usage, because all files above 250KB will be automatically streamed into a
+temporary file.
+
+ use Mojolicious::Lite;
+
+ # Upload form in DATA section
+ get '/' => 'form';
+
+ # Multipart upload handler
+ post '/upload' => sub {
+ my $c = shift;
+
+ # Check file size
+ return $c->render(text => 'File is too big.', status => 200)
+ if $c->req->is_limit_exceeded;
+
+ # Process uploaded file
+ return $c->redirect_to('form') unless my $example = $c->param('example');
+ my $size = $example->size;
+ my $name = $example->filename;
+ $c->render(text => "Thanks for uploading $size byte file $name.");
+ };
+
+ app->start;
+ __DATA__
+
+ @@ form.html.ep
+ <!DOCTYPE html>
+ <html>
+ <head><title>Upload</title></head>
+ <body>
+ %= form_for upload => (enctype => 'multipart/form-data') => begin
+ %= file_field 'example'
+ %= submit_button 'Upload'
+ % end
+ </body>
+ </html>
+
+To protect you from excessively large files there is also a limit of 10MB by
+default, which you can tweak with the attribute
+L<Mojo::Message/"max_message_size"> or C<MOJO_MAX_MESSAGE_SIZE> environment
+variable.
+
+ # Increase limit to 1GB
+ $ENV{MOJO_MAX_MESSAGE_SIZE} = 1073741824;
+
+=head2 User agent
+
+With L<Mojo::UserAgent>, which is available through the helper
+L<Mojolicious::Plugin::DefaultHelpers/"ua">, there's a full featured HTTP and
+WebSocket user agent built right in. Especially in combination with
+L<Mojo::JSON> and L<Mojo::DOM> this can be a very powerful tool.
+
+ use Mojolicious::Lite;
+
+ # Blocking
+ get '/headers' => sub {
+ my $c = shift;
+ my $url = $c->param('url') || 'http://mojolicio.us';
+ my $dom = $c->ua->get($url)->res->dom;
+ $c->render(json => $dom->find('h1, h2, h3')->map('text')->to_array);
+ };
+
+ # Non-blocking
+ get '/title' => sub {
+ my $c = shift;
+ $c->ua->get('mojolicio.us' => sub {
+ my ($ua, $tx) = @_;
+ $c->render(data => $tx->res->dom->at('title')->text);
+ });
+ };
+
+ # Concurrent non-blocking
+ get '/titles' => sub {
+ my $c = shift;
+ $c->delay(
+ sub {
+ my $delay = shift;
+ $c->ua->get('http://mojolicio.us' => $delay->begin);
+ $c->ua->get('https://metacpan.org' => $delay->begin);
+ },
+ sub {
+ my ($delay, $mojo, $cpan) = @_;
+ $c->render(json => {
+ mojo => $mojo->res->dom->at('title')->text,
+ cpan => $cpan->res->dom->at('title')->text
+ });
+ }
+ );
+ };
+
+ app->start;
+
+For more information about the user agent see also
+L<Mojolicious::Guides::Cookbook/"USER AGENT">.
+
+=head2 WebSockets
+
+WebSocket applications have never been this simple before. Just receive
+messages by subscribing to events such as
+L<Mojo::Transaction::WebSocket/"json"> with L<Mojolicious::Controller/"on">
+and return them with L<Mojolicious::Controller/"send">.
+
+ use Mojolicious::Lite;
+
+ websocket '/echo' => sub {
+ my $c = shift;
+ $c->on(json => sub {
+ my ($c, $hash) = @_;
+ $hash->{msg} = "echo: $hash->{msg}";
+ $c->send({json => $hash});
+ });
+ };
+
+ get '/' => 'index';
+
+ app->start;
+ __DATA__
+
+ @@ index.html.ep
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <title>Echo</title>
+ <script>
+ var ws = new WebSocket('<%= url_for('echo')->to_abs %>');
+ ws.onmessage = function (event) {
+ document.body.innerHTML += JSON.parse(event.data).msg;
+ };
+ ws.onopen = function (event) {
+ ws.send(JSON.stringify({msg: 'I ♥ Mojolicious!'}));
+ };
+ </script>
+ </head>
+ </html>
+
+For more information about real-time web features see also
+L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB">.
+
+=head2 Mode
+
+You can use the L<Mojo::Log> object from L<Mojo/"log"> to portably collect
+debug messages and automatically disable them later in a production setup by
+changing the L<Mojolicious> operating mode, which can also be retrieved from
+the attribute L<Mojolicious/"mode">.
+
+ use Mojolicious::Lite;
+
+ # Prepare mode specific message during startup
+ my $msg = app->mode eq 'development' ? 'Development!' : 'Something else!';
+
+ get '/' => sub {
+ my $c = shift;
+ $c->app->log->debug('Rendering mode specific message');
+ $c->render(text => $msg);
+ };
+
+ app->log->debug('Starting application');
+ app->start;
+
+The default operating mode will usually be C<development> and can be changed
+with command line options or the C<MOJO_MODE> and C<PLACK_ENV> environment
+variables. A mode other than C<development> will raise the log level from
+C<debug> to C<info>.
+
+ $ ./myapp.pl daemon -m production
+
+All messages will be written to C<STDERR> or a C<log/$mode.log> file if a
+C<log> directory exists.
+
+ $ mkdir log
+
+Mode changes also affect a few other aspects of the framework, such as mode
+specific C<exception> and C<not_found> templates.
+
+=head2 Testing
+
+Testing your application is as easy as creating a C<t> directory and filling
+it with normal Perl tests, which can be a lot of fun thanks to L<Test::Mojo>.
+
+ use Test::More;
+ use Test::Mojo;
+
+ use FindBin;
+ require "$FindBin::Bin/../myapp.pl";
+
+ my $t = Test::Mojo->new;
+ $t->get_ok('/')->status_is(200)->content_like(qr/Funky/);
+
+ done_testing();
+
+Run all tests with the command L<Mojolicious::Command::test>.
+
+ $ ./myapp.pl test
+ $ ./myapp.pl test -v
+
+=head1 MORE
+
+You can continue with L<Mojolicious::Guides> now or take a look at the
+L<Mojolicious wiki|http://github.com/kraih/mojo/wiki>, which contains a lot
+more documentation and examples by many different authors.
+
+=head1 SUPPORT
+
+If you have any questions the documentation might not yet answer, don't
+hesitate to ask on the
+L<mailing-list|http://groups.google.com/group/mojolicious> or the official IRC
+channel C<#mojo> on C<irc.perl.org>.
+
+=cut
@@ -29,7 +29,10 @@ L<learn.perl.org|http://learn.perl.org/>.
All web development starts with HTML, CSS and JavaScript, to learn the basics
we recommend the
-L<Mozilla Developer Network|https://developer.mozilla.org/en-US/docs/Web>.
+L<Mozilla Developer Network|https://developer.mozilla.org/en-US/docs/Web>. And
+if you want to know more about how browsers and web servers actually
+communicate, there's also a very nice introduction to
+L<HTTP|https://developer.mozilla.org/en-US/docs/Web/HTTP>.
=back
@@ -37,14 +40,14 @@ L<Mozilla Developer Network|https://developer.mozilla.org/en-US/docs/Web>.
=over 2
-=item L<Mojolicious::Lite>
+=item L<Mojolicious::Guides::Tutorial>
-A fast and fun way to get started developing web applications with Mojolicious
-is the L<Mojolicious::Lite> tutorial. This micro web framework is only a thin
-wrapper around the normal web framework, so almost everything you learn here
-also applies to full L<Mojolicious> applications. The simplified notation
-introduced in the tutorial is commonly used throughout the guides and is
-therefore considered a prerequisite, you should definitely take a look!
+A fast and fun way to get started developing web applications with
+L<Mojolicious>. The tutorial introduces the L<Mojolicious::Lite> micro web
+framework, which is only a thin wrapper around the full web framework. The
+simplified notation introduced in the tutorial is commonly used throughout the
+guides and is therefore considered a prerequisite, you should definitely take
+a look!
=back
@@ -62,7 +62,7 @@ sub import {
=head1 NAME
-Mojolicious::Lite - Real-time micro web framework
+Mojolicious::Lite - Micro real-time web framework
=head1 SYNOPSIS
@@ -84,936 +84,7 @@ Mojolicious::Lite - Real-time micro web framework
L<Mojolicious::Lite> is a micro real-time web framework built around
L<Mojolicious>.
-=head1 TUTORIAL
-
-A quick example driven introduction to the wonders of L<Mojolicious::Lite>.
-Most of what you'll learn here also applies to full L<Mojolicious>
-applications.
-
-=head2 Hello World
-
-A simple Hello World application can look like this, L<strict>, L<warnings>,
-L<utf8> and Perl 5.10 features are automatically enabled and a few
-L</"FUNCTIONS"> imported when you use L<Mojolicious::Lite>, turning your
-script into a full featured web application.
-
- #!/usr/bin/env perl
- use Mojolicious::Lite;
-
- get '/' => sub {
- my $c = shift;
- $c->render(text => 'Hello World!');
- };
-
- app->start;
-
-There is also a helper command to generate a small example application.
-
- $ mojo generate lite_app myapp.pl
-
-=head2 Commands
-
-All the normal L<Mojolicious::Commands> are available from the command line.
-Note that CGI and L<PSGI> environments can usually be auto detected and will
-just work without commands.
-
- $ ./myapp.pl daemon
- Server available at http://127.0.0.1:3000.
-
- $ ./myapp.pl daemon -l http://*:8080
- Server available at http://127.0.0.1:8080.
-
- $ ./myapp.pl cgi
- ...CGI output...
-
- $ ./myapp.pl get /
- Hello World!
-
- $ ./myapp.pl
- ...List of available commands (or automatically detected environment)...
-
-The C<app-E<gt>start> call that starts the L<Mojolicious> command system
-should usually be the last expression in your application and can be
-customized to override normal C<@ARGV> use.
-
- app->start('cgi');
-
-=head2 Reloading
-
-Your application will automatically reload itself if you start it with the
-C<morbo> development web server, so you don't have to restart the server after
-every change.
-
- $ morbo ./myapp.pl
- Server available at http://127.0.0.1:3000.
-
-For more information about how to deploy your application see also
-L<Mojolicious::Guides::Cookbook/"DEPLOYMENT">.
-
-=head2 Routes
-
-Routes are basically just fancy paths that can contain different kinds of
-placeholders and usually lead to an action. The first argument passed to all
-actions C<$c> is a L<Mojolicious::Controller> object containing both the HTTP
-request and response.
-
- use Mojolicious::Lite;
-
- # Route leading to an action
- get '/foo' => sub {
- my $c = shift;
- $c->render(text => 'Hello World!');
- };
-
- app->start;
-
-Response content is often generated by actions with
-L<Mojolicious::Controller/"render">, but more about that later.
-
-=head2 GET/POST parameters
-
-All C<GET> and C<POST> parameters sent with the request are accessible via
-L<Mojolicious::Controller/"param">.
-
- use Mojolicious::Lite;
-
- # /foo?user=sri
- get '/foo' => sub {
- my $c = shift;
- my $user = $c->param('user');
- $c->render(text => "Hello $user.");
- };
-
- app->start;
-
-=head2 Stash and templates
-
-The L<Mojolicious::Controller/"stash"> is used to pass data to templates,
-which can be inlined in the C<DATA> section.
-
- use Mojolicious::Lite;
-
- # Route leading to an action that renders a template
- get '/bar' => sub {
- my $c = shift;
- $c->stash(one => 23);
- $c->render('baz', two => 24);
- };
-
- app->start;
- __DATA__
-
- @@ baz.html.ep
- The magic numbers are <%= $one %> and <%= $two %>.
-
-For more information about templates see also
-L<Mojolicious::Guides::Rendering/"Embedded Perl">.
-
-=head2 HTTP
-
-L<Mojolicious::Controller/"req"> and L<Mojolicious::Controller/"res"> give you
-full access to all HTTP features and information.
-
- use Mojolicious::Lite;
-
- # Access request information
- get '/agent' => sub {
- my $c = shift;
- my $host = $c->req->url->to_abs->host;
- my $ua = $c->req->headers->user_agent;
- $c->render(text => "Request by $ua reached $host.");
- };
-
- # Echo the request body and send custom header with response
- post '/echo' => sub {
- my $c = shift;
- $c->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
- $c->render(data => $c->req->body);
- };
-
- app->start;
-
-You can test the more advanced examples right from the command line with
-L<Mojolicious::Command::get>.
-
- $ ./myapp.pl get -v -M POST -c 'test' /echo
-
-=head2 Built-in C<exception> and C<not_found> pages
-
-During development you will encounter these pages whenever you make a mistake,
-they are gorgeous and contain a lot of valuable information that will aid you
-in debugging your application.
-
- use Mojolicious::Lite;
-
- # Not found (404)
- get '/missing' => sub { shift->render('does_not_exist') };
-
- # Exception (500)
- get '/dies' => sub { die 'Intentional error' };
-
- app->start;
-
-You can even use CSS selectors with L<Mojolicious::Command::get> to extract
-only the information you're actually interested in.
-
- $ ./myapp.pl get /dies '#error'
-
-=head2 Route names
-
-All routes can have a name associated with them, this allows automatic
-template detection and backreferencing with
-L<Mojolicious::Controller/"url_for">, on which many methods and helpers like
-L<Mojolicious::Plugin::TagHelpers/"link_to"> rely.
-
- use Mojolicious::Lite;
-
- # Render the template "index.html.ep"
- get '/' => sub {
- my $c = shift;
- $c->render;
- } => 'index';
-
- # Render the template "hello.html.ep"
- get '/hello';
-
- app->start;
- __DATA__
-
- @@ index.html.ep
- <%= link_to Hello => 'hello' %>.
- <%= link_to Reload => 'index' %>.
-
- @@ hello.html.ep
- Hello World!
-
-Nameless routes get an automatically generated one assigned that is simply
-equal to the route itself without non-word characters.
-
-=head2 Layouts
-
-Templates can have layouts too, you just select one with the helper
-L<Mojolicious::Plugin::DefaultHelpers/"layout"> and place the result of the
-current template with the helper
-L<Mojolicious::Plugin::DefaultHelpers/"content">.
-
- use Mojolicious::Lite;
-
- get '/with_layout';
-
- app->start;
- __DATA__
-
- @@ with_layout.html.ep
- % title 'Green';
- % layout 'green';
- Hello World!
-
- @@ layouts/green.html.ep
- <!DOCTYPE html>
- <html>
- <head><title><%= title %></title></head>
- <body><%= content %></body>
- </html>
-
-The stash or helpers like L<Mojolicious::Plugin::DefaultHelpers/"title"> can
-be used to pass additional data to the layout.
-
-=head2 Blocks
-
-Template blocks can be used like normal Perl functions and are always
-delimited by the C<begin> and C<end> keywords, they are the foundation for
-many helpers.
-
- use Mojolicious::Lite;
-
- get '/with_block' => 'block';
-
- app->start;
- __DATA__
-
- @@ block.html.ep
- % my $link = begin
- % my ($url, $name) = @_;
- Try <%= link_to $url => begin %><%= $name %><% end %>.
- % end
- <!DOCTYPE html>
- <html>
- <head><title>Sebastians frameworks</title></head>
- <body>
- %= $link->('http://mojolicio.us', 'Mojolicious')
- %= $link->('http://catalystframework.org', 'Catalyst')
- </body>
- </html>
-
-=head2 Helpers
-
-Helpers are little functions you can reuse throughout your whole application,
-from actions to templates.
-
- use Mojolicious::Lite;
-
- # A helper to identify visitors
- helper whois => sub {
- my $c = shift;
- my $agent = $c->req->headers->user_agent || 'Anonymous';
- my $ip = $c->tx->remote_address;
- return "$agent ($ip)";
- };
-
- # Use helper in action and template
- get '/secret' => sub {
- my $c = shift;
- my $user = $c->whois;
- $c->app->log->debug("Request from $user.");
- };
-
- app->start;
- __DATA__
-
- @@ secret.html.ep
- We know who you are <%= whois %>.
-
-A list of all built-in ones can be found in
-L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
-
-=head2 Placeholders
-
-Route placeholders allow capturing parts of a request path until a C</> or
-C<.> separator occurs, results are accessible via
-L<Mojolicious::Controller/"stash"> and L<Mojolicious::Controller/"param">.
-
- use Mojolicious::Lite;
-
- # /foo/test
- # /foo/test123
- get '/foo/:bar' => sub {
- my $c = shift;
- my $bar = $c->stash('bar');
- $c->render(text => "Our :bar placeholder matched $bar");
- };
-
- # /testsomething/foo
- # /test123something/foo
- get '/(:bar)something/foo' => sub {
- my $c = shift;
- my $bar = $c->param('bar');
- $c->render(text => "Our :bar placeholder matched $bar");
- };
-
- app->start;
-
-=head2 Relaxed Placeholders
-
-Relaxed placeholders allow matching of everything until a C</> occurs.
-
- use Mojolicious::Lite;
-
- # /test/hello
- # /test123/hello
- # /test.123/hello
- get '/#you/hello' => 'groovy';
-
- app->start;
- __DATA__
-
- @@ groovy.html.ep
- Your name is <%= $you %>.
-
-=head2 Wildcard placeholders
-
-Wildcard placeholders allow matching absolutely everything, including C</> and
-C<.>.
-
- use Mojolicious::Lite;
-
- # /hello/test
- # /hello/test123
- # /hello/test.123/test/123
- get '/hello/*you' => 'groovy';
-
- app->start;
- __DATA__
-
- @@ groovy.html.ep
- Your name is <%= $you %>.
-
-=head2 HTTP methods
-
-Routes can be restricted to specific request methods with different keywords.
-
- use Mojolicious::Lite;
-
- # GET /hello
- get '/hello' => sub {
- my $c = shift;
- $c->render(text => 'Hello World!');
- };
-
- # PUT /hello
- put '/hello' => sub {
- my $c = shift;
- my $size = length $c->req->body;
- $c->render(text => "You uploaded $size bytes to /hello.");
- };
-
- # GET|POST|PATCH /bye
- any [qw(GET POST PATCH)] => '/bye' => sub {
- my $c = shift;
- $c->render(text => 'Bye World!');
- };
-
- # * /whatever
- any '/whatever' => sub {
- my $c = shift;
- my $method = $c->req->method;
- $c->render(text => "You called /whatever with $method.");
- };
-
- app->start;
-
-=head2 Optional placeholders
-
-All placeholders require a value, but by assigning them default values you can
-make capturing optional.
-
- use Mojolicious::Lite;
-
- # /hello
- # /hello/Sara
- get '/hello/:name' => {name => 'Sebastian', day => 'Monday'} => sub {
- my $c = shift;
- $c->render('groovy', format => 'txt');
- };
-
- app->start;
- __DATA__
-
- @@ groovy.txt.ep
- My name is <%= $name %> and it is <%= $day %>.
-
-Default values that don't belong to a placeholder simply get merged into the
-stash all the time.
-
-=head2 Restrictive placeholders
-
-The easiest way to make placeholders more restrictive are alternatives, you
-just make a list of possible values.
-
- use Mojolicious::Lite;
-
- # /test
- # /123
- any '/:foo' => [foo => [qw(test 123)]] => sub {
- my $c = shift;
- my $foo = $c->param('foo');
- $c->render(text => "Our :foo placeholder matched $foo");
- };
-
- app->start;
-
-All placeholders get compiled to a regular expression internally, this process
-can also be easily customized.
-
- use Mojolicious::Lite;
-
- # /1
- # /123
- any '/:bar' => [bar => qr/\d+/] => sub {
- my $c = shift;
- my $bar = $c->param('bar');
- $c->render(text => "Our :bar placeholder matched $bar");
- };
-
- app->start;
-
-Just make sure not to use C<^> and C<$> or capturing groups C<(...)>, because
-placeholders become part of a larger regular expression internally, C<(?:...)>
-is fine though.
-
-=head2 Under
-
-Authentication and code shared between multiple routes can be realized easily
-with routes generated by the L</"under"> statement. All following routes are
-only evaluated if the callback returned a true value.
-
- use Mojolicious::Lite;
-
- # Authenticate based on name parameter
- under sub {
- my $c = shift;
-
- # Authenticated
- my $name = $c->param('name') || '';
- return 1 if $name eq 'Bender';
-
- # Not authenticated
- $c->render('denied');
- return undef;
- };
-
- # Only reached when authenticated
- get '/' => 'index';
-
- app->start;
- __DATA__
-
- @@ denied.html.ep
- You are not Bender, permission denied.
-
- @@ index.html.ep
- Hi Bender.
-
-Prefixing multiple routes is another good use for L</"under">.
-
- use Mojolicious::Lite;
-
- # /foo
- under '/foo';
-
- # /foo/bar
- get '/bar' => {text => 'foo bar'};
-
- # /foo/baz
- get '/baz' => {text => 'foo baz'};
-
- # / (reset)
- under '/' => {msg => 'whatever'};
-
- # /bar
- get '/bar' => {inline => '<%= $msg %> works'};
-
- app->start;
-
-You can also L</"group"> related routes, which allows nesting of multiple
-L</"under"> statements.
-
- use Mojolicious::Lite;
-
- # Global logic shared by all routes
- under sub {
- my $c = shift;
- return 1 if $c->req->headers->header('X-Bender');
- $c->render(text => "You're not Bender.");
- return undef;
- };
-
- # Admin section
- group {
-
- # Local logic shared only by routes in this group
- under '/admin' => sub {
- my $c = shift;
- return 1 if $c->req->headers->header('X-Awesome');
- $c->render(text => "You're not awesome enough.");
- return undef;
- };
-
- # GET /admin/dashboard
- get '/dashboard' => {text => 'Nothing to see here yet.'};
- };
-
- # GET /welcome
- get '/welcome' => {text => 'Hi Bender.'};
-
- app->start;
-
-=head2 Formats
-
-Formats can be automatically detected from file extensions, they are used to
-find the right template and generate the correct C<Content-Type> header.
-
- use Mojolicious::Lite;
-
- # /detection
- # /detection.html
- # /detection.txt
- get '/detection' => sub {
- my $c = shift;
- $c->render('detected');
- };
-
- app->start;
- __DATA__
-
- @@ detected.html.ep
- <!DOCTYPE html>
- <html>
- <head><title>Detected</title></head>
- <body>HTML was detected.</body>
- </html>
-
- @@ detected.txt.ep
- TXT was detected.
-
-The default format is C<html>, restrictive placeholders can be used to limit
-possible values.
-
- use Mojolicious::Lite;
-
- # /hello.json
- # /hello.txt
- get '/hello' => [format => [qw(json txt)]] => sub {
- my $c = shift;
- return $c->render(json => {hello => 'world'})
- if $c->stash('format') eq 'json';
- $c->render(text => 'hello world');
- };
-
- app->start;
-
-Or you can just disable format detection.
-
- use Mojolicious::Lite;
-
- # /hello
- get '/hello' => [format => 0] => {text => 'No format detection.'};
-
- # Disable detection and allow the following routes selective re-enabling
- under [format => 0];
-
- # /foo
- get '/foo' => {text => 'No format detection again.'};
-
- # /bar.txt
- get '/bar' => [format => 'txt'] => {text => ' Just one format.'};
-
- app->start;
-
-=head2 Content negotiation
-
-For resources with different representations and that require truly RESTful
-content negotiation you can also use L<Mojolicious::Controller/"respond_to">.
-
- use Mojolicious::Lite;
-
- # /hello (Accept: application/json)
- # /hello (Accept: application/xml)
- # /hello.json
- # /hello.xml
- # /hello?format=json
- # /hello?format=xml
- get '/hello' => sub {
- my $c = shift;
- $c->respond_to(
- json => {json => {hello => 'world'}},
- xml => {text => '<hello>world</hello>'},
- any => {data => '', status => 204}
- );
- };
-
- app->start;
-
-MIME type mappings can be extended or changed easily with
-L<Mojolicious/"types">.
-
- app->types->type(rdf => 'application/rdf+xml');
-
-=head2 Static files
-
-Similar to templates, but with only a single file extension and optional
-Base64 encoding, static files can be inlined in the C<DATA> section and are
-served automatically.
-
- use Mojolicious::Lite;
-
- app->start;
- __DATA__
-
- @@ something.js
- alert('hello!');
-
- @@ test.txt (base64)
- dGVzdCAxMjMKbGFsYWxh
-
-External static files are not limited to a single file extension and will be
-served automatically from a C<public> directory if it exists.
-
- $ mkdir public
- $ mv something.js public/something.js
- $ mv mojolicious.tar.gz public/mojolicious.tar.gz
-
-Both have a higher precedence than routes for C<GET> and C<HEAD> requests.
-Content negotiation with C<Range>, C<If-None-Match> and C<If-Modified-Since>
-headers is supported as well and can be tested very easily with
-L<Mojolicious::Command::get>.
-
- $ ./myapp.pl get /something.js -v -H 'Range: bytes=2-4'
-
-=head2 External templates
-
-External templates will be searched by the renderer in a C<templates>
-directory if it exists and have a higher precedence than those in the C<DATA>
-section.
-
- use Mojolicious::Lite;
-
- # Render template "templates/foo/bar.html.ep"
- any '/external' => sub {
- my $c = shift;
- $c->render('foo/bar');
- };
-
- app->start;
-
-=head2 Conditions
-
-Conditions such as C<agent> and C<host> from
-L<Mojolicious::Plugin::HeaderCondition> allow even more powerful route
-constructs.
-
- use Mojolicious::Lite;
-
- # Firefox
- get '/foo' => (agent => qr/Firefox/) => sub {
- my $c = shift;
- $c->render(text => 'Congratulations, you are using a cool browser.');
- };
-
- # Internet Explorer
- get '/foo' => (agent => qr/Internet Explorer/) => sub {
- my $c = shift;
- $c->render(text => 'Dude, you really need to upgrade to Firefox.');
- };
-
- # http://mojolicio.us/bar
- get '/bar' => (host => 'mojolicio.us') => sub {
- my $c = shift;
- $c->render(text => 'Hello Mojolicious.');
- };
-
- app->start;
-
-=head2 Sessions
-
-Signed cookie based sessions just work out of the box as soon as you start
-using them through the helper
-L<Mojolicious::Plugin::DefaultHelpers/"session">, just be aware that all
-session data gets serialized with L<Mojo::JSON>.
-
- use Mojolicious::Lite;
-
- # Access session data in action and template
- get '/counter' => sub {
- my $c = shift;
- $c->session->{counter}++;
- };
-
- app->start;
- __DATA__
-
- @@ counter.html.ep
- Counter: <%= session 'counter' %>
-
-Note that you should use custom L<Mojolicious/"secrets"> to make signed
-cookies really secure.
-
- app->secrets(['My secret passphrase here']);
-
-=head2 File uploads
-
-All files uploaded via C<multipart/form-data> request are automatically
-available as L<Mojo::Upload> objects. And you don't have to worry about memory
-usage, because all files above 250KB will be automatically streamed into a
-temporary file.
-
- use Mojolicious::Lite;
-
- # Upload form in DATA section
- get '/' => 'form';
-
- # Multipart upload handler
- post '/upload' => sub {
- my $c = shift;
-
- # Check file size
- return $c->render(text => 'File is too big.', status => 200)
- if $c->req->is_limit_exceeded;
-
- # Process uploaded file
- return $c->redirect_to('form') unless my $example = $c->param('example');
- my $size = $example->size;
- my $name = $example->filename;
- $c->render(text => "Thanks for uploading $size byte file $name.");
- };
-
- app->start;
- __DATA__
-
- @@ form.html.ep
- <!DOCTYPE html>
- <html>
- <head><title>Upload</title></head>
- <body>
- %= form_for upload => (enctype => 'multipart/form-data') => begin
- %= file_field 'example'
- %= submit_button 'Upload'
- % end
- </body>
- </html>
-
-To protect you from excessively large files there is also a limit of 10MB by
-default, which you can tweak with the attribute
-L<Mojo::Message/"max_message_size"> or C<MOJO_MAX_MESSAGE_SIZE> environment
-variable.
-
- # Increase limit to 1GB
- $ENV{MOJO_MAX_MESSAGE_SIZE} = 1073741824;
-
-=head2 User agent
-
-With L<Mojo::UserAgent>, which is available through the helper
-L<Mojolicious::Plugin::DefaultHelpers/"ua">, there's a full featured HTTP and
-WebSocket user agent built right in. Especially in combination with
-L<Mojo::JSON> and L<Mojo::DOM> this can be a very powerful tool.
-
- use Mojolicious::Lite;
-
- # Blocking
- get '/headers' => sub {
- my $c = shift;
- my $url = $c->param('url') || 'http://mojolicio.us';
- my $dom = $c->ua->get($url)->res->dom;
- $c->render(json => $dom->find('h1, h2, h3')->map('text')->to_array);
- };
-
- # Non-blocking
- get '/title' => sub {
- my $c = shift;
- $c->ua->get('mojolicio.us' => sub {
- my ($ua, $tx) = @_;
- $c->render(data => $tx->res->dom->at('title')->text);
- });
- };
-
- # Concurrent non-blocking
- get '/titles' => sub {
- my $c = shift;
- $c->delay(
- sub {
- my $delay = shift;
- $c->ua->get('http://mojolicio.us' => $delay->begin);
- $c->ua->get('https://metacpan.org' => $delay->begin);
- },
- sub {
- my ($delay, $mojo, $cpan) = @_;
- $c->render(json => {
- mojo => $mojo->res->dom->at('title')->text,
- cpan => $cpan->res->dom->at('title')->text
- });
- }
- );
- };
-
- app->start;
-
-For more information about the user agent see also
-L<Mojolicious::Guides::Cookbook/"USER AGENT">.
-
-=head2 WebSockets
-
-WebSocket applications have never been this simple before. Just receive
-messages by subscribing to events such as
-L<Mojo::Transaction::WebSocket/"json"> with L<Mojolicious::Controller/"on">
-and return them with L<Mojolicious::Controller/"send">.
-
- use Mojolicious::Lite;
-
- websocket '/echo' => sub {
- my $c = shift;
- $c->on(json => sub {
- my ($c, $hash) = @_;
- $hash->{msg} = "echo: $hash->{msg}";
- $c->send({json => $hash});
- });
- };
-
- get '/' => 'index';
-
- app->start;
- __DATA__
-
- @@ index.html.ep
- <!DOCTYPE html>
- <html>
- <head>
- <title>Echo</title>
- <script>
- var ws = new WebSocket('<%= url_for('echo')->to_abs %>');
- ws.onmessage = function (event) {
- document.body.innerHTML += JSON.parse(event.data).msg;
- };
- ws.onopen = function (event) {
- ws.send(JSON.stringify({msg: 'I ♥ Mojolicious!'}));
- };
- </script>
- </head>
- </html>
-
-For more information about real-time web features see also
-L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB">.
-
-=head2 Mode
-
-You can use the L<Mojo::Log> object from L<Mojo/"log"> to portably collect
-debug messages and automatically disable them later in a production setup by
-changing the L<Mojolicious> operating mode, which can also be retrieved from
-the attribute L<Mojolicious/"mode">.
-
- use Mojolicious::Lite;
-
- # Prepare mode specific message during startup
- my $msg = app->mode eq 'development' ? 'Development!' : 'Something else!';
-
- get '/' => sub {
- my $c = shift;
- $c->app->log->debug('Rendering mode specific message.');
- $c->render(text => $msg);
- };
-
- app->log->debug('Starting application.');
- app->start;
-
-The default operating mode will usually be C<development> and can be changed
-with command line options or the C<MOJO_MODE> and C<PLACK_ENV> environment
-variables. A mode other than C<development> will raise the log level from
-C<debug> to C<info>.
-
- $ ./myapp.pl daemon -m production
-
-All messages will be written to C<STDERR> or a C<log/$mode.log> file if a
-C<log> directory exists.
-
- $ mkdir log
-
-Mode changes also affect a few other aspects of the framework, such as mode
-specific C<exception> and C<not_found> templates.
-
-=head2 Testing
-
-Testing your application is as easy as creating a C<t> directory and filling
-it with normal Perl tests, which can be a lot of fun thanks to L<Test::Mojo>.
-
- use Test::More;
- use Test::Mojo;
-
- use FindBin;
- require "$FindBin::Bin/../myapp.pl";
-
- my $t = Test::Mojo->new;
- $t->get_ok('/')->status_is(200)->content_like(qr/Funky/);
-
- done_testing();
-
-Run all tests with the command L<Mojolicious::Command::test>.
-
- $ ./myapp.pl test
- $ ./myapp.pl test -v
-
-=head2 More
-
-You can continue with L<Mojolicious::Guides> now, and don't forget to have
-fun!
+See L<Mojolicious::Guides::Tutorial> for more!
=head1 FUNCTIONS
@@ -1029,8 +100,8 @@ automatically exported.
my $route = any [qw(GET POST)] => '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"any">, matching any of the
-listed HTTP request methods or all. See also the tutorial above for many more
-argument variations.
+listed HTTP request methods or all. See also L<Mojolicious::Guides::Tutorial>
+for many more argument variations.
=head2 app
@@ -1050,8 +121,8 @@ L<Mojolicious>.
my $route = del '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"delete">, matching only
-C<DELETE> requests. See also the tutorial above for many more argument
-variations.
+C<DELETE> requests. See also L<Mojolicious::Guides::Tutorial> for many more
+argument variations.
=head2 get
@@ -1060,7 +131,8 @@ variations.
my $route = get '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"get">, matching only C<GET>
-requests. See also the tutorial above for many more argument variations.
+requests. See also L<Mojolicious::Guides::Tutorial> for many more argument
+variations.
=head2 group
@@ -1087,8 +159,8 @@ Share code with L<Mojolicious/"hook">.
my $route = options '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"options">, matching only
-C<OPTIONS> requests. See also the tutorial above for many more argument
-variations.
+C<OPTIONS> requests. See also L<Mojolicious::Guides::Tutorial> for many more
+argument variations.
=head2 patch
@@ -1097,8 +169,8 @@ variations.
my $route = patch '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"patch">, matching only
-C<PATCH> requests. See also the tutorial above for many more argument
-variations.
+C<PATCH> requests. See also L<Mojolicious::Guides::Tutorial> for many more
+argument variations.
=head2 plugin
@@ -1113,8 +185,8 @@ Load a plugin with L<Mojolicious/"plugin">.
my $route = post '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"post">, matching only
-C<POST> requests. See also the tutorial above for many more argument
-variations.
+C<POST> requests. See also L<Mojolicious::Guides::Tutorial> for many more
+argument variations.
=head2 put
@@ -1123,7 +195,8 @@ variations.
my $route = put '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"put">, matching only C<PUT>
-requests. See also the tutorial above for many more argument variations.
+requests. See also L<Mojolicious::Guides::Tutorial> for many more argument
+variations.
=head2 under
@@ -1134,8 +207,8 @@ requests. See also the tutorial above for many more argument variations.
my $route = under [format => 0];
Generate nested route with L<Mojolicious::Routes::Route/"under">, to which all
-following routes are automatically appended. See also the tutorial above for
-more argument variations.
+following routes are automatically appended. See also
+L<Mojolicious::Guides::Tutorial> for more argument variations.
=head2 websocket
@@ -1144,8 +217,8 @@ more argument variations.
my $route = websocket '/:foo' => [foo => qr/\w+/] => sub {...};
Generate route with L<Mojolicious::Routes::Route/"websocket">, matching only
-WebSocket handshakes. See also the tutorial above for many more argument
-variations.
+WebSocket handshakes. See also L<Mojolicious::Guides::Tutorial> for many more
+argument variations.
=head1 ATTRIBUTES
@@ -6,7 +6,7 @@ use Mojo::Util qw(decode slurp);
sub load {
my ($self, $file, $conf, $app) = @_;
- $app->log->debug(qq{Reading configuration file "$file".});
+ $app->log->debug(qq{Reading configuration file "$file"});
return $self->parse(decode('UTF-8', slurp $file), $file, $conf, $app);
}
@@ -5,11 +5,15 @@ use Mojo::ByteStream;
use Mojo::Collection;
use Mojo::Exception;
use Mojo::IOLoop;
-use Mojo::Util qw(dumper sha1_sum steady_time);
+use Mojo::Util qw(deprecated dumper sha1_sum steady_time);
sub register {
my ($self, $app) = @_;
+ # DEPRECATED in Tiger Face!
+ $app->helper(render_exception => sub { _render('exception', @_) });
+ $app->helper(render_not_found => sub { _render('not_found', @_) });
+
# Controller alias helpers
for my $name (qw(app flash param stash session url_for validation)) {
$app->helper($name => sub { shift->$name(@_) });
@@ -80,12 +84,20 @@ sub _delay {
my $c = shift;
my $tx = $c->render_later->tx;
my $delay = Mojo::IOLoop->delay(@_);
- $delay->catch(sub { $c->render_exception(pop) and undef $tx })->wait;
+ $delay->catch(sub { $c->helpers->reply->exception(pop) and undef $tx })
+ ->wait;
}
sub _development {
my ($page, $c, $e) = @_;
+ # DEPRECATED in Tiger Face!
+ if (my $sub = $c->can("render_$page")) {
+ deprecated "Mojolicious::Controller::render_$page is DEPRECATED in favor"
+ . " of the reply->$page helper";
+ return $c->$sub($page eq 'exception' ? $e : ());
+ }
+
my $app = $c->app;
$app->log->error($e = Mojo::Exception->new($e)) if $page eq 'exception';
@@ -137,11 +149,19 @@ sub _is_fresh {
return $c->app->static->is_fresh($c, \%options);
}
+# DEPRECATED in Tiger Face!
+sub _render {
+ my $page = shift;
+ deprecated "Mojolicious::Controller::render_$page is DEPRECATED in favor of"
+ . " the reply->$page helper";
+ shift->helpers->reply->$page(@_);
+}
+
sub _static {
my ($c, $file) = @_;
return !!$c->rendered if $c->app->static->serve($c, $file);
- $c->app->log->debug(qq{File "$file" not found, public directory missing?});
- return !$c->render_not_found;
+ $c->app->log->debug(qq{Static file "$file" not found});
+ return !$c->helpers->reply->not_found;
}
sub _url_with {
@@ -13,7 +13,7 @@ sub _epl {
my $mt = delete $options->{'mojo.template'} || Mojo::Template->new;
my $log = $c->app->log;
if ($mt->compiled) {
- $log->debug("Rendering cached @{[$mt->name]}.");
+ $log->debug("Rendering cached @{[$mt->name]}");
$$output = $mt->interpret($c);
}
@@ -25,7 +25,7 @@ sub _epl {
# Inline
if (defined $inline) {
- $log->debug(qq{Rendering inline template "$name".});
+ $log->debug(qq{Rendering inline template "$name"});
$$output = $mt->name(qq{inline template "$name"})->render($inline, $c);
}
@@ -35,19 +35,19 @@ sub _epl {
# Try template
if (defined(my $path = $renderer->template_path($options))) {
- $log->debug(qq{Rendering template "$name".});
+ $log->debug(qq{Rendering template "$name"});
$$output = $mt->name(qq{template "$name"})->render_file($path, $c);
}
# Try DATA section
elsif (my $d = $renderer->get_data_template($options)) {
- $log->debug(qq{Rendering template "$name" from DATA section.});
+ $log->debug(qq{Rendering template "$name" from DATA section});
$$output
= $mt->name(qq{template "$name" from DATA section})->render($d, $c);
}
# No template
- else { $log->debug(qq{Template "$name" not found.}) and return undef }
+ else { $log->debug(qq{Template "$name" not found}) and return undef }
}
}
@@ -2,7 +2,7 @@ package Mojolicious::Plugin::TagHelpers;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::ByteStream;
-use Mojo::Util 'xss_escape';
+use Mojo::DOM::HTML;
use Scalar::Util 'blessed';
sub register {
@@ -84,29 +84,22 @@ sub _input {
my $value = $attrs{value} // '';
if ($type eq 'checkbox' || $type eq 'radio') {
$attrs{value} = $value;
- $attrs{checked} = 'checked' if grep { $_ eq $value } @values;
+ $attrs{checked} = undef if grep { $_ eq $value } @values;
}
# Others
else { $attrs{value} = $values[0] }
}
- return _validation($c, $name, 'input', %attrs, name => $name);
+ return _validation($c, $name, 'input', name => $name, %attrs);
}
sub _javascript {
my $c = shift;
-
- # CDATA
- my $cb = sub {''};
- if (ref $_[-1] eq 'CODE' && (my $old = pop)) {
- $cb = sub { "//<![CDATA[\n" . $old->() . "\n//]]>" }
- }
-
- # URL
- my $src = @_ % 2 ? $c->url_for(shift) : undef;
-
- return _tag('script', @_, $src ? (src => $src) : (), $cb);
+ my $content
+ = ref $_[-1] eq 'CODE' ? "//<![CDATA[\n" . pop->() . "\n//]]>" : '';
+ my @src = @_ % 2 ? (src => $c->url_for(shift)) : ();
+ return _tag('script', @src, @_, sub {$content});
}
sub _label_for {
@@ -134,18 +127,14 @@ sub _link_to {
sub _option {
my ($values, $pair) = @_;
$pair = [$pair => $pair] unless ref $pair eq 'ARRAY';
-
- # Attributes
my %attrs = (value => $pair->[1]);
- $attrs{selected} = 'selected' if exists $values->{$pair->[1]};
- %attrs = (%attrs, @$pair[2 .. $#$pair]);
-
- return _tag('option', %attrs, $pair->[0]);
+ $attrs{selected} = undef if exists $values->{$pair->[1]};
+ return _tag('option', %attrs, @$pair[2 .. $#$pair], $pair->[0]);
}
sub _password_field {
my ($c, $name) = (shift, shift);
- return _validation($c, $name, 'input', @_, name => $name,
+ return _validation($c, $name, 'input', name => $name, @_,
type => 'password');
}
@@ -168,24 +157,16 @@ sub _select_field {
else { $groups .= _option(\%values, $group) }
}
- return _validation($c, $name, 'select', %attrs, name => $name,
+ return _validation($c, $name, 'select', name => $name, %attrs,
sub {$groups});
}
sub _stylesheet {
my $c = shift;
-
- # CDATA
- my $cb;
- if (ref $_[-1] eq 'CODE' && (my $old = pop)) {
- $cb = sub { "/*<![CDATA[*/\n" . $old->() . "\n/*]]>*/" }
- }
-
- # "link" or "style" tag
- my $href = @_ % 2 ? $c->url_for(shift) : undef;
- return $href
- ? _tag('link', rel => 'stylesheet', href => $href, @_)
- : _tag('style', @_, $cb);
+ my $content
+ = ref $_[-1] eq 'CODE' ? "/*<![CDATA[*/\n" . pop->() . "\n/*]]>*/" : '';
+ return _tag('style', @_, sub {$content}) unless @_ % 2;
+ return _tag('link', rel => 'stylesheet', href => $c->url_for(shift), @_);
}
sub _submit_button {
@@ -194,34 +175,23 @@ sub _submit_button {
}
sub _tag {
- my $name = shift;
+ my $tree = ['tag', shift, undef, undef];
# Content
- my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
- my $content = @_ % 2 ? pop : undef;
-
- # Start tag
- my $tag = "<$name";
+ if (ref $_[-1] eq 'CODE') { push @$tree, ['raw', pop->()] }
+ elsif (@_ % 2) { push @$tree, ['text', pop] }
# Attributes
- my %attrs = @_;
- if ($attrs{data} && ref $attrs{data} eq 'HASH') {
- while (my ($key, $value) = each %{$attrs{data}}) {
+ my $attrs = $tree->[2] = {@_};
+ if ($attrs->{data} && ref $attrs->{data} eq 'HASH') {
+ while (my ($key, $value) = each %{$attrs->{data}}) {
$key =~ y/_/-/;
- $attrs{lc("data-$key")} = $value;
+ $attrs->{lc "data-$key"} = $value;
}
- delete $attrs{data};
+ delete $attrs->{data};
}
- $tag .= qq{ $_="} . xss_escape($attrs{$_} // '') . '"' for sort keys %attrs;
-
- # Empty element
- unless ($cb || defined $content) { $tag .= ' />' }
-
- # End tag
- else { $tag .= '>' . ($cb ? $cb->() : xss_escape $content) . "</$name>" }
- # Prevent escaping
- return Mojo::ByteStream->new($tag);
+ return Mojo::ByteStream->new(Mojo::DOM::HTML::_render($tree));
}
sub _tag_with_error {
@@ -238,7 +208,7 @@ sub _text_area {
my $content = @_ % 2 ? shift : undef;
$content = $c->param($name) // $content // $cb // '';
- return _validation($c, $name, 'textarea', @_, name => $name, $content);
+ return _validation($c, $name, 'textarea', name => $name, @_, $content);
}
sub _validation {
@@ -279,10 +249,10 @@ necessary attributes always be generated automatically.
<%= radio_button country => 'uk' %> UK
For fields that failed validation with L<Mojolicious::Controller/"validation">
-the C<field-with-error> class will be automatically added through the
-C<tag_with_error> helper, to make styling with CSS easier.
+the C<field-with-error> class will be automatically added through
+L</"tag_with_error">, to make styling with CSS easier.
- <input class="field-with-error" name="age" type="text" value="250" />
+ <input class="field-with-error" name="age" type="text" value="250">
This is a core plugin, that means it is always enabled and its code a good
example for learning how to build new plugins, you're welcome to fork it.
@@ -302,8 +272,8 @@ L<Mojolicious::Plugin::TagHelpers> implements the following helpers.
Generate C<input> tag of type C<checkbox>. Previous input values will
automatically get picked up and shown as default.
- <input name="employed" type="checkbox" value="1" />
- <input disabled="disabled" name="employed" type="checkbox" value="1" />
+ <input name="employed" type="checkbox" value="1">
+ <input disabled="disabled" name="employed" type="checkbox" value="1">
=head2 color_field
@@ -314,9 +284,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<color>. Previous input values will
automatically get picked up and shown as default.
- <input name="background" type="color" />
- <input name="background" type="color" value="#ffffff" />
- <input id="foo" name="background" type="color" value="#ffffff" />
+ <input name="background" type="color">
+ <input name="background" type="color" value="#ffffff">
+ <input id="foo" name="background" type="color" value="#ffffff">
=head2 csrf_field
@@ -325,7 +295,7 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<hidden> with
L<Mojolicious::Plugin::DefaultHelpers/"csrf_token">.
- <input name="csrf_token" type="hidden" value="fa6a08..." />
+ <input name="csrf_token" type="hidden" value="fa6a08...">
=head2 date_field
@@ -336,9 +306,9 @@ L<Mojolicious::Plugin::DefaultHelpers/"csrf_token">.
Generate C<input> tag of type C<date>. Previous input values will
automatically get picked up and shown as default.
- <input name="end" type="date" />
- <input name="end" type="date" value="2012-12-21" />
- <input id="foo" name="end" type="date" value="2012-12-21" />
+ <input name="end" type="date">
+ <input name="end" type="date" value="2012-12-21">
+ <input id="foo" name="end" type="date" value="2012-12-21">
=head2 datetime_field
@@ -349,9 +319,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<datetime>. Previous input values will
automatically get picked up and shown as default.
- <input name="end" type="datetime" />
- <input name="end" type="datetime" value="2012-12-21T23:59:59Z" />
- <input id="foo" name="end" type="datetime" value="2012-12-21T23:59:59Z" />
+ <input name="end" type="datetime">
+ <input name="end" type="datetime" value="2012-12-21T23:59:59Z">
+ <input id="foo" name="end" type="datetime" value="2012-12-21T23:59:59Z">
=head2 email_field
@@ -362,9 +332,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<email>. Previous input values will
automatically get picked up and shown as default.
- <input name="notify" type="email" />
- <input name="notify" type="email" value="nospam@example.com" />
- <input id="foo" name="notify" type="email" value="nospam@example.com" />
+ <input name="notify" type="email">
+ <input name="notify" type="email" value="nospam@example.com">
+ <input id="foo" name="notify" type="email" value="nospam@example.com">
=head2 file_field
@@ -373,8 +343,8 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<file>.
- <input name="avatar" type="file" />
- <input id="foo" name="avatar" type="file" />
+ <input name="avatar" type="file">
+ <input id="foo" name="avatar" type="file">
=head2 form_for
@@ -400,20 +370,20 @@ routes that allow C<POST> but not C<GET>, a C<method> attribute will be
automatically added.
<form action="/path/to/login">
- <input name="first_name" type="text" />
- <input value="Ok" type="submit" />
+ <input name="first_name" type="text">
+ <input value="Ok" type="submit">
</form>
<form action="/path/to/login.txt" method="POST">
- <input name="first_name" type="text" />
- <input value="Ok" type="submit" />
+ <input name="first_name" type="text">
+ <input value="Ok" type="submit">
</form>
<form action="/path/to/login" enctype="multipart/form-data">
- <input disabled="disabled" name="first_name" type="text" />
- <input value="Ok" type="submit" />
+ <input disabled="disabled" name="first_name" type="text">
+ <input value="Ok" type="submit">
</form>
<form action="http://example.com/login" method="POST">
- <input name="first_name" type="text" />
- <input value="Ok" type="submit" />
+ <input name="first_name" type="text">
+ <input value="Ok" type="submit">
</form>
=head2 hidden_field
@@ -423,8 +393,8 @@ automatically added.
Generate C<input> tag of type C<hidden>.
- <input name="foo" type="hidden" value="bar" />
- <input id="bar" name="foo" type="hidden" value="bar" />
+ <input name="foo" type="hidden" value="bar">
+ <input id="bar" name="foo" type="hidden" value="bar">
=head2 image
@@ -433,8 +403,8 @@ Generate C<input> tag of type C<hidden>.
Generate portable C<img> tag.
- <img src="/path/to/images/foo.png" />
- <img alt="Foo" src="/path/to/images/foo.png" />
+ <img src="/path/to/images/foo.png">
+ <img alt="Foo" src="/path/to/images/foo.png">
=head2 input_tag
@@ -445,9 +415,9 @@ Generate portable C<img> tag.
Generate C<input> tag. Previous input values will automatically get picked up
and shown as default.
- <input name="first_name" />
- <input name="first_name" value="Default name" />
- <input name="employed" type="checkbox" />
+ <input name="first_name">
+ <input name="first_name" value="Default name">
+ <input name="employed" type="checkbox">
=head2 javascript
@@ -458,7 +428,7 @@ and shown as default.
Generate portable C<script> tag for JavaScript asset.
- <script src="/path/to/script.js" />
+ <script src="/path/to/script.js"></script>
<script><![CDATA[
var a = 'b';
]]></script>
@@ -521,9 +491,9 @@ to using the capitalized link target as content.
Generate C<input> tag of type C<month>. Previous input values will
automatically get picked up and shown as default.
- <input name="vacation" type="month" />
- <input name="vacation" type="month" value="2012-12" />
- <input id="foo" name="vacation" type="month" value="2012-12" />
+ <input name="vacation" type="month">
+ <input name="vacation" type="month" value="2012-12">
+ <input id="foo" name="vacation" type="month" value="2012-12">
=head2 number_field
@@ -534,9 +504,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<number>. Previous input values will
automatically get picked up and shown as default.
- <input name="age" type="number" />
- <input name="age" type="number" value="25" />
- <input id="foo" max="200" min="0" name="age" type="number" value="25" />
+ <input name="age" type="number">
+ <input name="age" type="number" value="25">
+ <input id="foo" max="200" min="0" name="age" type="number" value="25">
=head2 password_field
@@ -545,8 +515,8 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<password>.
- <input name="pass" type="password" />
- <input id="foo" name="pass" type="password" />
+ <input name="pass" type="password">
+ <input id="foo" name="pass" type="password">
=head2 radio_button
@@ -556,8 +526,8 @@ Generate C<input> tag of type C<password>.
Generate C<input> tag of type C<radio>. Previous input values will
automatically get picked up and shown as default.
- <input name="country" type="radio" value="germany" />
- <input id="foo" name="country" type="radio" value="germany" />
+ <input name="country" type="radio" value="germany">
+ <input id="foo" name="country" type="radio" value="germany">
=head2 range_field
@@ -568,9 +538,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<range>. Previous input values will
automatically get picked up and shown as default.
- <input name="age" type="range" />
- <input name="age" type="range" value="25" />
- <input id="foo" max="200" min="200" name="age" type="range" value="25" />
+ <input name="age" type="range">
+ <input name="age" type="range" value="25">
+ <input id="foo" max="200" min="200" name="age" type="range" value="25">
=head2 search_field
@@ -581,9 +551,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<search>. Previous input values will
automatically get picked up and shown as default.
- <input name="q" type="search" />
- <input name="q" type="search" value="perl" />
- <input id="foo" name="q" type="search" value="perl" />
+ <input name="q" type="search">
+ <input name="q" type="search" value="perl">
+ <input id="foo" name="q" type="search" value="perl">
=head2 select_field
@@ -635,7 +605,7 @@ automatically get picked up and shown as default.
Generate portable C<style> or C<link> tag for CSS asset.
- <link href="/path/to/foo.css" rel="stylesheet" />
+ <link href="/path/to/foo.css" rel="stylesheet">
<style><![CDATA[
body {color: #000}
]]></style>
@@ -647,8 +617,8 @@ Generate portable C<style> or C<link> tag for CSS asset.
Generate C<input> tag of type C<submit>.
- <input type="submit" value="Ok" />
- <input id="foo" type="submit" value="Ok!" />
+ <input type="submit" value="Ok">
+ <input id="foo" type="submit" value="Ok!">
=head2 t
@@ -660,8 +630,9 @@ Alias for L</"tag">.
=head2 tag
+ %= tag 'br'
%= tag 'div'
- %= tag 'div', id => 'foo'
+ %= tag 'div', id => 'foo', hidden => undef
%= tag div => 'test & 123'
%= tag div => (id => 'foo') => 'test & 123'
%= tag div => (data => {my_id => 1, Name => 'test'}) => 'test & 123'
@@ -670,10 +641,12 @@ Alias for L</"tag">.
% end
<%= tag div => (id => 'foo') => begin %>test & 123<% end %>
-HTML/XML tag generator.
+HTML tag generator, the C<data> attribute may contain a hash reference with
+pairs to generate attributes from.
- <div />
- <div id="foo" />
+ <br>
+ <div></div>
+ <div id="foo" hidden></div>
<div>test & 123</div>
<div id="foo">test & 123</div>
<div data-my-id="1" data-name="test">test & 123</div>
@@ -684,8 +657,8 @@ HTML/XML tag generator.
Very useful for reuse in more specific tag helpers.
- my $output = $c->tag('div');
- my $output = $c->tag('div', id => 'foo');
+ my $output = $c->tag('meta');
+ my $output = $c->tag('meta', charset => 'UTF-8');
my $output = $c->tag(div => '<p>This will be escaped</p>');
my $output = $c->tag(div => sub { '<p>This will not be escaped</p>' });
@@ -698,7 +671,7 @@ accidental double escaping in C<ep> templates.
Same as L</"tag">, but adds the class C<field-with-error>.
- <input class="foo field-with-error" />
+ <input class="foo field-with-error">
=head2 tel_field
@@ -709,9 +682,9 @@ Same as L</"tag">, but adds the class C<field-with-error>.
Generate C<input> tag of type C<tel>. Previous input values will automatically
get picked up and shown as default.
- <input name="work" type="tel" />
- <input name="work" type="tel" value="123456789" />
- <input id="foo" name="work" type="tel" value="123456789" />
+ <input name="work" type="tel">
+ <input name="work" type="tel" value="123456789">
+ <input id="foo" name="work" type="tel" value="123456789">
=head2 text_area
@@ -741,9 +714,9 @@ up and shown as default.
Generate C<input> tag of type C<text>. Previous input values will
automatically get picked up and shown as default.
- <input name="first_name" type="text" />
- <input name="first_name" type="text" value="Default name" />
- <input class="user" name="first_name" type="text" value="Default name" />
+ <input name="first_name" type="text">
+ <input name="first_name" type="text" value="Default name">
+ <input class="user" name="first_name" type="text" value="Default name">
=head2 time_field
@@ -754,9 +727,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<time>. Previous input values will
automatically get picked up and shown as default.
- <input name="start" type="time" />
- <input name="start" type="time" value="23:59:59" />
- <input id="foo" name="start" type="time" value="23:59:59" />
+ <input name="start" type="time">
+ <input name="start" type="time" value="23:59:59">
+ <input id="foo" name="start" type="time" value="23:59:59">
=head2 url_field
@@ -767,9 +740,9 @@ automatically get picked up and shown as default.
Generate C<input> tag of type C<url>. Previous input values will automatically
get picked up and shown as default.
- <input name="address" type="url" />
- <input name="address" type="url" value="http://mojolicio.us" />
- <input id="foo" name="address" type="url" value="http://mojolicio.us" />
+ <input name="address" type="url">
+ <input name="address" type="url" value="http://mojolicio.us">
+ <input id="foo" name="address" type="url" value="http://mojolicio.us">
=head2 week_field
@@ -780,9 +753,9 @@ get picked up and shown as default.
Generate C<input> tag of type C<week>. Previous input values will
automatically get picked up and shown as default.
- <input name="vacation" type="week" />
- <input name="vacation" type="week" value="2012-W17" />
- <input id="foo" name="vacation" type="week" value="2012-W17" />
+ <input name="vacation" type="week">
+ <input name="vacation" type="week" value="2012-W17">
+ <input id="foo" name="vacation" type="week" value="2012-W17">
=head1 METHODS
@@ -233,7 +233,7 @@ sub _render_template {
}
# No handler
- else { $c->app->log->error(qq{No handler for "$handler" available.}) }
+ else { $c->app->log->error(qq{No handler for "$handler" available}) }
return undef;
}
@@ -32,7 +32,12 @@ sub add_child {
sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
-sub bridge { shift->route(@_)->inline(1) }
+# DEPRECATED in Tiger Face!
+sub bridge {
+ Mojo::Util::deprecated 'Mojolicious::Routes::Route::bridge is DEPRECATED in'
+ . ' favor of Mojolicious::Routes::Route::under';
+ shift->route(@_)->inline(1);
+}
sub delete { shift->_generate_route(DELETE => @_) }
@@ -297,25 +302,11 @@ current parent if necessary.
my $route = $r->any([qw(GET POST)] => '/:foo' => [foo => qr/\w+/]);
Generate L<Mojolicious::Routes::Route> object matching any of the listed HTTP
-request methods or all. See also the L<Mojolicious::Lite> tutorial for many
+request methods or all. See also L<Mojolicious::Guides::Tutorial> for many
more argument variations.
$r->any('/user')->to('user#whatever');
-=head2 bridge
-
- my $route = $r->bridge;
- my $route = $r->bridge('/:action');
- my $route = $r->bridge('/:action', action => qr/\w+/);
- my $route = $r->bridge(format => 0);
-
-Low-level generator for nested routes with their own intermediate destination,
-returns a L<Mojolicious::Routes::Route> object.
-
- my $auth = $r->bridge('/user')->to('user#auth');
- $auth->get('/show')->to('#show');
- $auth->post('/create')->to('#create');
-
=head2 delete
my $route = $r->delete('/:foo');
@@ -324,7 +315,7 @@ returns a L<Mojolicious::Routes::Route> object.
my $route = $r->delete('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<DELETE>
-requests. See also the L<Mojolicious::Lite> tutorial for many more argument
+requests. See also L<Mojolicious::Guides::Tutorial> for many more argument
variations.
$r->delete('/user')->to('user#remove');
@@ -356,7 +347,7 @@ generated ones.
my $route = $r->get('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<GET> requests.
-See also the L<Mojolicious::Lite> tutorial for many more argument variations.
+See also L<Mojolicious::Guides::Tutorial> for many more argument variations.
$r->get('/user')->to('user#show');
@@ -399,7 +390,9 @@ the current route.
=head2 new
my $r = Mojolicious::Routes::Route->new;
- my $r = Mojolicious::Routes::Route->new('/:controller/:action');
+ my $r = Mojolicious::Routes::Route->new('/:action');
+ my $r = Mojolicious::Routes::Route->new('/:action', action => qr/\w+/);
+ my $r = Mojolicious::Routes::Route->new(format => 0);
Construct a new L<Mojolicious::Routes::Route> object and L</"parse"> pattern
if necessary.
@@ -412,7 +405,7 @@ if necessary.
my $route = $r->options('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<OPTIONS>
-requests. See also the L<Mojolicious::Lite> tutorial for many more argument
+requests. See also L<Mojolicious::Guides::Tutorial> for many more argument
variations.
$r->options('/user')->to('user#overview');
@@ -445,7 +438,7 @@ Parse pattern.
my $route = $r->patch('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<PATCH> requests.
-See also the L<Mojolicious::Lite> tutorial for many more argument variations.
+See also L<Mojolicious::Guides::Tutorial> for many more argument variations.
$r->patch('/user')->to('user#update');
@@ -457,7 +450,7 @@ See also the L<Mojolicious::Lite> tutorial for many more argument variations.
my $route = $r->post('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<POST> requests.
-See also the L<Mojolicious::Lite> tutorial for many more argument variations.
+See also L<Mojolicious::Guides::Tutorial> for many more argument variations.
$r->post('/user')->to('user#create');
@@ -469,7 +462,7 @@ See also the L<Mojolicious::Lite> tutorial for many more argument variations.
my $route = $r->put('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only C<PUT> requests.
-See also the L<Mojolicious::Lite> tutorial for many more argument variations.
+See also L<Mojolicious::Guides::Tutorial> for many more argument variations.
$r->put('/user')->to('user#replace');
@@ -539,7 +532,7 @@ Stringify the whole route.
my $route = $r->under([format => 0]);
Generate L<Mojolicious::Routes::Route> object for a nested route with its own
-intermediate destination. See also the L<Mojolicious::Lite> tutorial for many
+intermediate destination. See also L<Mojolicious::Guides::Tutorial> for many
more argument variations.
my $auth = $r->under('/user')->to('user#auth');
@@ -566,7 +559,7 @@ restrictions.
my $route = $r->websocket('/:foo' => [foo => qr/\w+/] => sub {...});
Generate L<Mojolicious::Routes::Route> object matching only WebSocket
-handshakes. See also the L<Mojolicious::Lite> tutorial for many more argument
+handshakes. See also L<Mojolicious::Guides::Tutorial> for many more argument
variations.
$r->websocket('/echo')->to('example#echo');
@@ -21,7 +21,7 @@ sub auto_render {
my ($self, $c) = @_;
my $stash = $c->stash;
return if $stash->{'mojo.rendered'};
- $c->render_maybe or $stash->{'mojo.routed'} or $c->render_not_found;
+ $c->render_maybe or $stash->{'mojo.routed'} or $c->helpers->reply->not_found;
}
sub continue {
@@ -111,9 +111,9 @@ sub _add {
sub _callback {
my ($self, $c, $cb, $last) = @_;
- $c->stash->{'mojo.routed'}++ if $last;
+ $c->stash->{'mojo.routed'} = 1 if $last;
my $app = $c->app;
- $app->log->debug('Routing to a callback.');
+ $app->log->debug('Routing to a callback');
return _action($app, $c, $cb, $last);
}
@@ -143,7 +143,7 @@ sub _class {
# Failed
next unless defined(my $found = $self->_load($class));
- return !$log->debug(qq{Class "$class" is not a controller.}) unless $found;
+ return !$log->debug(qq{Class "$class" is not a controller}) unless $found;
# Success
my $new = $class->new(%$c);
@@ -152,7 +152,7 @@ sub _class {
}
# Nothing found
- $log->debug(qq{Controller "$classes[-1]" does not exist.}) if @classes;
+ $log->debug(qq{Controller "$classes[-1]" does not exist}) if @classes;
return @classes ? undef : 0;
}
@@ -168,7 +168,7 @@ sub _controller {
my $app = $old->app;
my $log = $app->log;
if ($new->isa('Mojo')) {
- $log->debug(qq{Routing to application "$class".});
+ $log->debug(qq{Routing to application "$class"});
# Try to connect routes
if (my $sub = $new->can('routes')) {
@@ -176,22 +176,22 @@ sub _controller {
weaken $r->parent($old->match->endpoint)->{parent} unless $r->parent;
}
$new->handler($old);
- $old->stash->{'mojo.routed'}++;
+ $old->stash->{'mojo.routed'} = 1;
}
# Action
elsif (my $method = $field->{action}) {
if (!$self->is_hidden($method)) {
- $log->debug(qq{Routing to controller "$class" and action "$method".});
+ $log->debug(qq{Routing to controller "$class" and action "$method"});
if (my $sub = $new->can($method)) {
- $old->stash->{'mojo.routed'}++ if $last;
+ $old->stash->{'mojo.routed'} = 1 if $last;
return 1 if _action($app, $new, $sub, $last);
}
- else { $log->debug('Action not found in controller.') }
+ else { $log->debug('Action not found in controller') }
}
- else { $log->debug(qq{Action "$method" is not allowed.}) }
+ else { $log->debug(qq{Action "$method" is not allowed}) }
}
return undef;
@@ -206,7 +206,7 @@ sub _load {
# Check base classes
return 0 unless first { $app->isa($_) } @{$self->base_classes};
- return ++$self->{loaded}{$app};
+ return $self->{loaded}{$app} = 1;
}
1;
@@ -34,7 +34,7 @@ sub dispatch {
# Serve static file and prevent directory traversal
return undef if $parts[0] eq '..' || !$self->serve($c, join('/', @parts));
- $stash->{'mojo.static'}++;
+ $stash->{'mojo.static'} = 1;
return !!$c->rendered;
}
@@ -14,9 +14,8 @@ sub AUTOLOAD {
Carp::croak "Undefined subroutine &${package}::$method called"
unless Scalar::Util::blessed $self && $self->isa(__PACKAGE__);
- Carp::croak qq{Can't locate object method "$method" via package "$package"}
- unless $self->validator->checks->{$method};
- return $self->check($method => @_);
+ return $self->check($method => @_) if $self->validator->checks->{$method};
+ Carp::croak qq{Can't locate object method "$method" via package "$package"};
}
sub check {
@@ -54,7 +53,10 @@ sub error {
return $self;
}
-sub every_param { shift->_param(@_) }
+sub every_param {
+ return [] unless defined(my $value = shift->output->{shift()});
+ return [ref $value eq 'ARRAY' ? @$value : $value];
+}
sub has_data { !!keys %{shift->input} }
@@ -82,7 +84,7 @@ sub param {
# List names
return sort keys %{$self->output} unless defined $name;
- return $self->_param($name)->[-1];
+ return $self->every_param($name)->[-1];
}
sub required {
@@ -91,11 +93,6 @@ sub required {
return $self->error($name => ['required']);
}
-sub _param {
- return [] unless defined(my $value = shift->output->{shift()});
- return [ref $value eq 'ARRAY' ? @$value : $value];
-}
-
1;
=encoding utf8
@@ -142,7 +142,7 @@
<div id="nothing" class="box spaced"></div>
% my $cv = begin
% my ($key, $value, $i) = @_;
- %= tag 'tr', $i ? (class => 'important') : undef, begin
+ %= tag 'tr', $i ? (class => 'important') : (), begin
<td class="key"><%= $key %></td>
<td class="value wide">
<pre class="prettyprint"><%= $value %></pre>
@@ -32,7 +32,7 @@ has secrets => sub {
my $self = shift;
# Warn developers about insecure default
- $self->log->debug('Your secret passphrase needs to be changed!!!');
+ $self->log->debug('Your secret passphrase needs to be changed');
# Default to moniker
return [$self->moniker];
@@ -43,7 +43,7 @@ has types => sub { Mojolicious::Types->new };
has validator => sub { Mojolicious::Validator->new };
our $CODENAME = 'Tiger Face';
-our $VERSION = '5.72';
+our $VERSION = '5.75';
sub AUTOLOAD {
my $self = shift;
@@ -102,13 +102,13 @@ sub dispatch {
my $req = $c->req;
my $method = $req->method;
my $path = $req->url->path->to_abs_string;
- $self->log->debug(qq{$method "$path".});
+ $self->log->debug(qq{$method "$path"});
$stash->{'mojo.started'} = [Time::HiRes::gettimeofday];
}
# Routes
$plugins->emit_hook(before_routes => $c);
- $c->render_not_found
+ $c->helpers->reply->not_found
unless $tx->res->code || $self->routes->dispatch($c) || $tx->res->code;
}
@@ -127,14 +127,14 @@ sub handler {
$self->plugins->emit_chain(around_dispatch => $c);
# Delayed response
- $self->log->debug('Nothing has been rendered, expecting delayed response.')
+ $self->log->debug('Nothing has been rendered, expecting delayed response')
unless $c->tx->is_writing;
}
sub helper {
my ($self, $name, $cb) = @_;
my $r = $self->renderer;
- $self->log->debug(qq{Helper "$name" already exists, replacing.})
+ $self->log->debug(qq{Helper "$name" already exists, replacing})
if exists $r->helpers->{$name};
$r->add_helper($name => $cb);
}
@@ -154,10 +154,9 @@ sub new {
# Hide controller attributes/methods
$r->hide(qw(app continue cookie every_cookie every_param));
$r->hide(qw(every_signed_cookie finish flash helpers match on param));
- $r->hide(qw(redirect_to render render_exception render_later render_maybe));
- $r->hide(qw(render_not_found render_to_string rendered req res respond_to));
- $r->hide(qw(send session signed_cookie stash tx url_for validation write));
- $r->hide(qw(write_chunk));
+ $r->hide(qw(redirect_to render render_later render_maybe render_to_string));
+ $r->hide(qw(rendered req res respond_to send session signed_cookie stash));
+ $r->hide(qw(tx url_for validation write write_chunk));
# Check if we have a log directory that is writable
my $mode = $self->mode;
@@ -195,7 +194,7 @@ sub _exception {
my ($next, $c) = @_;
local $SIG{__DIE__}
= sub { ref $_[0] ? CORE::die($_[0]) : Mojo::Exception->throw(@_) };
- $c->render_exception($@) unless eval { $next->(); 1 };
+ $c->helpers->reply->exception($@) unless eval { $next->(); 1 };
}
1;
@@ -563,6 +562,9 @@ request.
# Remove value
my $foo = delete $app->defaults->{foo};
+ # Assign multiple values at once
+ $app->defaults(foo => 'test', bar => 23);
+
=head2 dispatch
$app->dispatch(Mojolicious::Controller->new);
@@ -646,9 +648,11 @@ L<Mojolicious> distribution see L<Mojolicious::Plugins/"PLUGINS">.
$app->start(@ARGV);
Start the command line interface for your application, for a full list of
-commands available by default see L<Mojolicious::Commands/"COMMANDS">.
+commands available by default see L<Mojolicious::Commands/"COMMANDS">. Note
+that the options C<-h>/C<--help>, C<--home> and C<-m>/C<--mode>, which are
+shared by all commands, will be parsed from C<@ARGV> during compile time.
- # Always start daemon and ignore @ARGV
+ # Always start daemon
$app->start('daemon', '-l', 'http://*:8080');
=head2 startup
@@ -328,7 +328,7 @@ sub _message {
if (ref $value eq 'HASH') {
my $expect = exists $value->{text} ? 'text' : 'binary';
$value = $value->{$expect};
- $msg = '' unless $type eq $expect;
+ $msg = '' unless ($type // '') eq $expect;
}
# Decode text frame if there is no type check
@@ -93,7 +93,7 @@ L<ojo> implements the following functions, which are automatically exported.
Create a route with L<Mojolicious::Lite/"any"> and return the current
L<Mojolicious::Lite> object. The current controller object is also available
-to actions as C<$_>. See also the L<Mojolicious::Lite> tutorial for more
+to actions as C<$_>. See also L<Mojolicious::Guides::Tutorial> for more
argument variations.
$ perl -Mojo -E 'a("/hello" => {text => "Hello Mojo!"})->start' daemon
@@ -35,10 +35,10 @@ hypnotoad - Hypnotoad HTTP and WebSocket server
hypnotoad -f ./myapp.pl
Options:
- -f, --foreground Keep manager process in foreground.
- -h, --help Show this message.
- -s, --stop Stop server gracefully.
- -t, --test Test application and exit.
+ -f, --foreground Keep manager process in foreground
+ -h, --help Show this message
+ -s, --stop Stop server gracefully
+ -t, --test Test application and exit
=head1 DESCRIPTION
@@ -41,20 +41,20 @@ morbo - Morbo HTTP and WebSocket development server
morbo -w /usr/local/lib -w public ./myapp.pl
Options:
- -h, --help Show this message.
+ -h, --help Show this message
-l, --listen <location> One or more locations you want to listen
on, defaults to the value of MOJO_LISTEN or
- "http://*:3000".
+ "http://*:3000"
-m, --mode <name> Operating mode for your application,
defaults to the value of
- MOJO_MODE/PLACK_ENV or "development".
+ MOJO_MODE/PLACK_ENV or "development"
-v, --verbose Print details about what files changed to
- STDOUT.
+ STDOUT
-w, --watch <directory/file> One or more directories and files to watch
for changes, defaults to the application
script as well as the "lib" and "templates"
directories in the current working
- directory.
+ directory
=head1 DESCRIPTION
@@ -232,4 +232,20 @@ ok -e $path, 'file exists';
unlink $path;
ok !-e $path, 'file has been cleaned up';
+# Abstract methods
+eval { Mojo::Asset->add_chunk };
+like $@, qr/Method "add_chunk" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->contains };
+like $@, qr/Method "contains" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->get_chunk };
+like $@, qr/Method "get_chunk" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->move_to };
+like $@, qr/Method "move_to" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->mtime };
+like $@, qr/Method "mtime" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->size };
+like $@, qr/Method "size" not implemented by subclass/, 'right error';
+eval { Mojo::Asset->slurp };
+like $@, qr/Method "slurp" not implemented by subclass/, 'right error';
+
done_testing();
@@ -152,4 +152,7 @@ $file = catfile $dir, 'test.txt';
is b("just\nworks!")->spurt($file)->quote, qq{"just\nworks!"}, 'right result';
is b($file)->slurp, "just\nworks!", 'successful roundtrip';
+# term_escape
+is b("\t\b\r\n\f")->term_escape, "\\x09\\x08\\x0d\n\\x0c", 'right result';
+
done_testing();
@@ -88,4 +88,13 @@ $content->parse(
"Content-Length: 18446744073709551616\x0d\x0a\x0d\x0aHello World!");
is $content->asset->size, 12, 'right size';
+# Abstract methods
+eval { Mojo::Content->body_contains };
+like $@, qr/Method "body_contains" not implemented by subclass/, 'right error';
+eval { Mojo::Content->body_size };
+like $@, qr/Method "body_size" not implemented by subclass/, 'right error';
+eval { Mojo::Content->get_body_chunk };
+like $@, qr/Method "get_body_chunk" not implemented by subclass/,
+ 'right error';
+
done_testing();
@@ -448,4 +448,10 @@ is $cookies->[0]->expires->epoch, 942189160, 'right expires epoch value';
is $cookies->[0]->secure, 1, 'right secure flag';
is $cookies->[1], undef, 'no more cookies';
+# Abstract methods
+eval { Mojo::Cookie->parse };
+like $@, qr/Method "parse" not implemented by subclass/, 'right error';
+eval { Mojo::Cookie->to_string };
+like $@, qr/Method "to_string" not implemented by subclass/, 'right error';
+
done_testing();
@@ -271,4 +271,8 @@ ok !!Mojo::IOLoop->acceptor($id), 'acceptor has been added';
undef $daemon;
ok !Mojo::IOLoop->acceptor($id), 'acceptor has been removed';
+# Abstract methods
+eval { Mojo::Server->run };
+like $@, qr/Method "run" not implemented by subclass/, 'right error';
+
done_testing();
@@ -222,9 +222,9 @@ sleep 1 while _port($port2);
# Check log
$log = slurp $log;
-like $log, qr/Worker \d+ started\./, 'right message';
-like $log, qr/Starting zero downtime software upgrade\./, 'right message';
-like $log, qr/Upgrade successful, stopping $old\./, 'right message';
+like $log, qr/Worker \d+ started/, 'right message';
+like $log, qr/Starting zero downtime software upgrade/, 'right message';
+like $log, qr/Upgrade successful, stopping $old/, 'right message';
sub _pid {
return undef unless open my $file, '<', catdir($dir, 'hypnotoad.pid');
@@ -27,9 +27,10 @@ is ref $loop->reactor, 'MyReactor', 'right class';
my $err;
Mojo::IOLoop->next_tick(
sub {
- eval { Mojo::IOLoop->start };
+ my $loop = shift;
+ eval { $loop->start };
$err = $@;
- Mojo::IOLoop->stop;
+ $loop->stop;
}
);
Mojo::IOLoop->start;
@@ -47,6 +47,7 @@ is $pointer->new([{'foo~/bar' => 'bar'}])->get('/0/foo~0~1bar'), 'bar',
is $pointer->new([{'f~o~o~/b~' => {'a~' => {'r' => 'baz'}}}])
->get('/0/f~0o~0o~0~1b~0/a~0/r'), 'baz',
'"/0/f~0o~0o~0~1b~0/a~0/r" is "baz"';
+is $pointer->new({'~1' => 'foo'})->get('/~01'), 'foo', '"/~01" is "foo"';
# Unicode
is $pointer->new({'☃' => 'snowman'})->get('/☃'), 'snowman',
@@ -10,14 +10,14 @@ use Mojo::Util qw(decode slurp);
my $dir = tempdir CLEANUP => 1;
my $path = catdir $dir, 'test.log';
my $log = Mojo::Log->new(level => 'error', path => $path);
-$log->error('Just works.');
-$log->fatal('I ♥ Mojolicious.');
-$log->debug('Does not work.');
+$log->error('Just works');
+$log->fatal('I ♥ Mojolicious');
+$log->debug('Does not work');
undef $log;
my $content = decode 'UTF-8', slurp($path);
-like $content, qr/\[.*\] \[error\] Just works\./, 'right error message';
-like $content, qr/\[.*\] \[fatal\] I ♥ Mojolicious\./, 'right fatal message';
-unlike $content, qr/\[.*\] \[debug\] Does not work\./, 'no debug message';
+like $content, qr/\[.*\] \[error\] Just works/, 'right error message';
+like $content, qr/\[.*\] \[fatal\] I ♥ Mojolicious/, 'right fatal message';
+unlike $content, qr/\[.*\] \[debug\] Does not work/, 'no debug message';
# Logging to STDERR
my $buffer = '';
@@ -25,24 +25,23 @@ my $buffer = '';
open my $handle, '>', \$buffer;
local *STDERR = $handle;
my $log = Mojo::Log->new;
- $log->error('Just works.');
- $log->fatal('I ♥ Mojolicious.');
- $log->debug('Works too.');
+ $log->error('Just works');
+ $log->fatal('I ♥ Mojolicious');
+ $log->debug('Works too');
}
$content = decode 'UTF-8', $buffer;
-like $content, qr/\[.*\] \[error\] Just works\.\n/, 'right error message';
-like $content, qr/\[.*\] \[fatal\] I ♥ Mojolicious\.\n/,
- 'right fatal message';
-like $content, qr/\[.*\] \[debug\] Works too\.\n/, 'right debug message';
+like $content, qr/\[.*\] \[error\] Just works\n/, 'right error message';
+like $content, qr/\[.*\] \[fatal\] I ♥ Mojolicious\n/, 'right fatal message';
+like $content, qr/\[.*\] \[debug\] Works too\n/, 'right debug message';
# Formatting
$log = Mojo::Log->new;
-like $log->format->(time, 'debug', 'Test 123.'),
- qr/^\[.*\] \[debug\] Test 123\.\n$/, 'right format';
+like $log->format->(time, 'debug', 'Test 123'),
+ qr/^\[.*\] \[debug\] Test 123\n$/, 'right format';
like $log->format->(time, 'debug', qw(Test 1 2 3)),
qr/^\[.*\] \[debug\] Test\n1\n2\n3\n$/, 'right format';
-like $log->format->(time, 'error', 'I ♥ Mojolicious.'),
- qr/^\[.*\] \[error\] I ♥ Mojolicious\.\n$/, 'right format';
+like $log->format->(time, 'error', 'I ♥ Mojolicious'),
+ qr/^\[.*\] \[error\] I ♥ Mojolicious\n$/, 'right format';
$log->format(
sub {
my ($time, $level, @lines) = @_;
@@ -86,22 +85,22 @@ my $history;
open my $handle, '>', \$buffer;
local *STDERR = $handle;
my $log = Mojo::Log->new->max_history_size(2)->level('info');
- $log->error('First.');
- $log->fatal('Second.');
- $log->debug('Third.');
- $log->info('Fourth.', 'Fifth.');
+ $log->error('First');
+ $log->fatal('Second');
+ $log->debug('Third');
+ $log->info('Fourth', 'Fifth');
$history = $log->history;
}
$content = decode 'UTF-8', $buffer;
-like $content, qr/\[.*\] \[error\] First\.\n/, 'right error message';
-like $content, qr/\[.*\] \[info\] Fourth\.\nFifth.\n/, 'right info message';
-unlike $content, qr/debug/, 'no debug message';
+like $content, qr/\[.*\] \[error\] First\n/, 'right error message';
+like $content, qr/\[.*\] \[info\] Fourth\nFifth\n/, 'right info message';
+unlike $content, qr/debug/, 'no debug message';
like $history->[0][0], qr/^\d+$/, 'right epoch time';
is $history->[0][1], 'fatal', 'right level';
-is $history->[0][2], 'Second.', 'right message';
+is $history->[0][2], 'Second', 'right message';
is $history->[1][1], 'info', 'right level';
-is $history->[1][2], 'Fourth.', 'right message';
-is $history->[1][3], 'Fifth.', 'right message';
+is $history->[1][2], 'Fourth', 'right message';
+is $history->[1][3], 'Fifth', 'right message';
ok !$history->[2], 'no more messages';
# "debug"
@@ -66,17 +66,17 @@ is $params->append($params2)->to_string, 'bar=bar&foo=&x=1&y=2',
is $params2->to_string, 'x=1&y=2', 'right format';
# "0"
-$params = Mojo::Parameters->new(foo => 0);
-is $params->param('foo'), 0, 'right value';
-is_deeply $params->every_param('foo'), [0], 'right value';
-is_deeply $params->every_param('bar'), [], 'no values';
-is $params->to_string, 'foo=0', 'right format';
+$params = Mojo::Parameters->new(0 => 0);
+is $params->param(0), 0, 'right value';
+is_deeply $params->every_param(0), [0], 'right value';
+is_deeply $params->every_param('foo'), [], 'no values';
+is $params->to_string, '0=0', 'right format';
$params = Mojo::Parameters->new($params->to_string);
-is $params->param('foo'), 0, 'right value';
-is_deeply $params->every_param('foo'), [0], 'right value';
-is $params->to_hash->{foo}, 0, 'right value';
-is_deeply $params->to_hash, {foo => 0}, 'right structure';
-is $params->to_string, 'foo=0', 'right format';
+is $params->param(0), 0, 'right value';
+is_deeply $params->every_param(0), [0], 'right value';
+is $params->to_hash->{0}, 0, 'right value';
+is_deeply $params->to_hash, {0 => 0}, 'right structure';
+is $params->to_string, '0=0', 'right format';
# Semicolon
$params = Mojo::Parameters->new('foo=bar;baz');
@@ -41,13 +41,14 @@ $prefork->on(
}
);
is $prefork->workers, 4, 'start with four workers';
-my (@spawn, @reap, $worker, $tx, $graceful);
+my (@spawn, @reap, $worker, $tx, $graceful, $healthy);
$prefork->on(spawn => sub { push @spawn, pop });
$prefork->once(
heartbeat => sub {
my ($prefork, $pid) = @_;
- $worker = $pid;
- $tx = Mojo::UserAgent->new->get("http://127.0.0.1:$port");
+ $worker = $pid;
+ $healthy = $prefork->healthy;
+ $tx = Mojo::UserAgent->new->get("http://127.0.0.1:$port");
kill 'QUIT', $$;
}
);
@@ -55,7 +56,9 @@ $prefork->on(reap => sub { push @reap, pop });
$prefork->on(finish => sub { $graceful = pop });
my $log = '';
my $cb = $prefork->app->log->on(message => sub { $log .= pop });
+is $prefork->healthy, 0, 'no healthy workers';
$prefork->run;
+ok $healthy >= 1, 'healthy workers';
is scalar @spawn, 4, 'four workers spawned';
is scalar @reap, 4, 'four workers reaped';
ok !!grep { $worker eq $_ } @spawn, 'worker has a heartbeat';
@@ -63,11 +66,11 @@ ok $graceful, 'server has been stopped gracefully';
is_deeply [sort @spawn], [sort @reap], 'same process ids';
is $tx->res->code, 200, 'right status';
is $tx->res->body, 'just works!', 'right content';
-like $log, qr/Listening at/, 'right message';
-like $log, qr/Manager $$ started\./, 'right message';
-like $log, qr/Creating process id file/, 'right message';
-like $log, qr/Trying to stop worker $spawn[0] gracefully\./, 'right message';
-like $log, qr/Worker $spawn[0] stopped\./, 'right message';
+like $log, qr/Listening at/, 'right message';
+like $log, qr/Manager $$ started/, 'right message';
+like $log, qr/Creating process id file/, 'right message';
+like $log, qr/Stopping worker $spawn[0] gracefully/, 'right message';
+like $log, qr/Worker $spawn[0] stopped/, 'right message';
$prefork->app->log->unsubscribe(message => $cb);
# Process id and lock files
@@ -269,4 +269,28 @@ like $client_err, qr/^Mojo::IOLoop already running/, 'right error';
ok $server_running, 'loop is running';
ok $client_running, 'loop is running';
+# Abstract methods
+eval { Mojo::Reactor->again };
+like $@, qr/Method "again" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->io };
+like $@, qr/Method "io" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->is_running };
+like $@, qr/Method "is_running" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->one_tick };
+like $@, qr/Method "one_tick" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->recurring };
+like $@, qr/Method "recurring" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->remove };
+like $@, qr/Method "remove" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->reset };
+like $@, qr/Method "reset" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->start };
+like $@, qr/Method "start" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->stop };
+like $@, qr/Method "stop" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->timer };
+like $@, qr/Method "timer" not implemented by subclass/, 'right error';
+eval { Mojo::Reactor->watch };
+like $@, qr/Method "watch" not implemented by subclass/, 'right error';
+
done_testing();
@@ -78,7 +78,6 @@ $req = Mojo::Message::Request->new;
$req->parse("12345\x0d\x0a");
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Bad request start-line', 'right error';
-is $req->error->{advice}, 400, 'right advice';
ok !$req->is_limit_exceeded, 'limit is not exceeded';
# Parse broken HTTP 1.1 message with header exceeding line limit
@@ -89,7 +88,6 @@ ok !$req->is_limit_exceeded, 'limit is not exceeded';
$req->parse("Foo: @{['a' x 10240]}");
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum header size exceeded', 'right error';
-is $req->error->{advice}, 431, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
is $req->method, 'GET', 'right method';
is $req->version, '1.1', 'right version';
@@ -352,12 +350,10 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse('GET /foo/bar/baz.html HTTP/1');
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum start-line size exceeded', 'right error';
- is $req->error->{advice}, 431, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
ok $limit, 'limit is exceeded';
$req->error({message => 'Nothing important.'});
is $req->error->{message}, 'Nothing important.', 'right error';
- is $req->error->{advice}, undef, 'no advice';
ok $req->is_limit_exceeded, 'limit is still exceeded';
}
@@ -370,7 +366,6 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse("Content-Type: text/plain\x0d\x0a");
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum header size exceeded', 'right error';
- is $req->error->{advice}, 431, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
}
@@ -384,7 +379,6 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse('GET /foo/bar/baz.html HTTP/1');
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum message size exceeded', 'right error';
- is $req->error->{advice}, 413, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
ok $limit, 'limit is exceeded';
}
@@ -397,7 +391,6 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse("Content-Type: text/plain\x0d\x0a");
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum message size exceeded', 'right error';
- is $req->error->{advice}, 413, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
}
@@ -411,7 +404,6 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse('Hello World!');
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum message size exceeded', 'right error';
- is $req->error->{advice}, 413, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
}
@@ -426,7 +418,6 @@ is $req->headers->content_length, undef, 'no "Content-Length" value';
$req->parse("D: d\x0d\x0a\x0d\x0a");
ok $req->is_finished, 'request is finished';
is $req->error->{message}, 'Maximum header size exceeded', 'right error';
- is $req->error->{advice}, 431, 'right advice';
ok $req->is_limit_exceeded, 'limit is exceeded';
is $req->method, 'GET', 'right method';
is $req->version, '1.1', 'right version';
@@ -2089,4 +2080,14 @@ is $req->version, '1.1', 'right version';
is $req->url, '/#09azAZ!$%&\'()*+,-./:;=?@%5B%5C%5D%5E_%60%7B%7C%7D~',
'right URL';
+# Abstract methods
+eval { Mojo::Message->cookies };
+like $@, qr/Method "cookies" not implemented by subclass/, 'right error';
+eval { Mojo::Message->extract_start_line };
+like $@, qr/Method "extract_start_line" not implemented by subclass/,
+ 'right error';
+eval { Mojo::Message->get_start_line_chunk };
+like $@, qr/Method "get_start_line_chunk" not implemented by subclass/,
+ 'right error';
+
done_testing();
@@ -373,7 +373,6 @@ is $res->headers->content_length, undef, 'right "Content-Length" value';
ok $res->is_finished, 'response is finished';
ok $res->content->is_finished, 'content is finished';
is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
- is $res->error->{advice}, 400, 'right advice';
ok $res->is_limit_exceeded, 'limit is not exceeded';
is $res->code, 200, 'right status';
is $res->message, 'OK', 'right message';
@@ -396,7 +395,6 @@ is $res->headers->content_length, undef, 'right "Content-Length" value';
ok $res->is_finished, 'response is finished';
ok $res->content->is_finished, 'content is finished';
is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
- is $res->error->{advice}, 400, 'right advice';
is $res->code, 200, 'right status';
is $res->message, 'OK', 'right message';
is $res->version, '1.1', 'right version';
@@ -420,7 +418,6 @@ is $res->headers->content_length, undef, 'right "Content-Length" value';
ok $res->is_finished, 'response is finished';
ok $res->content->is_finished, 'content is finished';
is $res->error->{message}, 'Maximum buffer size exceeded', 'right error';
- is $res->error->{advice}, 400, 'right advice';
is $res->code, 200, 'right status';
is $res->message, 'OK', 'right message';
is $res->version, '1.1', 'right version';
@@ -859,4 +859,14 @@ is $tx->req->body, '', 'no content';
is $tx->res->code, undef, 'no status';
is $tx->res->headers->location, undef, 'no "Location" value';
+# Abstract methods
+eval { Mojo::Transaction->client_read };
+like $@, qr/Method "client_read" not implemented by subclass/, 'right error';
+eval { Mojo::Transaction->client_write };
+like $@, qr/Method "client_write" not implemented by subclass/, 'right error';
+eval { Mojo::Transaction->server_read };
+like $@, qr/Method "server_read" not implemented by subclass/, 'right error';
+eval { Mojo::Transaction->server_write };
+like $@, qr/Method "server_write" not implemented by subclass/, 'right error';
+
done_testing();
@@ -294,7 +294,7 @@ app->log->unsubscribe(message => $msg);
ok !$tx->success, 'not successful';
is $tx->error->{message}, 'Premature connection close', 'right error';
is $timeout, 1, 'finish event has been emitted';
-like $log, qr/Inactivity timeout\./, 'right log message';
+like $log, qr/Inactivity timeout/, 'right log message';
# Client times out
$ua->once(
@@ -335,8 +335,7 @@ $ua->once(
$tx = $ua->get('/echo' => 'Hello World!');
ok !$tx->success, 'not successful';
is $tx->error->{message}, 'Maximum message size exceeded', 'right error';
-is $tx->error->{advice}, 413, 'right advice';
-is $tx->error->{code}, undef, 'no status';
+is $tx->error->{code}, undef, 'no status';
ok $tx->res->is_limit_exceeded, 'limit is exceeded';
# 404 response
@@ -16,8 +16,8 @@ use Mojo::Util
qw(decode dumper encode hmac_sha1_sum html_unescape md5_bytes md5_sum),
qw(monkey_patch punycode_decode punycode_encode quote secure_compare),
qw(secure_compare sha1_bytes sha1_sum slurp split_header spurt squish),
- qw(steady_time tablify trim unindent unquote url_escape url_unescape),
- qw(xml_escape xor_encode xss_escape);
+ qw(steady_time tablify term_escape trim unindent unquote url_escape),
+ qw(url_unescape xml_escape xor_encode xss_escape);
# camelize
is camelize('foo_bar_baz'), 'FooBarBaz', 'right camelized result';
@@ -417,7 +417,8 @@ is MojoMonkeyTest::yang(), 'yang', 'right result';
# monkey_patch (with name)
SKIP: {
- skip 'Sub::Util required!', 2 unless eval { require Sub::Util; 1 };
+ skip 'Sub::Util required!', 2
+ unless eval { require Sub::Util; !!Sub::Util->can('set_subname') };
is Sub::Util::subname(MojoMonkeyTest->can('foo')), 'MojoMonkeyTest::foo',
'right name';
is Sub::Util::subname(MojoMonkeyTest->can('bar')), 'MojoMonkeyTest::bar',
@@ -453,4 +454,10 @@ is tablify([['a', '', 'b'], ['c', '', 'd']]), "a b\nc d\n",
# dumper
is dumper([1, 2]), "[\n 1,\n 2\n]\n", 'right result';
+# term_escape
+is term_escape("Accept: */*\x0d\x0a"), "Accept: */*\\x0d\x0a", 'right result';
+is term_escape("\t\b\r\n\f"), "\\x09\\x08\\x0d\n\\x0c", 'right result';
+is term_escape("\x00\x09\x0b\x1f\x7f\x80\x9f"),
+ '\x00\x09\x0b\x1f\x7f\x80\x9f', 'right result';
+
done_testing();
@@ -448,7 +448,7 @@ $ua->websocket(
);
Mojo::IOLoop->start;
is $stash->{finished}, 1, 'finish event has been emitted once';
-like $log, qr/Inactivity timeout\./, 'right log message';
+like $log, qr/Inactivity timeout/, 'right log message';
app->log->unsubscribe(message => $msg);
# Ping/pong
@@ -115,10 +115,8 @@ ok $t->app->routes->is_hidden('on'), 'is hidden';
ok $t->app->routes->is_hidden('param'), 'is hidden';
ok $t->app->routes->is_hidden('redirect_to'), 'is hidden';
ok $t->app->routes->is_hidden('render'), 'is hidden';
-ok $t->app->routes->is_hidden('render_exception'), 'is hidden';
ok $t->app->routes->is_hidden('render_later'), 'is hidden';
ok $t->app->routes->is_hidden('render_maybe'), 'is hidden';
-ok $t->app->routes->is_hidden('render_not_found'), 'is hidden';
ok $t->app->routes->is_hidden('render_to_string'), 'is hidden';
ok $t->app->routes->is_hidden('rendered'), 'is hidden';
ok $t->app->routes->is_hidden('req'), 'is hidden';
@@ -146,7 +144,7 @@ my $log = '';
my $cb = $t->app->log->on(message => sub { $log .= pop });
$t->app->helper(replaced_helper => sub { });
$t->app->helper(replaced_helper => sub { });
-like $log, qr/Helper "replaced_helper" already exists, replacing\./,
+like $log, qr/Helper "replaced_helper" already exists, replacing/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -176,7 +174,7 @@ $t->get_ok('/plugin-test-some_plugin2/register')->status_isnt(500)
->status_is(404)->header_is(Server => 'Mojolicious (Perl)')
->content_unlike(qr/Something/)->content_like(qr/Page not found/);
like $log,
- qr/Class "MojoliciousTest::Plugin::Test::SomePlugin2" is not a controller\./,
+ qr/Class "MojoliciousTest::Plugin::Test::SomePlugin2" is not a controller/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -196,7 +194,7 @@ $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/foo/baz')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_unlike(qr/Something/)
->content_like(qr/Page not found/);
-like $log, qr/Action not found in controller\./, 'right message';
+like $log, qr/Action not found in controller/, 'right message';
$t->app->log->unsubscribe(message => $cb);
# Foo::render (action not allowed)
@@ -205,7 +203,7 @@ $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/foo/render')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')
->content_like(qr/Page not found/);
-like $log, qr/Action "render" is not allowed\./, 'right message';
+like $log, qr/Action "render" is not allowed/, 'right message';
$t->app->log->unsubscribe(message => $cb);
# Foo::yada (action-less template)
@@ -224,11 +222,11 @@ $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/foo/syntaxerror')->status_is(500)
->header_is(Server => 'Mojolicious (Perl)')
->content_like(qr/Missing right curly/);
-like $log, qr/Rendering template "syntaxerror.html.epl"\./, 'right message';
+like $log, qr/Rendering template "syntaxerror.html.epl"/, 'right message';
like $log, qr/Missing right curly/, 'right message';
-like $log, qr/Template "exception.development.html.ep" not found\./,
+like $log, qr/Template "exception.development.html.ep" not found/,
'right message';
-like $log, qr/Rendering template "exception.html.epl"\./, 'right message';
+like $log, qr/Rendering template "exception.html.epl"/, 'right message';
like $log, qr/500 Internal Server Error/, 'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -268,8 +266,7 @@ $url->path('/fun/time');
$t->get_ok($url => {'X-Test' => 'Hi there!'})->status_is(200)
->header_is('X-Bender' => undef)->header_is(Server => 'Mojolicious (Perl)')
->content_is('Have fun!');
-like $log,
- qr!Rendering cached template "foo/fun\.html\.ep" from DATA section\.!,
+like $log, qr!Rendering cached template "foo/fun\.html\.ep" from DATA section!,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -372,7 +369,7 @@ $log = '';
$cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/another')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)');
-like $log, qr/Controller "MojoliciousTest::Another" does not exist\./,
+like $log, qr/Controller "MojoliciousTest::Another" does not exist/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -498,7 +495,7 @@ $log = '';
$cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/redispatch')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('Redispatch!');
-like $log, qr/Routing to application "SingleFileTestApp::Redispatch"\./,
+like $log, qr/Routing to application "SingleFileTestApp::Redispatch"/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -538,13 +535,13 @@ $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/suspended')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('X-Suspended' => '0, 1, 1, 2')->content_is('Have fun!');
-like $log, qr!GET "/suspended"\.!, 'right message';
+like $log, qr!GET "/suspended"!, 'right message';
like $log,
- qr/Routing to controller "MojoliciousTest::Foo" and action "suspended"\./,
+ qr/Routing to controller "MojoliciousTest::Foo" and action "suspended"/,
'right message';
-like $log, qr/Routing to controller "MojoliciousTest::Foo" and action "fun"\./,
+like $log, qr/Routing to controller "MojoliciousTest::Foo" and action "fun"/,
'right message';
-like $log, qr!Rendering template "foo/fun.html.ep" from DATA section\.!,
+like $log, qr!Rendering template "foo/fun.html.ep" from DATA section!,
'right message';
like $log, qr/200 OK/, 'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -579,4 +576,8 @@ $t->get_ok('/foo/session')->status_is(200)
$t->get_ok('/rss.xml')->status_is(200)->content_type_is('application/rss+xml')
->content_like(qr!<\?xml version="1.0" encoding="UTF-8"\?><rss />!);
+# Abstract methods
+eval { Mojolicious::Plugin->register };
+like $@, qr/Method "register" not implemented by subclass/, 'right error';
+
done_testing();
@@ -9,7 +9,7 @@ use File::Temp 'tempdir';
use Mojolicious::Command;
# Application
-my $command = Mojolicious::Command->new;
+my $command = Mojolicious::Command->new(quiet => 1);
isa_ok $command->app, 'Mojo', 'right application';
isa_ok $command->app, 'Mojolicious', 'right application';
@@ -33,4 +33,8 @@ open my $xml, '<', $command->rel_file('123.xml');
is join('', <$xml>), "seems\nto\nwork", 'right result';
chdir $cwd;
+# Abstract methods
+eval { Mojolicious::Command->run };
+like $@, qr/Method "run" not implemented by subclass/, 'right error';
+
done_testing();
@@ -10,6 +10,9 @@ use Test::More;
use FindBin;
use lib "$FindBin::Bin/lib";
+use Cwd 'cwd';
+use File::Temp 'tempdir';
+
# Make sure @ARGV is not changed
{
local $ENV{MOJO_MODE};
@@ -33,6 +36,16 @@ my $commands = Mojolicious::Commands->new;
local $ENV{GATEWAY_INTERFACE} = 'CGI/1.1';
is $commands->detect, 'cgi', 'right environment';
}
+{
+ local @ENV{qw(PLACK_ENV PATH_INFO GATEWAY_INTERFACE)};
+ is $commands->detect, undef, 'no environment';
+}
+{
+ local $ENV{PLACK_ENV} = 'production';
+ is ref Mojolicious::Commands->new->run, 'CODE', 'right reference';
+ local $ENV{MOJO_NO_DETECT} = 1;
+ isnt ref Mojolicious::Commands->new->run, 'CODE', 'not a CODE reference';
+}
# Run command
is ref Mojolicious::Commands->new->run('psgi'), 'CODE', 'right reference';
@@ -76,6 +89,40 @@ is $app->start('test_command'), 'works!', 'right result';
ok $commands->description, 'has a description';
like $commands->message, qr/COMMAND/, 'has a message';
like $commands->hint, qr/help/, 'has a hint';
+my $buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ local $ENV{HARNESS_ACTIVE} = 0;
+ $commands->run;
+}
+like $buffer, qr/Usage: APPLICATION COMMAND \[OPTIONS\].*daemon.*version/s,
+ 'right output';
+
+# help
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $commands->run('help', 'generate', 'lite_app');
+}
+like $buffer, qr/Usage: APPLICATION generate lite_app \[NAME\]/,
+ 'right output';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $commands->run('generate', 'app', '-h');
+}
+like $buffer, qr/Usage: APPLICATION generate app \[NAME\]/, 'right output';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $commands->run('generate', 'lite_app', '--help');
+}
+like $buffer, qr/Usage: APPLICATION generate lite_app \[NAME\]/,
+ 'right output';
# cgi
require Mojolicious::Command::cgi;
@@ -100,43 +147,149 @@ require Mojolicious::Command::eval;
my $eval = Mojolicious::Command::eval->new;
ok $eval->description, 'has a description';
like $eval->usage, qr/eval/, 'has usage information';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $eval->run('-v', 'app->controller_class');
+}
+like $buffer, qr/Mojolicious::Controller/, 'right output';
# generate
require Mojolicious::Command::generate;
my $generator = Mojolicious::Command::generate->new;
ok $generator->description, 'has a description';
like $generator->message, qr/generate/, 'has a message';
-like $commands->hint, qr/help/, 'has a hint';
+like $generator->hint, qr/help/, 'has a hint';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ local $ENV{HARNESS_ACTIVE} = 0;
+ $generator->run;
+}
+like $buffer,
+ qr/Usage: APPLICATION generate GENERATOR \[OPTIONS\].*lite_app.*plugin/s,
+ 'right output';
# generate app
require Mojolicious::Command::generate::app;
$app = Mojolicious::Command::generate::app->new;
ok $app->description, 'has a description';
like $app->usage, qr/app/, 'has usage information';
+my $cwd = cwd;
+my $dir = tempdir CLEANUP => 1;
+chdir $dir;
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $app->run;
+}
+like $buffer, qr/my_app/, 'right output';
+ok -e $app->rel_file('my_app/script/my_app'), 'script exists';
+ok -e $app->rel_file('my_app/lib/MyApp.pm'), 'application class exists';
+ok -e $app->rel_file('my_app/lib/MyApp/Controller/Example.pm'),
+ 'controller exists';
+ok -e $app->rel_file('my_app/t/basic.t'), 'test exists';
+ok -e $app->rel_file('my_app/public/index.html'), 'static file exists';
+ok -e $app->rel_file('my_app/templates/layouts/default.html.ep'),
+ 'layout exists';
+ok -e $app->rel_file('my_app/templates/example/welcome.html.ep'),
+ 'template exists';
+chdir $cwd;
# generate lite_app
require Mojolicious::Command::generate::lite_app;
$app = Mojolicious::Command::generate::lite_app->new;
ok $app->description, 'has a description';
like $app->usage, qr/lite_app/, 'has usage information';
+$dir = tempdir CLEANUP => 1;
+chdir $dir;
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $app->run;
+}
+like $buffer, qr/myapp\.pl/, 'right output';
+ok -e $app->rel_file('myapp.pl'), 'app exists';
+chdir $cwd;
# generate makefile
require Mojolicious::Command::generate::makefile;
my $makefile = Mojolicious::Command::generate::makefile->new;
ok $makefile->description, 'has a description';
like $makefile->usage, qr/makefile/, 'has usage information';
+$dir = tempdir CLEANUP => 1;
+chdir $dir;
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $makefile->run;
+}
+like $buffer, qr/Makefile\.PL/, 'right output';
+ok -e $app->rel_file('Makefile.PL'), 'Makefile.PL exists';
+chdir $cwd;
# generate plugin
require Mojolicious::Command::generate::plugin;
my $plugin = Mojolicious::Command::generate::plugin->new;
ok $plugin->description, 'has a description';
like $plugin->usage, qr/plugin/, 'has usage information';
+$dir = tempdir CLEANUP => 1;
+chdir $dir;
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $plugin->run;
+}
+like $buffer, qr/MyPlugin\.pm/, 'right output';
+ok -e $app->rel_file(
+ 'Mojolicious-Plugin-MyPlugin/lib/Mojolicious/Plugin/MyPlugin.pm'),
+ 'class exists';
+ok -e $app->rel_file('Mojolicious-Plugin-MyPlugin/t/basic.t'), 'test exists';
+ok -e $app->rel_file('Mojolicious-Plugin-MyPlugin/Makefile.PL'),
+ 'Makefile.PL exists';
+chdir $cwd;
# get
require Mojolicious::Command::get;
my $get = Mojolicious::Command::get->new;
ok $get->description, 'has a description';
like $get->usage, qr/get/, 'has usage information';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $get->run('/');
+}
+like $buffer, qr/Your Mojo is working!/, 'right output';
+$get->app->hook(
+ before_dispatch => sub {
+ my $c = shift;
+ return $c->render(text => '<p>works</p>')
+ if $c->req->url->path->contains('/html');
+ $c->render(json => {works => 'too'})
+ if $c->req->url->path->contains('/json');
+ }
+);
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $get->run('/html', 'p', 'text');
+}
+like $buffer, qr/works/, 'right output';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $get->run('/json', '/works');
+}
+like $buffer, qr/too/, 'right output';
# inflate
require Mojolicious::Command::inflate;
@@ -161,6 +314,22 @@ require Mojolicious::Command::routes;
my $routes = Mojolicious::Command::routes->new;
ok $routes->description, 'has a description';
like $routes->usage, qr/routes/, 'has usage information';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $routes->run;
+}
+like $buffer, qr!/\*whatever!, 'right output';
+unlike $buffer, qr!/\(\.\+\)\?!, 'not verbose';
+$buffer = '';
+{
+ open my $handle, '>', \$buffer;
+ local *STDOUT = $handle;
+ $routes->run('-v');
+}
+like $buffer, qr!/\*whatever!, 'right output';
+like $buffer, qr!/\(\.\+\)\?!, 'verbose';
# test
require Mojolicious::Command::test;
@@ -41,8 +41,6 @@ $t->get_ok('/hello')->status_is(200)->content_is("Hello from the main app!\n");
# Session
$t->get_ok('/primary')->status_is(200)->content_is(1);
-
-# Session again
$t->get_ok('/primary')->status_is(200)->content_is(2);
# Session in external app
@@ -177,7 +177,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/x/1/">Test</a>
<form action="/x/1/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -210,7 +210,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/x/%E2%99%A5/">Test</a>
<form action="/x/%E2%99%A5/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -278,7 +278,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/">Test</a>
<form action="/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -295,7 +295,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/">Test</a>
<form action="/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -312,7 +312,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/">Test</a>
<form action="/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -337,7 +337,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/%E2%99%A5/123/">Test</a>
<form action="/%E2%99%A5/123/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -22,7 +22,7 @@ works ♥!Insecure!Insecure!
too!works!!!Mojolicious::Plugin::Config::Sandbox
<a href="/">Test</a>
<form action="/%E2%98%83">
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -213,11 +213,11 @@ my $log = '';
my $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/suspended?ok=1')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('suspended!');
-like $log, qr!GET "/suspended"\.!, 'right message';
-like $log, qr/Routing to a callback\./, 'right message';
-like $log, qr/Nothing has been rendered, expecting delayed response\./,
+like $log, qr!GET "/suspended"!, 'right message';
+like $log, qr/Routing to a callback/, 'right message';
+like $log, qr/Nothing has been rendered, expecting delayed response/,
'right message';
-like $log, qr/Rendering inline template "f75d6f5993c626fa8049366389f77928"\./,
+like $log, qr/Rendering inline template "f75d6f5993c626fa8049366389f77928"/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -271,8 +271,8 @@ $cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/bridge2stash')->status_is(200)
->content_is(
"stash too!cookie!!signed_cookie!!bad_cookie--12345678!session!flash!\n");
-like $log, qr/Cookie "foo" not signed\./, 'right message';
-like $log, qr/Cookie "bad" has bad signature\./, 'right message';
+like $log, qr/Cookie "foo" is not signed/, 'right message';
+like $log, qr/Cookie "bad" has a bad signature/, 'right message';
ok $t->tx->res->cookie('mojolicious')->httponly,
'session cookie has HttpOnly flag';
$t->app->log->unsubscribe(message => $cb);
@@ -28,7 +28,7 @@ my $log = '';
my $cb = app->log->on(message => sub { $log .= pop });
$path = abs_path catfile(dirname(__FILE__), 'json_config_lite_app_abs.json');
plugin JSONConfig => {file => $path};
-like $log, qr/Reading configuration file "\Q$path\E"\./, 'right message';
+like $log, qr/Reading configuration file "\Q$path\E"/, 'right message';
app->log->unsubscribe(message => $cb);
is $config->{foo}, 'bar', 'right value';
is $config->{hello}, 'there', 'right value';
@@ -68,8 +68,8 @@ sub startup {
$r->route('/exceptional/:action')->to('exceptional#');
# /exceptional_too/*
- $r->bridge('/exceptional_too')->to('exceptional#this_one_might_die')
- ->route('/:action');
+ $r->route('/exceptional_too')->inline(1)
+ ->to('exceptional#this_one_might_die')->route('/:action');
# /fun/time
$r->fun('/time')->to('foo#fun');
@@ -128,13 +128,13 @@ sub startup {
# /withblock (template with blocks)
$r->route('/withblock')->to('foo#withBlock');
- # /staged (authentication with bridges)
- my $b = $r->bridge('/staged')->to('foo#stage1', return => 1);
+ # /staged (authentication with intermediate destination)
+ my $b = $r->route('/staged')->inline(1)->to('foo#stage1', return => 1);
$b->route->to(action => 'stage2');
- # /suspended (suspended bridge)
- $r->bridge('/suspended')->to('foo#suspended')->bridge->to('foo#suspended')
- ->route->to('foo#fun');
+ # /suspended (suspended intermediate destination)
+ $r->route('/suspended')->inline(1)->to('foo#suspended')->route->inline(1)
+ ->to('foo#suspended')->route->to('foo#fun');
# /longpoll (long polling)
$r->route('/longpoll')->to('foo#longpoll');
@@ -24,7 +24,7 @@ app->defaults(default => 23);
my $log = '';
my $cb = app->log->on(message => sub { $log .= pop });
is app->secrets->[0], app->moniker, 'secret defaults to moniker';
-like $log, qr/Your secret passphrase needs to be changed!!!/, 'right message';
+like $log, qr/Your secret passphrase needs to be changed/, 'right message';
app->log->unsubscribe(message => $cb);
# Test helpers
@@ -80,6 +80,9 @@ get '/alternatives/:char' => [char => [qw(☃ ♥)]] => sub {
get '/optional/:middle/placeholder' =>
{middle => 'none', inline => '<%= $middle %>-<%= url_for =%>'};
+get '/optional/:param' =>
+ {param => undef, inline => '%= param("param") // "undef"'};
+
get '/alterformat' => [format => ['json']] => {format => 'json'} => sub {
my $c = shift;
$c->render(text => $c->stash('format'));
@@ -479,11 +482,7 @@ $t->get_ok('/☃')->status_is(200)
# Umlaut
$t->get_ok('/uni/aäb')->status_is(200)->content_is('/uni/a%C3%A4b');
-
-# Escaped umlaut
$t->get_ok('/uni/a%E4b')->status_is(200)->content_is('/uni/a%C3%A4b');
-
-# Escaped umlaut again
$t->get_ok('/uni/a%C3%A4b')->status_is(200)->content_is('/uni/a%C3%A4b');
# Captured snowman
@@ -525,21 +524,13 @@ $t->post_ok('/')->status_is(200)->header_is(Server => 'Mojolicious (Perl)')
$t->get_ok('/alternatives/☃')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_is('/alternatives/%E2%98%83');
-
-# Different Unicode alternative
$t->get_ok('/alternatives/♥')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_is('/alternatives/%E2%99%A5');
-
-# Invalid alternative
$t->get_ok('/alternatives/☃23')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_is("Oops!\n");
-
-# Invalid alternative
$t->get_ok('/alternatives')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_is("Oops!\n");
-
-# Invalid alternative
$t->get_ok('/alternatives/test')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_is("Oops!\n");
@@ -547,29 +538,29 @@ $t->get_ok('/alternatives/test')->status_is(404)
$t->get_ok('/optional/test/placeholder')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_is('test-/optional/test/placeholder');
-
-# Optional placeholder in the middle without value
$t->get_ok('/optional/placeholder')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_is('none-/optional/none/placeholder');
+# Optional placeholder
+$t->get_ok('/optional')->status_is(200)
+ ->header_is(Server => 'Mojolicious (Perl)')->content_is("undef\n");
+$t->get_ok('/optional/test')->status_is(200)
+ ->header_is(Server => 'Mojolicious (Perl)')->content_is("test\n");
+$t->get_ok('/optional?param=test')->status_is(200)
+ ->header_is(Server => 'Mojolicious (Perl)')->content_is("undef\n");
+
# No format
$t->get_ok('/alterformat')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('json');
-
-# Format alternative
$t->get_ok('/alterformat.json')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('json');
-
-# Invalid format alternative
$t->get_ok('/alterformat.html')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_is("Oops!\n");
# No format
$t->get_ok('/noformat')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('xml/noformat');
-
-# Invalid format
$t->get_ok('/noformat.xml')->status_is(404)
->header_is(Server => 'Mojolicious (Perl)')->content_is("Oops!\n");
@@ -604,11 +595,9 @@ $t->get_ok('/multi/B?foo=A&foo=E&baz=C&yada=D&yada=text&yada=fail')
$t->get_ok('/multi/B?baz=C')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('BC');
-# Reserved stash value
+# Reserved stash values
$t->get_ok('/reserved?data=just-works')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('just-worksdata');
-
-# More reserved stash values
$t->get_ok('/reserved?data=just-works&json=test')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_is('just-worksdata,json');
@@ -689,11 +678,9 @@ $t->get_ok('//sri:foo@/stream' => form => {foo => 'bar'})->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->content_like(qr!^foobarsri:foohttp://127\.0\.0\.1:\d+/stream$!);
-# Not ajax
+# Ajax
$t->get_ok('/maybe/ajax')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')->content_is('not ajax');
-
-# Ajax
$t->get_ok('/maybe/ajax' => {'X-Requested-With' => 'XMLHttpRequest'})
->status_is(200)->header_is(Server => 'Mojolicious (Perl)')
->content_is('is ajax');
@@ -780,8 +767,7 @@ $log = '';
$cb = $t->app->log->on(message => sub { $log .= pop });
$t->get_ok('/source?fail=1')->status_is(404)->header_is('X-Missing' => 1)
->content_is("Oops!\n");
-like $log, qr/File "does_not_exist.txt" not found, public directory missing\?/,
- 'right message';
+like $log, qr/Static file "does_not_exist.txt" not found/, 'right message';
$t->app->log->unsubscribe(message => $cb);
# With body and max message size
@@ -789,8 +775,8 @@ $t->app->log->unsubscribe(message => $cb);
local $ENV{MOJO_MAX_MESSAGE_SIZE} = 1024;
$t->get_ok('/', '1234' x 1024)->status_is(200)
->header_is(Connection => 'close')
- ->content_is(
- "413\n/root.html\n/root.html\n/root.html\n/root.html\n/root.html\n");
+ ->content_is("Maximum message size exceeded\n"
+ . "/root.html\n/root.html\n/root.html\n/root.html\n/root.html\n");
}
# Relaxed placeholder
@@ -810,19 +796,13 @@ $t->get_ok('/foo_wildcard_too/')->status_is(404);
$t->get_ok('/with/header/condition',
{'X-Secret-Header' => 'bar', 'X-Another-Header' => 'baz'})->status_is(200)
->content_is("Test ok!\n");
-
-# Missing headers
$t->get_ok('/with/header/condition')->status_is(404)->content_like(qr/Oops!/);
-
-# Missing header
$t->get_ok('/with/header/condition' => {'X-Secret-Header' => 'bar'})
->status_is(404)->content_like(qr/Oops!/);
# Single header condition
$t->post_ok('/with/header/condition' => {'X-Secret-Header' => 'bar'} => 'bar')
->status_is(200)->content_is('foo bar');
-
-# Missing header
$t->post_ok('/with/header/condition' => {} => 'bar')->status_is(404)
->content_like(qr/Oops!/);
@@ -1102,7 +1082,7 @@ Test ok!
@@ root.html.epl
% my $c = shift;
-<% if (my $err = $c->req->error) { =%><%= "$err->{advice}\n" %><% } =%>
+<% if (my $err = $c->req->error) { =%><%= "$err->{message}\n" %><% } =%>
%== $c->url_for('root_path')
%== $c->url_for('root_path')
%== $c->url_for('root_path')
@@ -248,7 +248,7 @@ ok !$t->tx->kept_alive, 'connection was not kept alive';
ok !$t->tx->keep_alive, 'connection will not be kept alive';
is $stash->{finished}, 1, 'finish event has been emitted once';
ok $stash->{destroyed}, 'controller has been destroyed';
-unlike $log, qr/Nothing has been rendered, expecting delayed response\./,
+unlike $log, qr/Nothing has been rendered, expecting delayed response/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -393,7 +393,7 @@ $t->get_ok('/longpoll/static/delayed')->status_is(200)
->content_is("Hello Mojo from a static file!\n");
is $stash->{finished}, 1, 'finish event has been emitted once';
ok $stash->{destroyed}, 'controller has been destroyed';
-like $log, qr/Nothing has been rendered, expecting delayed response\./,
+like $log, qr/Nothing has been rendered, expecting delayed response/,
'right message';
$t->app->log->unsubscribe(message => $cb);
@@ -43,8 +43,8 @@ $t->get_ok('/')->status_is(200)->header_is('X-Route' => 'root')
->content_is(<<'EOF');
http://example.com/rebased/
<script src="/rebased/mojo/jquery/jquery.js"></script>
-<img src="/rebased/images/test.png" />
-<link href="//example.com/base.css" rel="stylesheet" />
+<img src="/rebased/images/test.png">
+<link href="//example.com/base.css" rel="stylesheet">
<a href="mailto:sri@example.com">Contact</a>
http://example.com/rebased
http://example.com/rebased/foo
@@ -58,8 +58,8 @@ EOF
$t->get_ok('/foo')->status_is(200)->header_is('X-Route' => 'foo')
->content_is(<<EOF);
http://example.com/rebased/
-<link href="/rebased/b.css" media="test" rel="stylesheet" />
-<img alt="Test" src="/rebased/images/test.png" />
+<link href="/rebased/b.css" media="test" rel="stylesheet">
+<img alt="Test" src="/rebased/images/test.png">
http://example.com/rebased/foo
http://example.com/rebased
/rebased
@@ -78,8 +78,8 @@ ok $t->ua->cookie_jar->find($t->ua->server->url->path('/foo')),
# Rebased route with message from flash
$t->get_ok('/foo')->status_is(200)->content_is(<<EOF);
http://example.com/rebased/works!too!
-<link href="/rebased/b.css" media="test" rel="stylesheet" />
-<img alt="Test" src="/rebased/images/test.png" />
+<link href="/rebased/b.css" media="test" rel="stylesheet">
+<img alt="Test" src="/rebased/images/test.png">
http://example.com/rebased/foo
http://example.com/rebased
/rebased
@@ -92,8 +92,8 @@ $t->get_ok('/baz')->status_is(200)->header_is('X-Route' => 'baz')
->content_is(<<'EOF');
http://example.com/rebased/
<script src="/rebased/mojo/jquery/jquery.js"></script>
-<img src="/rebased/images/test.png" />
-<link href="//example.com/base.css" rel="stylesheet" />
+<img src="/rebased/images/test.png">
+<link href="//example.com/base.css" rel="stylesheet">
<a href="mailto:sri@example.com">Contact</a>
http://example.com/rebased/baz
http://example.com/rebased/foo
@@ -48,7 +48,7 @@ my $log = '';
my $cb = $c->app->log->on(message => sub { $log .= pop });
$c->stash->{handler} = 'not_defined';
is $renderer->render($c), undef, 'return undef for unrecognized handler';
-like $log, qr/No handler for "not_defined" available\./, 'right message';
+like $log, qr/No handler for "not_defined" available/, 'right message';
$c->app->log->unsubscribe(message => $cb);
# Default template name
@@ -59,7 +59,7 @@ is $c->app->renderer->template_for($c), 'foo/bar', 'right template name';
$log = '';
$cb = $c->app->log->on(message => sub { $log .= pop });
$c->cookie(foo => 'x' x 4097);
-like $log, qr/Cookie "foo" is bigger than 4096 bytes\./, 'right message';
+like $log, qr/Cookie "foo" is bigger than 4096 bytes/, 'right message';
$c->app->log->unsubscribe(message => $cb);
# Nested helpers
@@ -57,10 +57,10 @@ $r->route('/:controller/testedit')->to(action => 'testedit');
$test->route('/delete/(id)', id => qr/\d+/)->to(action => 'delete', id => 23);
# /test2
-my $test2 = $r->bridge('/test2/')->to(controller => 'test2');
+my $test2 = $r->route('/test2/')->inline(1)->to(controller => 'test2');
# /test2 (inline)
-my $test4 = $test2->bridge('/')->to(controller => 'index');
+my $test4 = $test2->route('/')->inline(1)->to(controller => 'index');
# /test2/foo
$test4->route('/foo')->to(controller => 'baz');
@@ -122,10 +122,10 @@ $r->route('/format7', format => [qw(foo foobar)])->to('perl#rocks');
# /articles/1/edit
# /articles/1/delete
-my $bridge = $r->bridge('/articles/:id')
+my $inline = $r->route('/articles/:id')->inline(1)
->to(controller => 'articles', action => 'load', format => 'html');
-$bridge->route('/edit')->to(controller => 'articles', action => 'edit');
-$bridge->route('/delete')
+$inline->route('/edit')->to(controller => 'articles', action => 'edit');
+$inline->route('/delete')
->to(controller => 'articles', action => 'delete', format => undef)
->name('articles_delete');
@@ -213,7 +213,7 @@ $r->route('/partial')->detour('foo#bar');
# GET /similar/*
# POST /similar/too
-my $similar = $r->bridge('/similar');
+my $similar = $r->route('/similar')->inline(1);
$similar->route('/:something')->via('GET')->to('similar#get');
$similar->route('/too')->via('POST')->to('similar#post');
@@ -711,7 +711,7 @@ is_deeply $m->stack, [{controller => 'test-test', action => 'test'}],
is $m->path_for->{path}, '/simple/form', 'right path';
is $m->path_for('current')->{path}, '/simple/form', 'right path';
-# Special edge case with nested bridges (regex)
+# Special edge case with intermediate destinations (regex)
$m = Mojolicious::Routes::Match->new(root => $r);
$m->match($c => {method => 'GET', path => '/regex/alternatives/foo'});
is_deeply $m->stack,
@@ -12,8 +12,6 @@ patch 'more_tags';
get 'small_tags';
-get 'circle';
-
get 'tags_with_error';
any [qw(GET POST)] => 'links';
@@ -53,8 +51,8 @@ is app->select_field(country => $values),
# Basic tags
$t->options_ok('/tags')->status_is(200)->content_is(<<EOF);
-<foo />
-<foo bar="baz" />
+<foo></foo>
+<foo bar="baz"></foo>
<foo one="t<wo" three="four">Hello</foo>
<div data-my-test-id="1" data-name="test">some content</div>
<div data="bar">some content</div>
@@ -69,7 +67,6 @@ EOF
# Shortcut
$t->get_ok('/small_tags')->status_is(200)->content_is(<<EOF);
<div id="&lt;">test & 123</div>
-<div id="<">test 321</div>
<div>
<p id="0">just</p>
<p>0</p>
@@ -77,13 +74,6 @@ $t->get_ok('/small_tags')->status_is(200)->content_is(<<EOF);
<div>works</div>
EOF
-# XML
-$t->get_ok('/circle.svg')->status_is(200)->content_is(<<EOF);
-<svg>
-<circle cx="75" cy="55" fill="red" r="35" stroke="black" stroke-width="2" />
-</svg>
-EOF
-
# Tags with error
$t->get_ok('/tags_with_error')->status_is(200)->content_is(<<EOF);
<bar class="field-with-error">0</bar>
@@ -132,7 +122,7 @@ EOF
# Stylesheets
$t->get_ok('/style')->status_is(200)->content_is(<<EOF);
-<link href="/foo.css" rel="stylesheet" />
+<link href="/foo.css" rel="stylesheet">
<style>/*<![CDATA[*/
body {color: #000}
@@ -149,33 +139,33 @@ EOF
$t->get_ok('/basicform')->status_is(200)->content_is(<<EOF);
<form action="/links">
<label for="foo"><Foo></label>
- <input name="foo" type="text" value="bar" />
+ <input name="foo" type="text" value="bar">
<label for="bar">
Bar<br>
</label>
- <input class="test" name="bar" type="text" value="baz" />
- <input name="yada" type="text" value="" />
- <input class="tset" name="baz" value="yada" />
- <input type="submit" value="Ok" />
+ <input class="test" name="bar" type="text" value="baz">
+ <input name="yada" type="text" value="">
+ <input class="tset" name="baz" value="yada">
+ <input type="submit" value="Ok">
</form>
EOF
# Text input fields
$t->post_ok('/text')->status_is(200)->content_is(<<'EOF');
<form action="/text" method="POST">
- <input class="foo" name="color" type="color" value="#ffffff" />
- <input class="foo" name="date" type="date" value="2012-12-12" />
- <input class="foo" name="dt" type="datetime" value="2012-12-12T23:59:59Z" />
- <input class="foo" name="email" type="email" value="nospam@example.com" />
- <input class="foo" name="month" type="month" value="2012-12" />
- <input class="foo" name="number" type="number" value="23" />
- <input class="foo" name="range" type="range" value="24" />
- <input class="foo" name="search" type="search" value="perl" />
- <input class="foo" name="tel" type="tel" value="123456789" />
- <input class="foo" name="time" type="time" value="23:59:59" />
- <input class="foo" name="url" type="url" value="http://mojolicio.us" />
- <input class="foo" name="week" type="week" value="2012-W16" />
- <input type="submit" value="Ok" />
+ <input class="foo" name="color" type="color" value="#ffffff">
+ <input class="foo" name="date" type="date" value="2012-12-12">
+ <input class="foo" name="dt" type="datetime" value="2012-12-12T23:59:59Z">
+ <input class="foo" name="email" type="email" value="nospam@example.com">
+ <input class="foo" name="month" type="month" value="2012-12">
+ <input class="foo" name="number" type="number" value="23">
+ <input class="foo" name="range" type="range" value="24">
+ <input class="foo" name="search" type="search" value="perl">
+ <input class="foo" name="tel" type="tel" value="123456789">
+ <input class="foo" name="time" type="time" value="23:59:59">
+ <input class="foo" name="url" type="url" value="http://mojolicio.us">
+ <input class="foo" name="week" type="week" value="2012-W16">
+ <input type="submit" value="Ok">
</form>
EOF
@@ -197,64 +187,64 @@ $t->post_ok(
}
)->status_is(200)->content_is(<<'EOF');
<form action="/text" method="POST">
- <input class="foo" name="color" type="color" value="#000000" />
- <input class="foo" name="date" type="date" value="2012-12-13" />
- <input class="foo" name="dt" type="datetime" value="2012-12-13T23:59:59Z" />
- <input class="foo" name="email" type="email" value="spam@example.com" />
- <input class="foo" name="month" type="month" value="2012-11" />
- <input class="foo" name="number" type="number" value="25" />
- <input class="foo" name="range" type="range" value="26" />
- <input class="foo" name="search" type="search" value="c" />
- <input class="foo" name="tel" type="tel" value="987654321" />
- <input class="foo" name="time" type="time" value="23:59:58" />
- <input class="foo" name="url" type="url" value="http://example.com" />
- <input class="foo" name="week" type="week" value="2012-W17" />
- <input type="submit" value="Ok" />
+ <input class="foo" name="color" type="color" value="#000000">
+ <input class="foo" name="date" type="date" value="2012-12-13">
+ <input class="foo" name="dt" type="datetime" value="2012-12-13T23:59:59Z">
+ <input class="foo" name="email" type="email" value="spam@example.com">
+ <input class="foo" name="month" type="month" value="2012-11">
+ <input class="foo" name="number" type="number" value="25">
+ <input class="foo" name="range" type="range" value="26">
+ <input class="foo" name="search" type="search" value="c">
+ <input class="foo" name="tel" type="tel" value="987654321">
+ <input class="foo" name="time" type="time" value="23:59:58">
+ <input class="foo" name="url" type="url" value="http://example.com">
+ <input class="foo" name="week" type="week" value="2012-W17">
+ <input type="submit" value="Ok">
</form>
EOF
# Checkboxes
$t->get_ok('/multibox')->status_is(200)->content_is(<<EOF);
<form action="/multibox">
- <input name="foo" type="checkbox" value="one" />
- <input name="foo" type="checkbox" value="two" />
- <input type="submit" value="Ok" />
+ <input name="foo" type="checkbox" value="one">
+ <input name="foo" type="checkbox" value="two">
+ <input type="submit" value="Ok">
</form>
EOF
# Checkboxes with one value
$t->get_ok('/multibox?foo=two')->status_is(200)->content_is(<<EOF);
<form action="/multibox">
- <input name="foo" type="checkbox" value="one" />
- <input checked="checked" name="foo" type="checkbox" value="two" />
- <input type="submit" value="Ok" />
+ <input name="foo" type="checkbox" value="one">
+ <input checked name="foo" type="checkbox" value="two">
+ <input type="submit" value="Ok">
</form>
EOF
# Checkboxes with one right and one wrong value
$t->get_ok('/multibox?foo=one&foo=three')->status_is(200)->content_is(<<EOF);
<form action="/multibox">
- <input checked="checked" name="foo" type="checkbox" value="one" />
- <input name="foo" type="checkbox" value="two" />
- <input type="submit" value="Ok" />
+ <input checked name="foo" type="checkbox" value="one">
+ <input name="foo" type="checkbox" value="two">
+ <input type="submit" value="Ok">
</form>
EOF
# Checkboxes with wrong value
$t->get_ok('/multibox?foo=bar')->status_is(200)->content_is(<<EOF);
<form action="/multibox">
- <input name="foo" type="checkbox" value="one" />
- <input name="foo" type="checkbox" value="two" />
- <input type="submit" value="Ok" />
+ <input name="foo" type="checkbox" value="one">
+ <input name="foo" type="checkbox" value="two">
+ <input type="submit" value="Ok">
</form>
EOF
# Checkboxes with two values
$t->get_ok('/multibox?foo=two&foo=one')->status_is(200)->content_is(<<EOF);
<form action="/multibox">
- <input checked="checked" name="foo" type="checkbox" value="one" />
- <input checked="checked" name="foo" type="checkbox" value="two" />
- <input type="submit" value="Ok" />
+ <input checked name="foo" type="checkbox" value="one">
+ <input checked name="foo" type="checkbox" value="two">
+ <input type="submit" value="Ok">
</form>
EOF
@@ -262,63 +252,63 @@ EOF
$t->get_ok('/form/lala?a=2&b=0&c=2&d=3&escaped=1%22+%222')->status_is(200)
->content_is(<<EOF);
<form action="/links" method="post">
- <input name="foo" />
+ <input name="foo">
</form>
<form action="/form/24" method="post">
- <input name="foo" type="text" />
- <input data-id="1" data-name="test" name="foo" type="text" value="1" />
- <input data="ok" name="foo" type="text" value="1" />
- <input name="foo" type="checkbox" value="1" />
- <input checked="checked" name="a" type="checkbox" value="2" />
- <input name="b" type="radio" value="1" />
- <input checked="checked" name="b" type="radio" value="0" />
- <input name="c" type="hidden" value="foo" />
- <input name="d" type="file" />
+ <input name="foo" type="text">
+ <input data-id="1" data-name="test" name="foo" type="text" value="1">
+ <input data="ok" name="foo" type="text" value="1">
+ <input name="foo" type="checkbox" value="1">
+ <input checked name="a" type="checkbox" value="2">
+ <input name="b" type="radio" value="1">
+ <input checked name="b" type="radio" value="0">
+ <input name="c" type="hidden" value="foo">
+ <input name="d" type="file">
<textarea cols="40" name="e" rows="50">
default!
</textarea>
<textarea name="f"></textarea>
- <input name="g" type="password" />
- <input id="foo" name="h" type="password" />
- <input type="submit" value="Ok!" />
- <input id="bar" type="submit" value="Ok too!" />
+ <input name="g" type="password">
+ <input id="foo" name="h" type="password">
+ <input type="submit" value="Ok!">
+ <input id="bar" type="submit" value="Ok too!">
</form>
<form action="/">
- <input name="foo" />
+ <input name="foo">
</form>
-<input name="escaped" value="1" "2" />
-<input name="a" value="2" />
-<input name="a" value="2" />
+<input name="escaped" value="1" "2">
+<input name="a" value="2">
+<input name="a" value="2">
EOF
# Advanced form with different values
$t->get_ok('/form/lala?c=b&d=3&e=4&f=<5')->status_is(200)->content_is(<<EOF);
<form action="/links" method="post">
- <input name="foo" />
+ <input name="foo">
</form>
<form action="/form/24" method="post">
- <input name="foo" type="text" />
- <input data-id="1" data-name="test" name="foo" type="text" value="1" />
- <input data="ok" name="foo" type="text" value="1" />
- <input name="foo" type="checkbox" value="1" />
- <input name="a" type="checkbox" value="2" />
- <input name="b" type="radio" value="1" />
- <input name="b" type="radio" value="0" />
- <input name="c" type="hidden" value="foo" />
- <input name="d" type="file" />
+ <input name="foo" type="text">
+ <input data-id="1" data-name="test" name="foo" type="text" value="1">
+ <input data="ok" name="foo" type="text" value="1">
+ <input name="foo" type="checkbox" value="1">
+ <input name="a" type="checkbox" value="2">
+ <input name="b" type="radio" value="1">
+ <input name="b" type="radio" value="0">
+ <input name="c" type="hidden" value="foo">
+ <input name="d" type="file">
<textarea cols="40" name="e" rows="50">4</textarea>
<textarea name="f"><5</textarea>
- <input name="g" type="password" />
- <input id="foo" name="h" type="password" />
- <input type="submit" value="Ok!" />
- <input id="bar" type="submit" value="Ok too!" />
+ <input name="g" type="password">
+ <input id="foo" name="h" type="password">
+ <input type="submit" value="Ok!">
+ <input id="bar" type="submit" value="Ok too!">
</form>
<form action="/">
- <input name="foo" />
+ <input name="foo">
</form>
-<input name="escaped" />
-<input name="a" />
-<input name="a" value="c" />
+<input name="escaped">
+<input name="a">
+<input name="a" value="c">
EOF
# Empty selection
@@ -347,7 +337,7 @@ $t->put_ok('/selection')->status_is(200)
. '<option value="b">b</option>'
. '</optgroup>'
. "</select>\n "
- . '<input type="submit" value="Ok" />'
+ . '<input type="submit" value="Ok">'
. "\n</form>\n");
# Selection with values
@@ -357,26 +347,26 @@ $t->put_ok('/selection?a=e&foo=bar&bar=baz&yada=b')->status_is(200)
. '<option value="b">b</option>'
. '<optgroup label="c">'
. '<option value="<d"><d</option>'
- . '<option selected="selected" value="e">E</option>'
+ . '<option selected value="e">E</option>'
. '<option value="f">f</option>'
. '</optgroup>'
. '<option value="g">g</option>'
. "</select>\n "
. '<select multiple="multiple" name="foo">'
- . '<option selected="selected" value="bar">bar</option>'
+ . '<option selected value="bar">bar</option>'
. '<option value="baz">baz</option>'
. "</select>\n "
. '<select name="bar">'
. '<option disabled="disabled" value="d">D</option>'
- . '<option selected="selected" value="baz">baz</option>'
+ . '<option selected value="baz">baz</option>'
. "</select>\n "
. '<select name="yada">'
. '<optgroup class="x" label="test">'
. '<option value="a">a</option>'
- . '<option selected="selected" value="b">b</option>'
+ . '<option selected value="b">b</option>'
. '</optgroup>'
. "</select>\n "
- . '<input type="submit" value="Ok" />'
+ . '<input type="submit" value="Ok">'
. "\n</form>\n");
# Selection with multiple values
@@ -387,39 +377,39 @@ $t->put_ok('/selection?foo=bar&a=e&foo=baz&bar=d&yada=a&yada=b')
. '<option value="b">b</option>'
. '<optgroup label="c">'
. '<option value="<d"><d</option>'
- . '<option selected="selected" value="e">E</option>'
+ . '<option selected value="e">E</option>'
. '<option value="f">f</option>'
. '</optgroup>'
. '<option value="g">g</option>'
. "</select>\n "
. '<select multiple="multiple" name="foo">'
- . '<option selected="selected" value="bar">bar</option>'
- . '<option selected="selected" value="baz">baz</option>'
+ . '<option selected value="bar">bar</option>'
+ . '<option selected value="baz">baz</option>'
. "</select>\n "
. '<select name="bar">'
- . '<option disabled="disabled" selected="selected" value="d">D</option>'
+ . '<option disabled="disabled" selected value="d">D</option>'
. '<option value="baz">baz</option>'
. "</select>\n "
. '<select name="yada">'
. '<optgroup class="x" label="test">'
- . '<option selected="selected" value="a">a</option>'
- . '<option selected="selected" value="b">b</option>'
+ . '<option selected value="a">a</option>'
+ . '<option selected value="b">b</option>'
. '</optgroup>'
. "</select>\n "
- . '<input type="submit" value="Ok" />'
+ . '<input type="submit" value="Ok">'
. "\n</form>\n");
# Selection with multiple values preselected
$t->put_ok('/selection?preselect=1')->status_is(200)
->content_is("<form action=\"/selection\">\n "
. '<select name="a">'
- . '<option selected="selected" value="b">b</option>'
+ . '<option selected value="b">b</option>'
. '<optgroup label="c">'
. '<option value="<d"><d</option>'
. '<option value="e">E</option>'
. '<option value="f">f</option>'
. '</optgroup>'
- . '<option selected="selected" value="g">g</option>'
+ . '<option selected value="g">g</option>'
. "</select>\n "
. '<select multiple="multiple" name="foo">'
. '<option value="bar">bar</option>'
@@ -435,14 +425,14 @@ $t->put_ok('/selection?preselect=1')->status_is(200)
. '<option value="b">b</option>'
. '</optgroup>'
. "</select>\n "
- . '<input type="submit" value="Ok" />'
+ . '<input type="submit" value="Ok">'
. "\n</form>\n");
# Snowman form
$t->post_ok('/☃')->status_is(200)->content_is(<<'EOF');
<form action="/%E2%98%83" method="POST">
<textarea cols="40" name="foo">b<a>r</textarea>
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -450,7 +440,7 @@ EOF
$t->post_ok('/☃?foo=ba<z')->status_is(200)->content_is(<<'EOF');
<form action="/%E2%98%83" method="POST">
<textarea cols="40" name="foo">ba<z</textarea>
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -458,7 +448,7 @@ EOF
$t->patch_ok('/☃?foo=')->status_is(200)->content_is(<<'EOF');
<form action="/%E2%98%83" method="POST">
<textarea cols="40" name="foo"></textarea>
- <input type="submit" value="☃" />
+ <input type="submit" value="☃">
</form>
EOF
@@ -466,7 +456,7 @@ EOF
$t->post_ok('/no_snowman')->status_is(200)->content_is(<<'EOF');
<form action="/%E2%98%83" method="POST">
<textarea cols="40" name="bar"></textarea>
- <input type="submit" value="whatever" />
+ <input type="submit" value="whatever">
</form>
EOF
@@ -474,7 +464,7 @@ EOF
$t->post_ok('/no_snowman?foo=1')->status_is(200)->content_is(<<'EOF');
<form action="/%E2%98%83" method="PATCH">
<textarea cols="40" name="bar"></textarea>
- <input type="submit" value="whatever" />
+ <input type="submit" value="whatever">
</form>
EOF
@@ -496,7 +486,6 @@ __DATA__
@@ small_tags.html.ep
%=t div => (id => '<') => 'test & 123'
-%=t div => (id => b('<')) => b('test 321')
%=t div => begin
%=t p => (id => 0) => 'just'
%=t p => 0
@@ -510,14 +499,6 @@ __DATA__
0
%= end
-@@ circle.svg.ep
-%= tag svg => begin
-<%=
- tag 'circle', cx => 75, cy => 55, r => 35, stroke => 'black',
- 'stroke-width' => 2, fill => 'red'
-%>
-%= end
-
@@ links.html.ep
<%= link_to 'Pa<th' => '/path' %>
<%= link_to 'http://example.com/', title => 'Foo', sub { 'Foo' } %>
@@ -554,7 +535,7 @@ __DATA__
Bar<br>
%= end
%= text_field bar => 'baz', class => 'test'
- %= text_field yada => undef
+ %= text_field yada => ''
%= input_tag baz => 'yada', class => 'tset'
%= submit_button
%= end
@@ -128,9 +128,9 @@ is_deeply $validation->error('foo'), ['required'], 'right error';
# "0"
$validation = $t->app->validation->input({0 => 0});
ok $validation->has_data, 'has data';
-ok $validation->required('0')->size(1, 1)->is_valid, 'valid';
+ok $validation->required(0)->size(1, 1)->is_valid, 'valid';
is_deeply $validation->output, {0 => 0}, 'right result';
-is $validation->param('0'), 0, 'right value';
+is $validation->param(0), 0, 'right value';
# Custom error
$validation = $t->app->validation->input({foo => 'bar'});
@@ -10,7 +10,7 @@ use Test::Mojo;
websocket '/echo' => sub {
my $c = shift;
- $c->tx->max_websocket_size(262145)->with_compression;
+ $c->tx->max_websocket_size(65538)->with_compression;
$c->on(binary => sub { shift->send({binary => shift}) });
$c->on(
text => sub {
@@ -139,21 +139,25 @@ $t->websocket_ok('/echo')->send_ok(0)->message_ok->message_is('echo: 0')
->send_ok(0)->message_ok->message_like({text => qr/0/})->finish_ok(1000)
->finished_ok(1000);
-# 64-bit binary message (extended limit)
+# 64-bit binary message
$t->request_ok($t->ua->build_websocket_tx('/echo'));
is $t->tx->max_websocket_size, 262144, 'right size';
-$t->tx->max_websocket_size(262145);
-$t->send_ok({binary => 'a' x 262145})
- ->message_ok->message_is({binary => 'a' x 262145})
+$t->tx->max_websocket_size(65538);
+$t->send_ok({binary => 'a' x 65538})
+ ->message_ok->message_is({binary => 'a' x 65538})
->finish_ok->finished_ok(1005);
-# 64-bit binary message (too large)
-$t->websocket_ok('/echo')->send_ok({binary => 'b' x 262145})
- ->finished_ok(1009);
+# 64-bit binary message (too large for server)
+$t->websocket_ok('/echo')->send_ok({binary => 'b' x 65539})->finished_ok(1009);
-# Binary message in two 64-bit frames without FIN bit (too large)
-$t->websocket_ok('/echo')->send_ok([0, 0, 0, 0, 2, 'c' x 100000])
- ->send_ok([0, 0, 0, 0, 0, 'c' x 162146])->finished_ok(1009);
+# 64-bit binary message (too large for client)
+$t->websocket_ok('/echo');
+$t->tx->max_websocket_size(65536);
+$t->send_ok({binary => 'c' x 65537})->finished_ok(1009);
+
+# Binary message in two frames without FIN bit (too large for server)
+$t->websocket_ok('/echo')->send_ok([0, 0, 0, 0, 2, 'd' x 30000])
+ ->send_ok([0, 0, 0, 0, 0, 'd' x 35539])->finished_ok(1009);
# Plain alternative
$t->get_ok('/echo')->status_is(200)->content_is('plain echo!');
@@ -171,7 +175,7 @@ $t->send_ok({binary => 'a' x 500})
$t->websocket_ok(
'/echo' => {'Sec-WebSocket-Extensions' => 'permessage-deflate'});
ok $t->tx->compressed, 'WebSocket has compression';
-$t->send_ok({binary => 'a' x 50000})
+$t->send_ok({binary => 'a' x 10000})
->header_is('Sec-WebSocket-Extensions' => 'permessage-deflate');
is $t->tx->req->headers->sec_websocket_extensions, 'permessage-deflate',
'right "Sec-WebSocket-Extensions" value';
@@ -182,8 +186,8 @@ $t->tx->once(
$payload = $frame->[5];
}
);
-$t->message_ok->message_is({binary => 'a' x 50000});
-ok length $payload < 50000, 'message has been compressed';
+$t->message_ok->message_is({binary => 'a' x 10000});
+ok length $payload < 10000, 'message has been compressed';
$t->finish_ok->finished_ok(1005);
# Compressed message exceeding the limit when decompressed
@@ -193,7 +197,7 @@ $t->websocket_ok(
->send_ok({binary => 'a' x 1000000})->finished_ok(1009);
# Huge message that doesn't compress very well
-my $huge = join '', map { int rand(9) } 1 .. 262144;
+my $huge = join '', map { int rand(9) } 1 .. 65538;
$t->websocket_ok(
'/echo' => {'Sec-WebSocket-Extensions' => 'permessage-deflate'})
->send_ok({binary => $huge})->message_ok->message_is({binary => $huge})
@@ -8,6 +8,6 @@ plan skip_all => 'Test::Pod::Coverage 1.04 required for this test!'
unless eval 'use Test::Pod::Coverage 1.04; 1';
# DEPRECATED in Tiger Face!
-my @tiger = qw(decode encode error new pluck siblings val);
+my @tiger = qw(bridge siblings);
all_pod_coverage_ok({also_private => [@tiger]});