Skip to content

Instantly share code, notes, and snippets.

@dex4er
Last active February 12, 2018 14:47
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dex4er/0e2ef2d1582307a14329 to your computer and use it in GitHub Desktop.
Save dex4er/0e2ef2d1582307a14329 to your computer and use it in GitHub Desktop.
#!/bin/sh
die () {
msg="$1\n"
shift
printf "$msg" "$@" 1>&2
exit 1
}
use=`
for mod in \
Mojolicious::Lite \
; do
echo "--use=$mod"
done
`
export MOJO_REACTOR=Mojo::Reactor::Poll
cd `dirname $0`
rm -f fatpacker.trace packlist pureproxy
rm -rf fatlib
PLACK_HTTP_PARSER_PP=1 fatpack trace $use mojo-app.pl
sed -i "$delete" fatpacker.trace
fatpack packlists-for `cat fatpacker.trace` >packlists
fatpack tree `cat packlists`
for mod in \
Mojolicious \
; do
path=$(echo "$mod" | sed 's,::,/,g')
test -f fatlib/$path.pm || die "Missing module at site_perl. Reinstall it with command:\ncpanm --reinstall %s" $mod
done
rm -rf fatlib/auto/share
fatpack file mojo-app.pl > mojo-app.plc
sed -i 's,^#!.*/perl$,#!/usr/bin/env perl,' mojo-app.plc
chmod +x mojo-app.plc
echo __DATA__ >> mojo-app.plc
find fatlib/Mojolicious/templates fatlib/Mojolicious/public -type f | while read f; do
label=${f#fatlib/Mojolicious/}
label=${label#*/}
case "$f" in
*/development.html.ep)
echo "@@ not_found.development.html.ep"
sed 's/inline => app->renderer->_bundled//' $f
;;
*.ep)
echo "@@ $label"
sed 's/inline => app->renderer->_bundled//' $f
;;
*.ico|*.png)
echo "@@ $label (base64)"
base64 $f
;;
*)
echo "@@ $label"
cat $f
esac
done >> mojo-app.plc
#!/usr/bin/env perl
use v5.10;
use strict;
use warnings;
use Mojolicious::Lite;
my $daemon = Mojo::Server::Daemon->new(app => app, listen => ['http://*:3000']);
$daemon->inactivity_timeout(300);
$daemon->start;
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
This file has been truncated, but you can view the full file.
#!/usr/bin/env perl
# This chunk of stuff was generated by App::FatPacker. To find the original
# file's code, look for the end of this BEGIN block or the string 'FATPACK'
BEGIN {
my %fatpacked;
$fatpacked{"Mojo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO';
package Mojo;
use Mojo::Base -base;
# "Professor: These old Doomsday devices are dangerously unstable. I'll rest
# easier not knowing where they are."
use Carp ();
use Mojo::Home;
use Mojo::Log;
use Mojo::Transaction::HTTP;
use Mojo::UserAgent;
use Mojo::Util;
use Scalar::Util ();
has home => sub { Mojo::Home->new->detect(ref shift) };
has log => sub { Mojo::Log->new };
has ua => sub {
my $ua = Mojo::UserAgent->new;
Scalar::Util::weaken $ua->server->app(shift)->{app};
return $ua;
};
sub build_tx { Mojo::Transaction::HTTP->new }
sub config { Mojo::Util::_stash(config => @_) }
sub handler { Carp::croak 'Method "handler" not implemented in subclass' }
1;
=encoding utf8
=head1 NAME
Mojo - Duct tape for the HTML5 web!
=head1 SYNOPSIS
package MyApp;
use Mojo::Base 'Mojo';
# All the complexities of CGI, PSGI, HTTP and WebSockets get reduced to a
# single method call!
sub handler {
my ($self, $tx) = @_;
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
}
=head1 DESCRIPTION
A flexible runtime environment for Perl real-time web frameworks, with all the
basic tools and helpers needed to write simple web applications and higher
level web frameworks, such as L<Mojolicious>.
See L<Mojolicious::Guides> for more!
=head1 ATTRIBUTES
L<Mojo> implements the following attributes.
=head2 home
my $home = $app->home;
$app = $app->home(Mojo::Home->new);
The home directory of your application, defaults to a L<Mojo::Home> object
which stringifies to the actual path.
# Generate portable path relative to home directory
my $path = $app->home->rel_file('data/important.txt');
=head2 log
my $log = $app->log;
$app = $app->log(Mojo::Log->new);
The logging layer of your application, defaults to a L<Mojo::Log> object.
# Log debug message
$app->log->debug('It works!');
=head2 ua
my $ua = $app->ua;
$app = $app->ua(Mojo::UserAgent->new);
A full featured HTTP user agent for use in your applications, defaults to a
L<Mojo::UserAgent> object.
# Perform blocking request
say $app->ua->get('example.com')->res->body;
=head1 METHODS
L<Mojo> inherits all methods from L<Mojo::Base> and implements the following
new ones.
=head2 build_tx
my $tx = $app->build_tx;
Transaction builder, defaults to building a L<Mojo::Transaction::HTTP>
object.
=head2 config
my $hash = $app->config;
my $foo = $app->config('foo');
$app = $app->config({foo => 'bar'});
$app = $app->config(foo => 'bar');
Application configuration.
# Remove value
my $foo = delete $app->config->{foo};
=head2 handler
$app->handler(Mojo::Transaction::HTTP->new);
The handler is the main entry point to your application or framework and will
be called for each new transaction, which will usually be a
L<Mojo::Transaction::HTTP> or L<Mojo::Transaction::WebSocket> object. Meant to
be overloaded in a subclass.
sub handler {
my ($self, $tx) = @_;
...
}
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO
$fatpacked{"Mojo/Asset.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_ASSET';
package Mojo::Asset;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
has 'end_range';
has start_range => 0;
sub add_chunk { croak 'Method "add_chunk" not implemented by subclass' }
sub contains { croak 'Method "contains" not implemented by subclass' }
sub get_chunk { croak 'Method "get_chunk" not implemented by subclass' }
sub is_file {undef}
sub is_range { !!($_[0]->end_range || $_[0]->start_range) }
sub move_to { croak 'Method "move_to" not implemented by subclass' }
sub mtime { croak 'Method "mtime" not implemented by subclass' }
sub size { croak 'Method "size" not implemented by subclass' }
sub slurp { croak 'Method "slurp" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Asset - HTTP content storage base class
=head1 SYNOPSIS
package Mojo::Asset::MyAsset;
use Mojo::Base 'Mojo::Asset';
sub add_chunk {...}
sub contains {...}
sub get_chunk {...}
sub move_to {...}
sub mtime {...}
sub size {...}
sub slurp {...}
=head1 DESCRIPTION
L<Mojo::Asset> is an abstract base class for HTTP content storage.
=head1 EVENTS
L<Mojo::Asset> inherits all events from L<Mojo::EventEmitter>.
=head1 ATTRIBUTES
L<Mojo::Asset> implements the following attributes.
=head2 end_range
my $end = $asset->end_range;
$asset = $asset->end_range(8);
Pretend file ends earlier.
=head2 start_range
my $start = $asset->start_range;
$asset = $asset->start_range(3);
Pretend file starts later.
=head1 METHODS
L<Mojo::Asset> inherits all methods from L<Mojo::EventEmitter> and implements
the following new ones.
=head2 add_chunk
$asset = $asset->add_chunk('foo bar baz');
Add chunk of data to asset. Meant to be overloaded in a subclass.
=head2 contains
my $position = $asset->contains('bar');
Check if asset contains a specific string. Meant to be overloaded in a
subclass.
=head2 get_chunk
my $bytes = $asset->get_chunk($offset);
my $bytes = $asset->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum
chunk size of C<131072> bytes (128KB). Meant to be overloaded in a subclass.
=head2 is_file
my $false = $asset->is_file;
False.
=head2 is_range
my $bool = $asset->is_range;
Check if asset has a L</"start_range"> or L</"end_range">.
=head2 move_to
$asset = $asset->move_to('/home/sri/foo.txt');
Move asset data into a specific file. Meant to be overloaded in a subclass.
=head2 mtime
my $mtime = $asset->mtime;
Modification time of asset. Meant to be overloaded in a subclass.
=head2 size
my $size = $asset->size;
Size of asset data in bytes. Meant to be overloaded in a subclass.
=head2 slurp
my $bytes = $asset->slurp;
Read all asset data at once. Meant to be overloaded in a subclass.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_ASSET
$fatpacked{"Mojo/Asset/File.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_ASSET_FILE';
package Mojo::Asset::File;
use Mojo::Base 'Mojo::Asset';
use Carp 'croak';
use Errno 'EEXIST';
use Fcntl qw(O_APPEND O_CREAT O_EXCL O_RDONLY O_RDWR);
use File::Copy 'move';
use File::Spec::Functions 'catfile';
use IO::File;
use Mojo::Util 'md5_sum';
has [qw(cleanup path)];
has handle => sub {
my $self = shift;
# Open existing file
my $handle = IO::File->new;
my $path = $self->path;
if (defined $path && -f $path) {
$handle->open($path, O_RDONLY) or croak qq{Can't open file "$path": $!};
return $handle;
}
# Open new or temporary file
my $base = catfile $self->tmpdir, 'mojo.tmp';
my $name = $path // $base;
until ($handle->open($name, O_APPEND | O_CREAT | O_EXCL | O_RDWR)) {
croak qq{Can't open file "$name": $!} if defined $path || $! != $!{EEXIST};
$name = "$base." . md5_sum(time . $$ . rand 999);
}
$self->path($name);
# Enable automatic cleanup
$self->cleanup(1) unless defined $self->cleanup;
return $handle;
};
has tmpdir => sub { $ENV{MOJO_TMPDIR} || File::Spec::Functions::tmpdir };
sub DESTROY {
my $self = shift;
return unless $self->cleanup && defined(my $path = $self->path);
close $self->handle;
unlink $path if -w $path;
}
sub add_chunk {
my ($self, $chunk) = @_;
$chunk //= '';
croak "Can't write to asset: $!"
unless defined $self->handle->syswrite($chunk, length $chunk);
return $self;
}
sub contains {
my ($self, $str) = @_;
my $handle = $self->handle;
$handle->sysseek($self->start_range, SEEK_SET);
# Calculate window size
my $end = $self->end_range // $self->size;
my $len = length $str;
my $size = $len > 131072 ? $len : 131072;
$size = $end - $self->start_range if $size > $end - $self->start_range;
# Sliding window search
my $offset = 0;
my $start = $handle->sysread(my $window, $len);
while ($offset < $end) {
# Read as much as possible
my $diff = $end - ($start + $offset);
my $read = $handle->sysread(my $buffer, $diff < $size ? $diff : $size);
$window .= $buffer;
# Search window
my $pos = index $window, $str;
return $offset + $pos if $pos >= 0;
return -1 if $read == 0 || ($offset += $read) == $end;
# Resize window
substr $window, 0, $read, '';
}
return -1;
}
sub get_chunk {
my ($self, $offset, $max) = @_;
$max //= 131072;
$offset += $self->start_range;
my $handle = $self->handle;
$handle->sysseek($offset, SEEK_SET);
my $buffer;
if (defined(my $end = $self->end_range)) {
return '' if (my $chunk = $end + 1 - $offset) <= 0;
$handle->sysread($buffer, $chunk > $max ? $max : $chunk);
}
else { $handle->sysread($buffer, $max) }
return $buffer;
}
sub is_file {1}
sub move_to {
my ($self, $to) = @_;
# Windows requires that the handle is closed
close $self->handle;
delete $self->{handle};
# Move file and prevent clean up
my $from = $self->path;
move($from, $to) or croak qq{Can't move file "$from" to "$to": $!};
return $self->path($to)->cleanup(0);
}
sub mtime { (stat shift->handle)[9] }
sub size { -s shift->handle }
sub slurp {
return '' unless defined(my $file = shift->path);
return Mojo::Util::slurp $file;
}
1;
=encoding utf8
=head1 NAME
Mojo::Asset::File - File storage for HTTP content
=head1 SYNOPSIS
use Mojo::Asset::File;
# Temporary file
my $file = Mojo::Asset::File->new;
$file->add_chunk('foo bar baz');
say 'File contains "bar"' if $file->contains('bar') >= 0;
say $file->slurp;
# Existing file
my $file = Mojo::Asset::File->new(path => '/home/sri/foo.txt');
$file->move_to('/yada.txt');
say $file->slurp;
=head1 DESCRIPTION
L<Mojo::Asset::File> is a file storage backend for HTTP content.
=head1 EVENTS
L<Mojo::Asset::File> inherits all events from L<Mojo::Asset>.
=head1 ATTRIBUTES
L<Mojo::Asset::File> inherits all attributes from L<Mojo::Asset> and
implements the following new ones.
=head2 cleanup
my $bool = $file->cleanup;
$file = $file->cleanup($bool);
Delete L</"path"> automatically once the file is not used anymore.
=head2 handle
my $handle = $file->handle;
$file = $file->handle(IO::File->new);
Filehandle, created on demand.
=head2 path
my $path = $file->path;
$file = $file->path('/home/sri/foo.txt');
File path used to create L</"handle">, can also be automatically generated if
necessary.
=head2 tmpdir
my $tmpdir = $file->tmpdir;
$file = $file->tmpdir('/tmp');
Temporary directory used to generate L</"path">, defaults to the value of the
C<MOJO_TMPDIR> environment variable or auto detection.
=head1 METHODS
L<Mojo::Asset::File> inherits all methods from L<Mojo::Asset> and implements
the following new ones.
=head2 add_chunk
$file = $file->add_chunk('foo bar baz');
Add chunk of data.
=head2 contains
my $position = $file->contains('bar');
Check if asset contains a specific string.
=head2 get_chunk
my $bytes = $file->get_chunk($offset);
my $bytes = $file->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum
chunk size of C<131072> bytes (128KB).
=head2 is_file
my $true = $file->is_file;
True.
=head2 move_to
$file = $file->move_to('/home/sri/bar.txt');
Move asset data into a specific file and disable L</"cleanup">.
=head2 mtime
my $mtime = $file->mtime;
Modification time of asset.
=head2 size
my $size = $file->size;
Size of asset data in bytes.
=head2 slurp
my $bytes = $file->slurp;
Read all asset data at once.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_ASSET_FILE
$fatpacked{"Mojo/Asset/Memory.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_ASSET_MEMORY';
package Mojo::Asset::Memory;
use Mojo::Base 'Mojo::Asset';
use Mojo::Asset::File;
use Mojo::Util 'spurt';
# Last modified default
my $MTIME = time;
has 'auto_upgrade';
has max_memory_size => sub { $ENV{MOJO_MAX_MEMORY_SIZE} || 262144 };
has mtime => sub {$MTIME};
sub add_chunk {
my ($self, $chunk) = @_;
# Upgrade if necessary
$self->{content} .= $chunk // '';
return $self
if !$self->auto_upgrade || $self->size <= $self->max_memory_size;
my $file = Mojo::Asset::File->new;
return $file->add_chunk($self->emit(upgrade => $file)->slurp);
}
sub contains {
my ($self, $str) = @_;
my $start = $self->start_range;
my $pos = index $self->{content}, $str, $start;
$pos -= $start if $start && $pos >= 0;
my $end = $self->end_range;
return $end && ($pos + length $str) >= $end ? -1 : $pos;
}
sub get_chunk {
my ($self, $offset, $max) = @_;
$max //= 131072;
$offset += $self->start_range;
if (my $end = $self->end_range) {
$max = $end + 1 - $offset if ($offset + $max) > $end;
}
return substr shift->{content}, $offset, $max;
}
sub move_to {
my ($self, $to) = @_;
spurt $self->{content}, $to;
return $self;
}
sub new { shift->SUPER::new(@_, content => '') }
sub size { length shift->{content} }
sub slurp { shift->{content} }
1;
=encoding utf8
=head1 NAME
Mojo::Asset::Memory - In-memory storage for HTTP content
=head1 SYNOPSIS
use Mojo::Asset::Memory;
my $mem = Mojo::Asset::Memory->new;
$mem->add_chunk('foo bar baz');
say $mem->slurp;
=head1 DESCRIPTION
L<Mojo::Asset::Memory> is an in-memory storage backend for HTTP content.
=head1 EVENTS
L<Mojo::Asset::Memory> inherits all events from L<Mojo::Asset> and can emit
the following new ones.
=head2 upgrade
$mem->on(upgrade => sub {
my ($mem, $file) = @_;
...
});
Emitted when asset gets upgraded to a L<Mojo::Asset::File> object.
$mem->on(upgrade => sub {
my ($mem, $file) = @_;
$file->tmpdir('/tmp');
});
=head1 ATTRIBUTES
L<Mojo::Asset::Memory> inherits all attributes from L<Mojo::Asset> and
implements the following new ones.
=head2 auto_upgrade
my $bool = $mem->auto_upgrade;
$mem = $mem->auto_upgrade($bool);
Try to detect if content size exceeds L</"max_memory_size"> limit and
automatically upgrade to a L<Mojo::Asset::File> object.
=head2 max_memory_size
my $size = $mem->max_memory_size;
$mem = $mem->max_memory_size(1024);
Maximum size in bytes of data to keep in memory before automatically upgrading
to a L<Mojo::Asset::File> object, defaults to the value of the
C<MOJO_MAX_MEMORY_SIZE> environment variable or C<262144> (256KB).
=head2 mtime
my $mtime = $mem->mtime;
$mem = $mem->mtime(1408567500);
Modification time of asset, defaults to the time this class was loaded.
=head1 METHODS
L<Mojo::Asset::Memory> inherits all methods from L<Mojo::Asset> and implements
the following new ones.
=head2 add_chunk
$mem = $mem->add_chunk('foo bar baz');
my $file = $mem->add_chunk('abc' x 262144);
Add chunk of data and upgrade to L<Mojo::Asset::File> object if necessary.
=head2 contains
my $position = $mem->contains('bar');
Check if asset contains a specific string.
=head2 get_chunk
my $bytes = $mem->get_chunk($offset);
my $bytes = $mem->get_chunk($offset, $max);
Get chunk of data starting from a specific position, defaults to a maximum
chunk size of C<131072> bytes (128KB).
=head2 move_to
$mem = $mem->move_to('/home/sri/foo.txt');
Move asset data into a specific file.
=head2 new
my $mem = Mojo::Asset::Memory->new;
Construct a new L<Mojo::Asset::Memory> object.
=head2 size
my $size = $mem->size;
Size of asset data in bytes.
=head2 slurp
my $bytes = mem->slurp;
Read all asset data at once.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_ASSET_MEMORY
$fatpacked{"Mojo/Base.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_BASE';
package Mojo::Base;
use strict;
use warnings;
use utf8;
use feature ();
# No imports because we get subclassed, a lot!
use Carp ();
# Only Perl 5.14+ requires it on demand
use IO::Handle ();
# Protect subclasses using AUTOLOAD
sub DESTROY { }
sub import {
my $class = shift;
return unless my $flag = shift;
# Base
if ($flag eq '-base') { $flag = $class }
# Strict
elsif ($flag eq '-strict') { $flag = undef }
# Module
elsif ((my $file = $flag) && !$flag->can('new')) {
$file =~ s!::|'!/!g;
require "$file.pm";
}
# ISA
if ($flag) {
my $caller = caller;
no strict 'refs';
push @{"${caller}::ISA"}, $flag;
*{"${caller}::has"} = sub { attr($caller, @_) };
}
# Mojo modules are strict!
$_->import for qw(strict warnings utf8);
feature->import(':5.10');
}
sub attr {
my ($self, $attrs, $default) = @_;
return unless (my $class = ref $self || $self) && $attrs;
Carp::croak 'Default has to be a code reference or constant value'
if ref $default && ref $default ne 'CODE';
for my $attr (@{ref $attrs eq 'ARRAY' ? $attrs : [$attrs]}) {
Carp::croak qq{Attribute "$attr" invalid} unless $attr =~ /^[a-zA-Z_]\w*$/;
# Header (check arguments)
my $code = "package $class;\nsub $attr {\n if (\@_ == 1) {\n";
# No default value (return value)
unless (defined $default) { $code .= " return \$_[0]{'$attr'};" }
# Default value
else {
# Return value
$code .= " return \$_[0]{'$attr'} if exists \$_[0]{'$attr'};\n";
# Return default value
$code .= " return \$_[0]{'$attr'} = ";
$code .= ref $default eq 'CODE' ? '$default->($_[0]);' : '$default;';
}
# Footer (store value and return invocant)
$code .= "\n }\n \$_[0]{'$attr'} = \$_[1];\n \$_[0];\n}";
warn "-- Attribute $attr in $class\n$code\n\n" if $ENV{MOJO_BASE_DEBUG};
Carp::croak "Mojo::Base error: $@" unless eval "$code;1";
}
}
sub new {
my $class = shift;
bless @_ ? @_ > 1 ? {@_} : {%{$_[0]}} : {}, ref $class || $class;
}
sub tap {
my ($self, $cb) = (shift, shift);
$_->$cb(@_) for $self;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Base - Minimal base class for Mojo projects
=head1 SYNOPSIS
package Cat;
use Mojo::Base -base;
has name => 'Nyan';
has [qw(birds mice)] => 2;
package Tiger;
use Mojo::Base 'Cat';
has friend => sub { Cat->new };
has stripes => 42;
package main;
use Mojo::Base -strict;
my $mew = Cat->new(name => 'Longcat');
say $mew->mice;
say $mew->mice(3)->birds(4)->mice;
my $rawr = Tiger->new(stripes => 23, mice => 0);
say $rawr->tap(sub { $_->friend->name('Tacgnol') })->mice;
=head1 DESCRIPTION
L<Mojo::Base> is a simple base class for L<Mojo> projects.
# Automatically enables "strict", "warnings", "utf8" and Perl 5.10 features
use Mojo::Base -strict;
use Mojo::Base -base;
use Mojo::Base 'SomeBaseClass';
All three forms save a lot of typing.
# use Mojo::Base -strict;
use strict;
use warnings;
use utf8;
use feature ':5.10';
use IO::Handle ();
# use Mojo::Base -base;
use strict;
use warnings;
use utf8;
use feature ':5.10';
use IO::Handle ();
use Mojo::Base;
push @ISA, 'Mojo::Base';
sub has { Mojo::Base::attr(__PACKAGE__, @_) }
# use Mojo::Base 'SomeBaseClass';
use strict;
use warnings;
use utf8;
use feature ':5.10';
use IO::Handle ();
require SomeBaseClass;
push @ISA, 'SomeBaseClass';
use Mojo::Base;
sub has { Mojo::Base::attr(__PACKAGE__, @_) }
=head1 FUNCTIONS
L<Mojo::Base> implements the following functions, which can be imported with
the C<-base> flag or by setting a base class.
=head2 has
has 'name';
has [qw(name1 name2 name3)];
has name => 'foo';
has name => sub {...};
has [qw(name1 name2 name3)] => 'foo';
has [qw(name1 name2 name3)] => sub {...};
Create attributes for hash-based objects, just like the L</"attr"> method.
=head1 METHODS
L<Mojo::Base> implements the following methods.
=head2 attr
$object->attr('name');
SubClass->attr('name');
SubClass->attr([qw(name1 name2 name3)]);
SubClass->attr(name => 'foo');
SubClass->attr(name => sub {...});
SubClass->attr([qw(name1 name2 name3)] => 'foo');
SubClass->attr([qw(name1 name2 name3)] => sub {...});
Create attribute accessor for hash-based objects, an array reference can be
used to create more than one at a time. Pass an optional second argument to
set a default value, it should be a constant or a callback. The callback will
be executed at accessor read time if there's no set value. Accessors can be
chained, that means they return their invocant when they are called with an
argument.
=head2 new
my $object = SubClass->new;
my $object = SubClass->new(name => 'value');
my $object = SubClass->new({name => 'value'});
This base class provides a basic constructor for hash-based objects. You can
pass it either a hash or a hash reference with attribute values.
=head2 tap
$object = $object->tap(sub {...});
$object = $object->tap($method);
$object = $object->tap($method, @args);
K combinator, tap into a method chain to perform operations on an object
within the chain. The object will be the first argument passed to the callback
and is also available as C<$_>.
# Longer version
$object = $object->tap(sub { $_->$method(@args) });
=head1 DEBUGGING
You can set the C<MOJO_BASE_DEBUG> environment variable to get some advanced
diagnostics information printed to C<STDERR>.
MOJO_BASE_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_BASE
$fatpacked{"Mojo/ByteStream.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_BYTESTREAM';
package Mojo::ByteStream;
use Mojo::Base -strict;
use overload bool => sub {1}, '""' => sub { ${$_[0]} }, fallback => 1;
use Exporter 'import';
use Mojo::Collection;
use Mojo::Util;
our @EXPORT_OK = ('b');
# Turn most functions from Mojo::Util into methods
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)
);
for my $name (@UTILS) {
my $sub = Mojo::Util->can($name);
Mojo::Util::monkey_patch __PACKAGE__, $name, sub {
my $self = shift;
$$self = $sub->($$self, @_);
return $self;
};
}
sub b { __PACKAGE__->new(@_) }
sub clone { $_[0]->new(${$_[0]}) }
sub decode { shift->_delegate(\&Mojo::Util::decode, @_) }
sub encode { shift->_delegate(\&Mojo::Util::encode, @_) }
sub new {
my $class = shift;
return bless \(my $dummy = join '', @_), ref $class || $class;
}
sub say {
my ($self, $handle) = @_;
$handle ||= \*STDOUT;
say $handle $$self;
return $self;
}
sub secure_compare { Mojo::Util::secure_compare ${shift()}, shift }
sub size { length ${$_[0]} }
sub split {
my ($self, $pattern) = @_;
return Mojo::Collection->new(map { $self->new($_) } split $pattern, $$self);
}
sub tap { shift->Mojo::Base::tap(@_) }
sub to_string { ${$_[0]} }
sub _delegate {
my ($self, $sub) = (shift, shift);
$$self = $sub->(shift || 'UTF-8', $$self);
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::ByteStream - ByteStream
=head1 SYNOPSIS
use Mojo::ByteStream;
# Manipulate bytestream
my $stream = Mojo::ByteStream->new('foo_bar_baz');
say $stream->camelize;
# Chain methods
my $stream = Mojo::ByteStream->new('foo bar baz')->quote;
$stream = $stream->unquote->encode('UTF-8')->b64_encode('');
say "$stream";
# Use the alternative constructor
use Mojo::ByteStream 'b';
my $stream = b('foobarbaz')->b64_encode('')->say;
=head1 DESCRIPTION
L<Mojo::ByteStream> is a scalar-based container for bytestreams that provides
a more friendly API for many of the functions in L<Mojo::Util>.
# Access scalar directly to manipulate bytestream
my $stream = Mojo::ByteStream->new('foo');
$$stream .= 'bar';
=head1 FUNCTIONS
L<Mojo::ByteStream> implements the following functions, which can be imported
individually.
=head2 b
my $stream = b('test123');
Construct a new scalar-based L<Mojo::ByteStream> object.
=head1 METHODS
L<Mojo::ByteStream> implements the following methods.
=head2 b64_decode
$stream = $stream->b64_decode;
Base64 decode bytestream with L<Mojo::Util/"b64_decode">.
=head2 b64_encode
$stream = $stream->b64_encode;
$stream = $stream->b64_encode("\n");
Base64 encode bytestream with L<Mojo::Util/"b64_encode">.
b('foo bar baz')->b64_encode('')->say;
=head2 camelize
$stream = $stream->camelize;
Camelize bytestream with L<Mojo::Util/"camelize">.
=head2 clone
my $stream2 = $stream->clone;
Clone bytestream.
=head2 decamelize
$stream = $stream->decamelize;
Decamelize bytestream with L<Mojo::Util/"decamelize">.
=head2 decode
$stream = $stream->decode;
$stream = $stream->decode('iso-8859-1');
Decode bytestream with L<Mojo::Util/"decode">, defaults to C<UTF-8>.
$stream->decode('UTF-16LE')->unquote->trim->say;
=head2 encode
$stream = $stream->encode;
$stream = $stream->encode('iso-8859-1');
Encode bytestream with L<Mojo::Util/"encode">, defaults to C<UTF-8>.
$stream->trim->quote->encode->say;
=head2 hmac_sha1_sum
$stream = $stream->hmac_sha1_sum('passw0rd');
Generate HMAC-SHA1 checksum for bytestream with L<Mojo::Util/"hmac_sha1_sum">.
b('foo bar baz')->hmac_sha1_sum('secr3t')->quote->say;
=head2 html_unescape
$stream = $stream->html_unescape;
Unescape all HTML entities in bytestream with L<Mojo::Util/"html_unescape">.
b('&lt;html&gt;')->html_unescape->url_escape->say;
=head2 md5_bytes
$stream = $stream->md5_bytes;
Generate binary MD5 checksum for bytestream with L<Mojo::Util/"md5_bytes">.
=head2 md5_sum
$stream = $stream->md5_sum;
Generate MD5 checksum for bytestream with L<Mojo::Util/"md5_sum">.
=head2 new
my $stream = Mojo::ByteStream->new('test123');
Construct a new scalar-based L<Mojo::ByteStream> object.
=head2 punycode_decode
$stream = $stream->punycode_decode;
Punycode decode bytestream with L<Mojo::Util/"punycode_decode">.
=head2 punycode_encode
$stream = $stream->punycode_encode;
Punycode encode bytestream with L<Mojo::Util/"punycode_encode">.
=head2 quote
$stream = $stream->quote;
Quote bytestream with L<Mojo::Util/"quote">.
=head2 say
$stream = $stream->say;
$stream = $stream->say(*STDERR);
Print bytestream to handle and append a newline, defaults to C<STDOUT>.
=head2 secure_compare
my $bool = $stream->secure_compare($str);
Compare bytestream with L<Mojo::Util/"secure_compare">.
say 'Match!' if b('foo')->secure_compare('foo');
=head2 sha1_bytes
$stream = $stream->sha1_bytes;
Generate binary SHA1 checksum for bytestream with L<Mojo::Util/"sha1_bytes">.
=head2 sha1_sum
$stream = $stream->sha1_sum;
Generate SHA1 checksum for bytestream with L<Mojo::Util/"sha1_sum">.
=head2 size
my $size = $stream->size;
Size of bytestream.
=head2 slurp
$stream = $stream->slurp;
Read all data at once from file into bytestream with L<Mojo::Util/"slurp">.
b('/home/sri/myapp.pl')->slurp->split("\n")->shuffle->join("\n")->say;
=head2 spurt
$stream = $stream->spurt('/home/sri/myapp.pl');
Write all data from bytestream at once to file with L<Mojo::Util/"spurt">.
b('/home/sri/foo.txt')->slurp->squish->spurt('/home/sri/bar.txt');
=head2 split
my $collection = $stream->split(',');
Turn bytestream into L<Mojo::Collection> object containing L<Mojo::ByteStream>
objects.
b('a,b,c')->split(',')->quote->join(',')->say;
=head2 squish
$stream = $stream->squish;
Trim whitespace characters from both ends of bytestream and then change all
consecutive groups of whitespace into one space each with
L<Mojo::Util/"squish">.
=head2 tap
$stream = $stream->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 to_string
my $str = $stream->to_string;
Stringify bytestream.
=head2 trim
$stream = $stream->trim;
Trim whitespace characters from both ends of bytestream with
L<Mojo::Util/"trim">.
=head2 unindent
$stream = $stream->unindent;
Unindent bytestream with L<Mojo::Util/"unindent">.
=head2 unquote
$stream = $stream->unquote;
Unquote bytestream with L<Mojo::Util/"unquote">.
=head2 url_escape
$stream = $stream->url_escape;
$stream = $stream->url_escape('^A-Za-z0-9\-._~');
Percent encode all unsafe characters in bytestream with
L<Mojo::Util/"url_escape">.
b('foo bar baz')->url_escape->say;
=head2 url_unescape
$stream = $stream->url_unescape;
Decode percent encoded characters in bytestream with
L<Mojo::Util/"url_unescape">.
b('%3Chtml%3E')->url_unescape->xml_escape->say;
=head2 xml_escape
$stream = $stream->xml_escape;
Escape only the characters C<&>, C<E<lt>>, C<E<gt>>, C<"> and C<'> in
bytestream with L<Mojo::Util/"xml_escape">.
=head2 xor_encode
$stream = $stream->xor_encode($key);
XOR encode bytestream with L<Mojo::Util/"xor_encode">.
=head1 OPERATORS
L<Mojo::ByteStream> overloads the following operators.
=head2 bool
my $bool = !!$bytestream;
Always true.
=head2 stringify
my $str = "$bytestream";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_BYTESTREAM
$fatpacked{"Mojo/Cache.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_CACHE';
package Mojo::Cache;
use Mojo::Base -base;
has 'max_keys' => 100;
sub get { (shift->{cache} || {})->{shift()} }
sub set {
my ($self, $key, $value) = @_;
return $self unless (my $max = $self->max_keys) > 0;
my $cache = $self->{cache} ||= {};
my $queue = $self->{queue} ||= [];
delete $cache->{shift @$queue} while @$queue >= $max;
push @$queue, $key unless exists $cache->{$key};
$cache->{$key} = $value;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cache - Naive in-memory cache
=head1 SYNOPSIS
use Mojo::Cache;
my $cache = Mojo::Cache->new(max_keys => 50);
$cache->set(foo => 'bar');
my $foo = $cache->get('foo');
=head1 DESCRIPTION
L<Mojo::Cache> is a naive in-memory cache with size limits.
=head1 ATTRIBUTES
L<Mojo::Cache> implements the following attributes.
=head2 max_keys
my $max = $cache->max_keys;
$cache = $cache->max_keys(50);
Maximum number of cache keys, defaults to C<100>. Setting the value to C<0>
will disable caching.
=head1 METHODS
L<Mojo::Cache> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 get
my $value = $cache->get('foo');
Get cached value.
=head2 set
$cache = $cache->set(foo => 'bar');
Set cached value.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_CACHE
$fatpacked{"Mojo/Collection.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_COLLECTION';
package Mojo::Collection;
use Mojo::Base -strict;
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 {
$_[0]->new(grep { defined && (ref || length) } @{$_[0]});
}
sub each {
my ($self, $cb) = @_;
return @$self unless $cb;
my $i = 1;
$_->$cb($i++) for @$self;
return $self;
}
sub first {
my ($self, $cb) = @_;
return $self->[0] unless $cb;
return List::Util::first { $cb->($_) } @$self if ref $cb eq 'CODE';
return List::Util::first { $_ =~ $cb } @$self;
}
sub flatten { $_[0]->new(_flatten(@{$_[0]})) }
sub grep {
my ($self, $cb) = @_;
return $self->new(grep { $cb->($_) } @$self) if ref $cb eq 'CODE';
return $self->new(grep { $_ =~ $cb } @$self);
}
sub join {
Mojo::ByteStream->new(join $_[1] // '', map {"$_"} @{$_[0]});
}
sub last { shift->[-1] }
sub map {
my ($self, $cb) = (shift, shift);
return $self->new(map { $_->$cb(@_) } @$self);
}
sub new {
my $class = shift;
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);
goto &List::Util::reduce;
}
sub reverse { $_[0]->new(reverse @{$_[0]}) }
sub shuffle { $_[0]->new(List::Util::shuffle @{$_[0]}) }
sub size { scalar @{$_[0]} }
sub slice {
my $self = shift;
return $self->new(@$self[@_]);
}
sub sort {
my ($self, $cb) = @_;
return $self->new(sort @$self) unless $cb;
my $caller = caller;
no strict 'refs';
my @sorted = sort {
local (*{"${caller}::a"}, *{"${caller}::b"}) = (\$a, \$b);
$a->$cb($b);
} @$self;
return $self->new(@sorted);
}
sub tap { shift->Mojo::Base::tap(@_) }
sub to_array { [@{shift()}] }
sub uniq {
my %seen;
return $_[0]->new(grep { !$seen{$_}++ } @{$_[0]});
}
sub _flatten {
map { _ref($_) ? _flatten(@$_) : $_ } @_;
}
sub _ref { ref $_[0] eq 'ARRAY' || blessed $_[0] && $_[0]->isa(__PACKAGE__) }
1;
=encoding utf8
=head1 NAME
Mojo::Collection - Collection
=head1 SYNOPSIS
use Mojo::Collection;
# Manipulate collection
my $collection = Mojo::Collection->new(qw(just works));
unshift @$collection, 'it';
say $collection->join("\n");
# Chain methods
$collection->map(sub { ucfirst })->shuffle->each(sub {
my ($word, $count) = @_;
say "$count: $word";
});
# Use the alternative constructor
use Mojo::Collection 'c';
c(qw(a b c))->join('/')->url_escape->say;
=head1 DESCRIPTION
L<Mojo::Collection> is an array-based container for collections.
# Access array directly to manipulate collection
my $collection = Mojo::Collection->new(1 .. 25);
$collection->[23] += 100;
say for @$collection;
=head1 FUNCTIONS
L<Mojo::Collection> implements the following functions, which can be imported
individually.
=head2 c
my $collection = c(1, 2, 3);
Construct a new array-based L<Mojo::Collection> object.
=head1 METHODS
L<Mojo::Collection> implements the following methods.
=head2 compact
my $new = $collection->compact;
Create a new collection with all elements that are defined and not an empty
string.
=head2 each
my @elements = $collection->each;
$collection = $collection->each(sub {...});
Evaluate callback for each element in collection or return all elements as a
list if none has been provided. The element will be the first argument passed
to the callback and is also available as C<$_>.
# Make a numbered list
$collection->each(sub {
my ($e, $count) = @_;
say "$count: $e";
});
=head2 first
my $first = $collection->first;
my $first = $collection->first(qr/foo/);
my $first = $collection->first(sub {...});
Evaluate regular expression or callback for each element in collection and
return the first one that matched the regular expression, or for which the
callback returned true. The element will be the first argument passed to the
callback and is also available as C<$_>.
# Find first value that is greater than 5
my $greater = $collection->first(sub { $_ > 5 });
=head2 flatten
my $new = $collection->flatten;
Flatten nested collections/arrays recursively and create a new collection with
all elements.
=head2 grep
my $new = $collection->grep(qr/foo/);
my $new = $collection->grep(sub {...});
Evaluate regular expression or callback for each element in collection and
create a new collection with all elements that matched the regular expression,
or for which the callback returned true. The element will be the first
argument passed to the callback and is also available as C<$_>.
# Find all values that contain the word "mojo"
my $interesting = $collection->grep(qr/mojo/i);
=head2 join
my $stream = $collection->join;
my $stream = $collection->join("\n");
Turn collection into L<Mojo::ByteStream>.
# Join all values with commas
$collection->join(', ')->say;
=head2 last
my $last = $collection->last;
Return the last element in collection.
=head2 map
my $new = $collection->map(sub {...});
my $new = $collection->map($method);
my $new = $collection->map($method, @args);
Evaluate callback for, or call method on, each element in collection and
create a new collection from the results. The element will be the first
argument passed to the callback and is also available as C<$_>.
# Longer version
my $new = $collection->map(sub { $_->$method(@args) });
# Append the word "mojo" to all values
my $mojoified = $collection->map(sub { $_ . 'mojo' });
=head2 new
my $collection = Mojo::Collection->new(1, 2, 3);
Construct a new array-based L<Mojo::Collection> object.
=head2 reduce
my $result = $collection->reduce(sub {...});
my $result = $collection->reduce(sub {...}, $initial);
Reduce elements in collection with callback, the first element will be used as
initial value if none has been provided.
# Calculate the sum of all values
my $sum = $collection->reduce(sub { $a + $b });
# Count how often each value occurs in collection
my $hash = $collection->reduce(sub { $a->{$b}++; $a }, {});
=head2 reverse
my $new = $collection->reverse;
Create a new collection with all elements in reverse order.
=head2 slice
my $new = $collection->slice(4 .. 7);
Create a new collection with all selected elements.
=head2 shuffle
my $new = $collection->shuffle;
Create a new collection with all elements in random order.
=head2 size
my $size = $collection->size;
Number of elements in collection.
=head2 sort
my $new = $collection->sort;
my $new = $collection->sort(sub {...});
Sort elements based on return value of callback and create a new collection
from the results.
# Sort values case insensitive
my $insensitive = $collection->sort(sub { uc($a) cmp uc($b) });
=head2 tap
$collection = $collection->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 to_array
my $array = $collection->to_array;
Turn collection into array reference.
=head2 uniq
my $new = $collection->uniq;
Create a new collection without duplicate elements.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_COLLECTION
$fatpacked{"Mojo/Content.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_CONTENT';
package Mojo::Content;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Compress::Raw::Zlib qw(WANT_GZIP Z_STREAM_END);
use Mojo::Headers;
use Scalar::Util 'looks_like_number';
has [qw(auto_decompress auto_relax expect_close relaxed skip_body)];
has headers => sub { Mojo::Headers->new };
has max_buffer_size => sub { $ENV{MOJO_MAX_BUFFER_SIZE} || 262144 };
has max_leftover_size => sub { $ENV{MOJO_MAX_LEFTOVER_SIZE} || 262144 };
my $BOUNDARY_RE
= qr!multipart.*boundary\s*=\s*(?:"([^"]+)"|([\w'(),.:?\-+/]+))!i;
sub body_contains {
croak 'Method "body_contains" not implemented by subclass';
}
sub body_size { croak 'Method "body_size" not implemented by subclass' }
sub boundary {
(shift->headers->content_type // '') =~ $BOUNDARY_RE ? $1 // $2 : undef;
}
sub build_body { shift->_build('get_body_chunk') }
sub build_headers { shift->_build('get_header_chunk') }
sub charset {
my $type = shift->headers->content_type // '';
return $type =~ /charset\s*=\s*"?([^"\s;]+)"?/i ? $1 : undef;
}
sub clone {
my $self = shift;
return undef if $self->is_dynamic;
return $self->new(headers => $self->headers->clone);
}
sub generate_body_chunk {
my ($self, $offset) = @_;
$self->emit(drain => $offset)
if !delete $self->{delay} && !length($self->{body_buffer} // '');
my $chunk = delete $self->{body_buffer} // '';
return $self->{eof} ? '' : undef unless length $chunk;
return $chunk;
}
sub get_body_chunk {
croak 'Method "get_body_chunk" not implemented by subclass';
}
sub get_header_chunk {
my ($self, $offset) = @_;
unless (defined $self->{header_buffer}) {
my $headers = $self->headers->to_string;
$self->{header_buffer}
= $headers ? "$headers\x0d\x0a\x0d\x0a" : "\x0d\x0a";
}
return substr $self->{header_buffer}, $offset, 131072;
}
sub header_size { length shift->build_headers }
sub is_chunked { !!shift->headers->transfer_encoding }
sub is_compressed { (shift->headers->content_encoding // '') =~ /^gzip$/i }
sub is_dynamic { $_[0]{dynamic} && !defined $_[0]->headers->content_length }
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub is_multipart {undef}
sub is_parsing_body { (shift->{state} // '') eq 'body' }
sub leftovers { shift->{buffer} }
sub parse {
my $self = shift;
# Headers
$self->_parse_until_body(@_);
return $self if $self->{state} eq 'headers';
# Chunked content
$self->{real_size} //= 0;
if ($self->is_chunked && $self->{state} ne 'headers') {
$self->_parse_chunked;
$self->{state} = 'finished' if ($self->{chunk_state} // '') eq 'finished';
}
# Not chunked, pass through to second buffer
else {
$self->{real_size} += length $self->{pre_buffer};
my $limit = $self->is_finished
&& length($self->{buffer}) > $self->max_leftover_size;
$self->{buffer} .= $self->{pre_buffer} unless $limit;
$self->{pre_buffer} = '';
}
# No content
if ($self->skip_body) {
$self->{state} = 'finished';
return $self;
}
# Relaxed parsing
my $headers = $self->headers;
my $len = $headers->content_length // '';
if ($self->auto_relax && !length $len) {
my $connection = lc($headers->connection // '');
$self->relaxed(1)
if $connection eq 'close' || (!$connection && $self->expect_close);
}
# Chunked or relaxed content
if ($self->is_chunked || $self->relaxed) {
$self->_decompress($self->{buffer} //= '');
$self->{size} += length $self->{buffer};
$self->{buffer} = '';
return $self;
}
# Normal content
$len = 0 unless looks_like_number $len;
if ((my $need = $len - ($self->{size} ||= 0)) > 0) {
my $len = length $self->{buffer};
my $chunk = substr $self->{buffer}, 0, $need > $len ? $len : $need, '';
$self->_decompress($chunk);
$self->{size} += length $chunk;
}
$self->{state} = 'finished' if $len <= $self->progress;
return $self;
}
sub parse_body {
my $self = shift;
$self->{state} = 'body';
return $self->parse(@_);
}
sub progress {
my $self = shift;
return 0 unless my $state = $self->{state};
return 0 unless $state eq 'body' || $state eq 'finished';
return $self->{raw_size} - ($self->{header_size} || 0);
}
sub write {
my ($self, $chunk, $cb) = @_;
$self->{dynamic} = 1;
if (defined $chunk) { $self->{body_buffer} .= $chunk }
else { $self->{delay} = 1 }
$self->once(drain => $cb) if $cb;
$self->{eof} = 1 if defined $chunk && $chunk eq '';
return $self;
}
sub write_chunk {
my ($self, $chunk, $cb) = @_;
$self->headers->transfer_encoding('chunked') unless $self->is_chunked;
$self->write(defined $chunk ? $self->_build_chunk($chunk) : $chunk, $cb);
$self->{eof} = 1 if defined $chunk && $chunk eq '';
return $self;
}
sub _build {
my ($self, $method) = @_;
my ($buffer, $offset) = ('', 0);
while (1) {
# No chunk yet, try again
next unless defined(my $chunk = $self->$method($offset));
# End of part
last unless my $len = length $chunk;
$offset += $len;
$buffer .= $chunk;
}
return $buffer;
}
sub _build_chunk {
my ($self, $chunk) = @_;
# End
return "\x0d\x0a0\x0d\x0a\x0d\x0a" unless length $chunk;
# First chunk has no leading CRLF
my $crlf = $self->{chunks}++ ? "\x0d\x0a" : '';
return $crlf . sprintf('%x', length $chunk) . "\x0d\x0a$chunk";
}
sub _decompress {
my ($self, $chunk) = @_;
# No compression
return $self->emit(read => $chunk)
unless $self->auto_decompress && $self->is_compressed;
# Decompress
$self->{post_buffer} .= $chunk;
my $gz = $self->{gz}
//= Compress::Raw::Zlib::Inflate->new(WindowBits => WANT_GZIP);
my $status = $gz->inflate(\$self->{post_buffer}, my $out);
$self->emit(read => $out) if defined $out;
# Replace Content-Encoding with Content-Length
$self->headers->content_length($gz->total_out)->remove('Content-Encoding')
if $status == Z_STREAM_END;
# Check buffer size
@$self{qw(state limit)} = ('finished', 1)
if length($self->{post_buffer} // '') > $self->max_buffer_size;
}
sub _parse_chunked {
my $self = shift;
# Trailing headers
return $self->_parse_chunked_trailing_headers
if ($self->{chunk_state} // '') eq 'trailing_headers';
while (my $len = length $self->{pre_buffer}) {
# Start new chunk (ignore the chunk extension)
unless ($self->{chunk_len}) {
last
unless $self->{pre_buffer} =~ s/^(?:\x0d?\x0a)?([0-9a-fA-F]+).*\x0a//;
next if $self->{chunk_len} = hex $1;
# Last chunk
$self->{chunk_state} = 'trailing_headers';
last;
}
# Remove as much as possible from payload
$len = $self->{chunk_len} if $self->{chunk_len} < $len;
$self->{buffer} .= substr $self->{pre_buffer}, 0, $len, '';
$self->{real_size} += $len;
$self->{chunk_len} -= $len;
}
# Trailing headers
$self->_parse_chunked_trailing_headers
if ($self->{chunk_state} // '') eq 'trailing_headers';
# Check buffer size
@$self{qw(state limit)} = ('finished', 1)
if length($self->{pre_buffer} // '') > $self->max_buffer_size;
}
sub _parse_chunked_trailing_headers {
my $self = shift;
my $headers = $self->headers->parse(delete $self->{pre_buffer});
return unless $headers->is_finished;
$self->{chunk_state} = 'finished';
# Take care of leftover and replace Transfer-Encoding with Content-Length
$self->{buffer} .= $headers->leftovers;
$headers->remove('Transfer-Encoding');
$headers->content_length($self->{real_size}) unless $headers->content_length;
}
sub _parse_headers {
my $self = shift;
my $headers = $self->headers->parse(delete $self->{pre_buffer});
return unless $headers->is_finished;
$self->{state} = 'body';
# Take care of leftovers
my $leftovers = $self->{pre_buffer} = $headers->leftovers;
$self->{header_size} = $self->{raw_size} - length $leftovers;
}
sub _parse_until_body {
my ($self, $chunk) = @_;
$self->{raw_size} += length($chunk //= '');
$self->{pre_buffer} .= $chunk;
$self->_parse_headers if ($self->{state} ||= 'headers') eq 'headers';
$self->emit('body') if $self->{state} ne 'headers' && !$self->{body}++;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content - HTTP content base class
=head1 SYNOPSIS
package Mojo::Content::MyContent;
use Mojo::Base 'Mojo::Content';
sub body_contains {...}
sub body_size {...}
sub get_body_chunk {...}
=head1 DESCRIPTION
L<Mojo::Content> is an abstract base class for HTTP content based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Content> inherits all events from L<Mojo::EventEmitter> and can emit
the following new ones.
=head2 body
$content->on(body => sub {
my $content = shift;
...
});
Emitted once all headers have been parsed and the body starts.
$content->on(body => sub {
my $content = shift;
$content->auto_upgrade(0) if $content->headers->header('X-No-MultiPart');
});
=head2 drain
$content->on(drain => sub {
my ($content, $offset) = @_;
...
});
Emitted once all data has been written.
$content->on(drain => sub {
my $content = shift;
$content->write_chunk(time);
});
=head2 read
$content->on(read => sub {
my ($content, $bytes) = @_;
...
});
Emitted when a new chunk of content arrives.
$content->unsubscribe('read');
$content->on(read => sub {
my ($content, $bytes) = @_;
say "Streaming: $bytes";
});
=head1 ATTRIBUTES
L<Mojo::Content> implements the following attributes.
=head2 auto_decompress
my $bool = $content->auto_decompress;
$content = $content->auto_decompress($bool);
Decompress content automatically if L</"is_compressed"> is true.
=head2 auto_relax
my $bool = $content->auto_relax;
$content = $content->auto_relax($bool);
Try to detect when relaxed parsing is necessary.
=head2 expect_close
my $bool = $content->expect_close;
$content = $content->expect_close($bool);
Expect a response that is terminated with a connection close.
=head2 headers
my $headers = $content->headers;
$content = $content->headers(Mojo::Headers->new);
Content headers, defaults to a L<Mojo::Headers> object.
=head2 max_buffer_size
my $size = $content->max_buffer_size;
$content = $content->max_buffer_size(1024);
Maximum size in bytes of buffer for content parser, defaults to the value of
the C<MOJO_MAX_BUFFER_SIZE> environment variable or C<262144> (256KB).
=head2 max_leftover_size
my $size = $content->max_leftover_size;
$content = $content->max_leftover_size(1024);
Maximum size in bytes of buffer for pipelined HTTP requests, defaults to the
value of the C<MOJO_MAX_LEFTOVER_SIZE> environment variable or C<262144>
(256KB).
=head2 relaxed
my $bool = $content->relaxed;
$content = $content->relaxed($bool);
Activate relaxed parsing for responses that are terminated with a connection
close.
=head2 skip_body
my $bool = $content->skip_body;
$content = $content->skip_body($bool);
Skip body parsing and finish after headers.
=head1 METHODS
L<Mojo::Content> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 body_contains
my $bool = $content->body_contains('foo bar baz');
Check if content contains a specific string. Meant to be overloaded in a
subclass.
=head2 body_size
my $size = $content->body_size;
Content size in bytes. Meant to be overloaded in a subclass.
=head2 boundary
my $boundary = $content->boundary;
Extract multipart boundary from C<Content-Type> header.
=head2 build_body
my $str = $content->build_body;
Render whole body.
=head2 build_headers
my $str = $content->build_headers;
Render all headers.
=head2 charset
my $charset = $content->charset;
Extract charset from C<Content-Type> header.
=head2 clone
my $clone = $content->clone;
Clone content if possible, otherwise return C<undef>.
=head2 generate_body_chunk
my $bytes = $content->generate_body_chunk(0);
Generate dynamic content.
=head2 get_body_chunk
my $bytes = $content->get_body_chunk(0);
Get a chunk of content starting from a specific position. Meant to be
overloaded in a subclass.
=head2 get_header_chunk
my $bytes = $content->get_header_chunk(13);
Get a chunk of the headers starting from a specific position.
=head2 header_size
my $size = $content->header_size;
Size of headers in bytes.
=head2 is_chunked
my $bool = $content->is_chunked;
Check if content is chunked.
=head2 is_compressed
my $bool = $content->is_compressed;
Check if content is gzip compressed.
=head2 is_dynamic
my $bool = $content->is_dynamic;
Check if content will be dynamically generated, which prevents L</"clone">
from working.
=head2 is_finished
my $bool = $content->is_finished;
Check if parser is finished.
=head2 is_limit_exceeded
my $bool = $content->is_limit_exceeded;
Check if buffer has exceeded L</"max_buffer_size">.
=head2 is_multipart
my $false = $content->is_multipart;
False.
=head2 is_parsing_body
my $bool = $content->is_parsing_body;
Check if body parsing started yet.
=head2 leftovers
my $bytes = $content->leftovers;
Get leftover data from content parser.
=head2 parse
$content
= $content->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
Parse content chunk.
=head2 parse_body
$content = $content->parse_body('Hi!');
Parse body chunk and skip headers.
=head2 progress
my $size = $content->progress;
Size of content already received from message in bytes.
=head2 write
$content = $content->write($bytes);
$content = $content->write($bytes => sub {...});
Write dynamic content non-blocking, the optional drain callback will be
invoked once all data has been written.
=head2 write_chunk
$content = $content->write_chunk($bytes);
$content = $content->write_chunk($bytes => sub {...});
Write dynamic content non-blocking with C<chunked> transfer encoding, the
optional drain callback will be invoked once all data has been written.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_CONTENT
$fatpacked{"Mojo/Content/MultiPart.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_CONTENT_MULTIPART';
package Mojo::Content::MultiPart;
use Mojo::Base 'Mojo::Content';
use Mojo::Util 'b64_encode';
has parts => sub { [] };
sub body_contains {
my ($self, $chunk) = @_;
for my $part (@{$self->parts}) {
return 1 if index($part->build_headers, $chunk) >= 0;
return 1 if $part->body_contains($chunk);
}
return undef;
}
sub body_size {
my $self = shift;
# Check for existing Content-Lenght header
my $content_len = $self->headers->content_length;
return $content_len if $content_len;
# Calculate length of whole body
my $boundary_len = length($self->build_boundary) + 6;
my $len = $boundary_len - 2;
$len += $_->header_size + $_->body_size + $boundary_len for @{$self->parts};
return $len;
}
sub build_boundary {
my $self = shift;
# Check for existing boundary
if (defined(my $boundary = $self->boundary)) { return $boundary }
# Generate and check boundary
my $boundary;
my $size = 1;
while (1) {
$boundary = b64_encode join('', map chr(rand 256), 1 .. $size++ * 3);
$boundary =~ s/\W/X/g;
last unless $self->body_contains($boundary);
}
# Add boundary to Content-Type header
my $headers = $self->headers;
($headers->content_type // '') =~ m!^(.*multipart/[^;]+)(.*)$!;
my $before = $1 || 'multipart/mixed';
my $after = $2 || '';
$headers->content_type("$before; boundary=$boundary$after");
return $boundary;
}
sub clone {
my $self = shift;
return undef unless my $clone = $self->SUPER::clone();
return $clone->parts($self->parts);
}
sub get_body_chunk {
my ($self, $offset) = @_;
# Body generator
return $self->generate_body_chunk($offset) if $self->{dynamic};
# First boundary
my $boundary = $self->build_boundary;
my $boundary_len = length($boundary) + 6;
my $len = $boundary_len - 2;
return substr "--$boundary\x0d\x0a", $offset if $len > $offset;
# Prepare content part by part
my $parts = $self->parts;
for (my $i = 0; $i < @$parts; $i++) {
my $part = $parts->[$i];
# Headers
my $header_len = $part->header_size;
return $part->get_header_chunk($offset - $len)
if ($len + $header_len) > $offset;
$len += $header_len;
# Content
my $content_len = $part->body_size;
return $part->get_body_chunk($offset - $len)
if ($len + $content_len) > $offset;
$len += $content_len;
# Boundary
if (($len + $boundary_len) > $offset) {
# Last boundary
return substr "\x0d\x0a--$boundary--", $offset - $len
if $#{$parts} == $i;
# Middle boundary
return substr "\x0d\x0a--$boundary\x0d\x0a", $offset - $len;
}
$len += $boundary_len;
}
}
sub is_multipart {1}
sub new {
my $self = shift->SUPER::new(@_);
$self->on(read => \&_read);
return $self;
}
sub _parse_multipart_body {
my ($self, $boundary) = @_;
# Whole part in buffer
my $pos = index $self->{multipart}, "\x0d\x0a--$boundary";
if ($pos < 0) {
my $len = length($self->{multipart}) - (length($boundary) + 8);
return undef unless $len > 0;
# Store chunk
my $chunk = substr $self->{multipart}, 0, $len, '';
$self->parts->[-1] = $self->parts->[-1]->parse($chunk);
return undef;
}
# Store chunk
my $chunk = substr $self->{multipart}, 0, $pos, '';
$self->parts->[-1] = $self->parts->[-1]->parse($chunk);
return !!($self->{multi_state} = 'multipart_boundary');
}
sub _parse_multipart_boundary {
my ($self, $boundary) = @_;
# Boundary begins
if ((index $self->{multipart}, "\x0d\x0a--$boundary\x0d\x0a") == 0) {
substr $self->{multipart}, 0, length($boundary) + 6, '';
# New part
my $part = Mojo::Content::Single->new(relaxed => 1);
$self->emit(part => $part);
push @{$self->parts}, $part;
return !!($self->{multi_state} = 'multipart_body');
}
# Boundary ends
my $end = "\x0d\x0a--$boundary--";
if ((index $self->{multipart}, $end) == 0) {
substr $self->{multipart}, 0, length $end, '';
$self->{multi_state} = 'finished';
}
return undef;
}
sub _parse_multipart_preamble {
my ($self, $boundary) = @_;
# No boundary yet
return undef if (my $pos = index $self->{multipart}, "--$boundary") < 0;
# Replace preamble with carriage return and line feed
substr $self->{multipart}, 0, $pos, "\x0d\x0a";
# Parse boundary
return !!($self->{multi_state} = 'multipart_boundary');
}
sub _read {
my ($self, $chunk) = @_;
$self->{multipart} .= $chunk;
my $boundary = $self->boundary;
until (($self->{multi_state} //= 'multipart_preamble') eq 'finished') {
# Preamble
if ($self->{multi_state} eq 'multipart_preamble') {
last unless $self->_parse_multipart_preamble($boundary);
}
# Boundary
elsif ($self->{multi_state} eq 'multipart_boundary') {
last unless $self->_parse_multipart_boundary($boundary);
}
# Body
elsif ($self->{multi_state} eq 'multipart_body') {
last unless $self->_parse_multipart_body($boundary);
}
}
# Check buffer size
@$self{qw(state limit)} = ('finished', 1)
if length($self->{multipart} // '') > $self->max_buffer_size;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content::MultiPart - HTTP multipart content
=head1 SYNOPSIS
use Mojo::Content::MultiPart;
my $multi = Mojo::Content::MultiPart->new;
$multi->parse('Content-Type: multipart/mixed; boundary=---foobar');
my $single = $multi->parts->[4];
=head1 DESCRIPTION
L<Mojo::Content::MultiPart> is a container for HTTP multipart content based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230>,
L<RFC 7231|http://tools.ietf.org/html/rfc7231> and
L<RFC 2388|http://tools.ietf.org/html/rfc2388>.
=head1 EVENTS
L<Mojo::Content::Multipart> inherits all events from L<Mojo::Content> and can
emit the following new ones.
=head2 part
$multi->on(part => sub {
my ($multi, $single) = @_;
...
});
Emitted when a new L<Mojo::Content::Single> part starts.
$multi->on(part => sub {
my ($multi, $single) = @_;
return unless $single->headers->content_disposition =~ /name="([^"]+)"/;
say "Field: $1";
});
=head1 ATTRIBUTES
L<Mojo::Content::MultiPart> inherits all attributes from L<Mojo::Content> and
implements the following new ones.
=head2 parts
my $parts = $multi->parts;
$multi = $multi->parts([]);
Content parts embedded in this multipart content, usually
L<Mojo::Content::Single> objects.
=head1 METHODS
L<Mojo::Content::MultiPart> inherits all methods from L<Mojo::Content> and
implements the following new ones.
=head2 body_contains
my $bool = $multi->body_contains('foobarbaz');
Check if content parts contain a specific string.
=head2 body_size
my $size = $multi->body_size;
Content size in bytes.
=head2 build_boundary
my $boundary = $multi->build_boundary;
Generate a suitable boundary for content and add it to C<Content-Type> header.
=head2 clone
my $clone = $multi->clone;
Clone content if possible, otherwise return C<undef>.
=head2 get_body_chunk
my $bytes = $multi->get_body_chunk(0);
Get a chunk of content starting from a specific position.
=head2 is_multipart
my $true = $multi->is_multipart;
True.
=head2 new
my $multi = Mojo::Content::MultiPart->new;
Construct a new L<Mojo::Content::MultiPart> object and subscribe to L</"read">
event with default content parser.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_CONTENT_MULTIPART
$fatpacked{"Mojo/Content/Single.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_CONTENT_SINGLE';
package Mojo::Content::Single;
use Mojo::Base 'Mojo::Content';
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
has asset => sub { Mojo::Asset::Memory->new(auto_upgrade => 1) };
has auto_upgrade => 1;
sub body_contains { shift->asset->contains(shift) >= 0 }
sub body_size {
my $self = shift;
return ($self->headers->content_length || 0) if $self->{dynamic};
return $self->asset->size;
}
sub clone {
my $self = shift;
return undef unless my $clone = $self->SUPER::clone();
return $clone->asset($self->asset);
}
sub get_body_chunk {
my ($self, $offset) = @_;
return $self->generate_body_chunk($offset) if $self->{dynamic};
return $self->asset->get_chunk($offset);
}
sub new {
my $self = shift->SUPER::new(@_);
$self->{read}
= $self->on(read => sub { $_[0]->asset($_[0]->asset->add_chunk($_[1])) });
return $self;
}
sub parse {
my $self = shift;
# Parse headers
$self->_parse_until_body(@_);
# Parse body
return $self->SUPER::parse
unless $self->auto_upgrade && defined $self->boundary;
# Content needs to be upgraded to multipart
$self->unsubscribe(read => $self->{read});
my $multi = Mojo::Content::MultiPart->new(%$self);
$self->emit(upgrade => $multi);
return $multi->parse;
}
1;
=encoding utf8
=head1 NAME
Mojo::Content::Single - HTTP content
=head1 SYNOPSIS
use Mojo::Content::Single;
my $single = Mojo::Content::Single->new;
$single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
say $single->headers->content_length;
=head1 DESCRIPTION
L<Mojo::Content::Single> is a container for HTTP content based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Content::Single> inherits all events from L<Mojo::Content> and can
emit the following new ones.
=head2 upgrade
$single->on(upgrade => sub {
my ($single, $multi) = @_;
...
});
Emitted when content gets upgraded to a L<Mojo::Content::MultiPart> object.
$single->on(upgrade => sub {
my ($single, $multi) = @_;
return unless $multi->headers->content_type =~ /multipart\/([^;]+)/i;
say "Multipart: $1";
});
=head1 ATTRIBUTES
L<Mojo::Content::Single> inherits all attributes from L<Mojo::Content> and
implements the following new ones.
=head2 asset
my $asset = $single->asset;
$single = $single->asset(Mojo::Asset::Memory->new);
The actual content, defaults to a L<Mojo::Asset::Memory> object with
C<auto_upgrade> enabled.
=head2 auto_upgrade
my $bool = $single->auto_upgrade;
$single = $single->auto_upgrade($bool);
Try to detect multipart content and automatically upgrade to a
L<Mojo::Content::MultiPart> object, defaults to a true value.
=head1 METHODS
L<Mojo::Content::Single> inherits all methods from L<Mojo::Content> and
implements the following new ones.
=head2 body_contains
my $bool = $single->body_contains('1234567');
Check if content contains a specific string.
=head2 body_size
my $size = $single->body_size;
Content size in bytes.
=head2 clone
my $clone = $single->clone;
Clone content if possible, otherwise return C<undef>.
=head2 get_body_chunk
my $bytes = $single->get_body_chunk(0);
Get a chunk of content starting from a specific position.
=head2 new
my $single = Mojo::Content::Single->new;
Construct a new L<Mojo::Content::Single> object and subscribe to L</"read">
event with default content parser.
=head2 parse
$single = $single->parse("Content-Length: 12\x0d\x0a\x0d\x0aHello World!");
my $multi
= $single->parse("Content-Type: multipart/form-data\x0d\x0a\x0d\x0a");
Parse content chunk and upgrade to L<Mojo::Content::MultiPart> object if
necessary.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_CONTENT_SINGLE
$fatpacked{"Mojo/Cookie.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_COOKIE';
package Mojo::Cookie;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Carp 'croak';
has [qw(name value)];
sub parse { croak 'Method "parse" not implemented by subclass' }
sub to_string { croak 'Method "to_string" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Cookie - HTTP cookie base class
=head1 SYNOPSIS
package Mojo::Cookie::MyCookie;
use Mojo::Base 'Mojo::Cookie';
sub parse {...}
sub to_string {...}
=head1 DESCRIPTION
L<Mojo::Cookie> is an abstract base class for HTTP cookies based on
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::Cookie> implements the following attributes.
=head2 name
my $name = $cookie->name;
$cookie = $cookie->name('foo');
Cookie name.
=head2 value
my $value = $cookie->value;
$cookie = $cookie->value('/test');
Cookie value.
=head1 METHODS
L<Mojo::Cookie> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 parse
my $cookies = $cookie->parse($str);
Parse cookies. Meant to be overloaded in a subclass.
=head2 to_string
my $str = $cookie->to_string;
Render cookie. Meant to be overloaded in a subclass.
=head1 OPERATORS
L<Mojo::Cookie> overloads the following operators.
=head2 bool
my $bool = !!$cookie;
Always true.
=head2 stringify
my $str = "$cookie";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_COOKIE
$fatpacked{"Mojo/Cookie/Request.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_COOKIE_REQUEST';
package Mojo::Cookie::Request;
use Mojo::Base 'Mojo::Cookie';
use Mojo::Util qw(quote split_header);
sub parse {
my ($self, $str) = @_;
my @cookies;
my @pairs = map {@$_} @{split_header($str // '')};
while (@pairs) {
my ($name, $value) = (shift @pairs, shift @pairs);
next if $name =~ /^\$/;
push @cookies, $self->new(name => $name, value => $value // '');
}
return \@cookies;
}
sub to_string {
my $self = shift;
return '' unless length(my $name = $self->name // '');
my $value = $self->value // '';
return join '=', $name, $value =~ /[,;" ]/ ? quote($value) : $value;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cookie::Request - HTTP request cookie
=head1 SYNOPSIS
use Mojo::Cookie::Request;
my $cookie = Mojo::Cookie::Request->new;
$cookie->name('foo');
$cookie->value('bar');
say "$cookie";
=head1 DESCRIPTION
L<Mojo::Cookie::Request> is a container for HTTP request cookies based on
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::Cookie::Request> inherits all attributes from L<Mojo::Cookie>.
=head1 METHODS
L<Mojo::Cookie::Request> inherits all methods from L<Mojo::Cookie> and
implements the following new ones.
=head2 parse
my $cookies = Mojo::Cookie::Request->parse('f=b; g=a');
Parse cookies.
=head2 to_string
my $str = $cookie->to_string;
Render cookie.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_COOKIE_REQUEST
$fatpacked{"Mojo/Cookie/Response.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_COOKIE_RESPONSE';
package Mojo::Cookie::Response;
use Mojo::Base 'Mojo::Cookie';
use Mojo::Date;
use Mojo::Util qw(quote split_header);
has [qw(domain httponly max_age origin path secure)];
sub expires {
my $self = shift;
# Upgrade
my $e = $self->{expires};
return $self->{expires} = defined $e && !ref $e ? Mojo::Date->new($e) : $e
unless @_;
$self->{expires} = shift;
return $self;
}
sub parse {
my ($self, $str) = @_;
my @cookies;
my $tree = split_header($str // '');
while (my $pairs = shift @$tree) {
my $i = 0;
while (@$pairs) {
my ($name, $value) = (shift @$pairs, shift @$pairs);
# "expires" is a special case, thank you Netscape...
if ($name =~ /^expires$/i) {
push @$pairs, @{shift @$tree // []};
my $len = ($pairs->[0] // '') =~ /-/ ? 6 : 10;
$value .= join ' ', ',', grep {defined} splice @$pairs, 0, $len;
}
# This will only run once
push @cookies, $self->new(name => $name, value => $value // '') and next
unless $i++;
# Attributes (Netscape and RFC 6265)
next unless $name =~ /^(expires|domain|path|secure|max-age|httponly)$/i;
my $attr = lc $1;
$attr = 'max_age' if $attr eq 'max-age';
$cookies[-1]
->$attr($attr eq 'secure' || $attr eq 'httponly' ? 1 : $value);
}
}
return \@cookies;
}
sub to_string {
my $self = shift;
# Name and value (Netscape)
return '' unless length(my $name = $self->name // '');
my $value = $self->value // '';
my $cookie = join '=', $name, $value =~ /[,;" ]/ ? quote($value) : $value;
# "expires" (Netscape)
if (defined(my $e = $self->expires)) { $cookie .= "; expires=$e" }
# "domain" (Netscape)
if (my $domain = $self->domain) { $cookie .= "; domain=$domain" }
# "path" (Netscape)
if (my $path = $self->path) { $cookie .= "; path=$path" }
# "secure" (Netscape)
$cookie .= "; secure" if $self->secure;
# "Max-Age" (RFC 6265)
if (defined(my $max = $self->max_age)) { $cookie .= "; Max-Age=$max" }
# "HttpOnly" (RFC 6265)
$cookie .= "; HttpOnly" if $self->httponly;
return $cookie;
}
1;
=encoding utf8
=head1 NAME
Mojo::Cookie::Response - HTTP response cookie
=head1 SYNOPSIS
use Mojo::Cookie::Response;
my $cookie = Mojo::Cookie::Response->new;
$cookie->name('foo');
$cookie->value('bar');
say "$cookie";
=head1 DESCRIPTION
L<Mojo::Cookie::Response> is a container for HTTP response cookies based on
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::Cookie::Response> inherits all attributes from L<Mojo::Cookie> and
implements the following new ones.
=head2 domain
my $domain = $cookie->domain;
$cookie = $cookie->domain('localhost');
Cookie domain.
=head2 httponly
my $bool = $cookie->httponly;
$cookie = $cookie->httponly($bool);
HttpOnly flag, which can prevent client-side scripts from accessing this
cookie.
=head2 max_age
my $max_age = $cookie->max_age;
$cookie = $cookie->max_age(60);
Max age for cookie.
=head2 origin
my $origin = $cookie->origin;
$cookie = $cookie->origin('mojolicio.us');
Origin of the cookie.
=head2 path
my $path = $cookie->path;
$cookie = $cookie->path('/test');
Cookie path.
=head2 secure
my $bool = $cookie->secure;
$cookie = $cookie->secure($bool);
Secure flag, which instructs browsers to only send this cookie over HTTPS
connections.
=head1 METHODS
L<Mojo::Cookie::Response> inherits all methods from L<Mojo::Cookie> and
implements the following new ones.
=head2 expires
my $expires = $cookie->expires;
$cookie = $cookie->expires(time + 60);
$cookie = $cookie->expires(Mojo::Date->new(time + 60));
Expiration for cookie.
=head2 parse
my $cookies = Mojo::Cookie::Response->parse('f=b; path=/');
Parse cookies.
=head2 to_string
my $str = $cookie->to_string;
Render cookie.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_COOKIE_RESPONSE
$fatpacked{"Mojo/DOM.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_DOM';
package Mojo::DOM;
use Mojo::Base -strict;
use overload
'@{}' => sub { shift->contents },
'%{}' => sub { shift->attr },
bool => sub {1},
'""' => sub { shift->to_string },
fallback => 1;
# "Fry: This snow is beautiful. I'm glad global warming never happened.
# Leela: Actually, it did. But thank God nuclear winter canceled it out."
use Carp 'croak';
use List::Util 'first';
use Mojo::Collection;
use Mojo::DOM::CSS;
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, @_) }
sub ancestors { _select($_[0]->_collect($_[0]->_ancestors), $_[1]) }
sub append { shift->_add(1, @_) }
sub append_content { shift->_content(1, 0, @_) }
sub at {
my $self = shift;
return undef unless my $result = $self->_css->select_one(@_);
return _build($self, $result, $self->xml);
}
sub attr {
my $self = shift;
# Hash
my $tree = $self->tree;
my $attrs = $tree->[0] ne 'tag' ? {} : $tree->[2];
return $attrs unless @_;
# Get
return $attrs->{$_[0]} // '' unless @_ > 1 || ref $_[0];
# Set
my $values = ref $_[0] ? $_[0] : {@_};
@$attrs{keys %$values} = values %$values;
return $self;
}
sub children {
my $self = shift;
return _select(
$self->_collect(grep { $_->[0] eq 'tag' } _nodes($self->tree)), @_);
}
sub content {
my $self = shift;
my $node = $self->node;
if ($node eq 'root' || $node 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);
}
return $self->tree->[1] unless @_;
$self->tree->[1] = shift;
return $self;
}
sub contents { $_[0]->_collect(_nodes($_[0]->tree)) }
sub find { $_[0]->_collect(@{$_[0]->_css->select($_[1])}) }
sub match { $_[0]->_css->match($_[1]) ? $_[0] : undef }
sub namespace {
my $self = shift;
return '' if (my $tree = $self->tree)->[0] ne 'tag';
# Extract namespace prefix and search parents
my $ns = $tree->[1] =~ /^(.*?):/ ? "xmlns:$1" : undef;
for my $n ($tree, $self->_ancestors) {
# Namespace for prefix
my $attrs = $n->[2];
if ($ns) { /^\Q$ns\E$/ and return $attrs->{$_} for keys %$attrs }
# Namespace attribute
elsif (defined $attrs->{xmlns}) { return $attrs->{xmlns} }
}
return '';
}
sub new {
my $class = shift;
my $self = bless \Mojo::DOM::HTML->new, ref $class || $class;
return @_ ? $self->parse(@_) : $self;
}
sub next { _maybe($_[0], $_[0]->_siblings(1)->[1]) }
sub next_sibling { _maybe($_[0], $_[0]->_siblings->[1]) }
sub node { shift->tree->[0] }
sub parent {
my $self = shift;
return undef if $self->tree->[0] eq 'root';
return _build($self, $self->_parent, $self->xml);
}
sub parse { shift->_delegate(parse => shift) }
sub prepend { shift->_add(0, @_) }
sub prepend_content { shift->_content(0, 0, @_) }
sub previous { _maybe($_[0], $_[0]->_siblings(1)->[0]) }
sub previous_sibling { _maybe($_[0], $_[0]->_siblings->[0]) }
sub remove { shift->replace('') }
sub replace {
my ($self, $new) = @_;
return $self->parse($new) if (my $tree = $self->tree)->[0] eq 'root';
return $self->_replace($self->_parent, $tree, $self->_parse("$new"));
}
sub root {
my $self = shift;
return $self unless my $tree = $self->_ancestors(1);
return _build($self, $tree, $self->xml);
}
sub siblings { _select($_[0]->_collect(@{_siblings($_[0], 1, 1)}), $_[1]) }
sub strip {
my $self = shift;
return $self if (my $tree = $self->tree)->[0] ne 'tag';
return $self->_replace($tree->[3], $tree, ['root', _nodes($tree)]);
}
sub tap { shift->Mojo::Base::tap(@_) }
sub text { shift->_all_text(0, @_) }
sub to_string { shift->_delegate('render') }
sub tree { shift->_delegate(tree => @_) }
sub type {
my ($self, $type) = @_;
return '' if (my $tree = $self->tree)->[0] ne 'tag';
return $tree->[1] unless $type;
$tree->[1] = $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, @_) }
sub xml { shift->_delegate(xml => @_) }
sub _add {
my ($self, $offset, $new) = @_;
return $self if (my $tree = $self->tree)->[0] eq 'root';
my $parent = $self->_parent;
splice @$parent, _offset($parent, $tree) + $offset, 0,
_link($self->_parse("$new"), $parent);
return $self;
}
sub _all {
map { $_->[0] eq 'tag' ? ($_, _all(_nodes($_))) : ($_) } @_;
}
sub _all_text {
my ($self, $recurse, $trim) = @_;
# Detect "pre" tag
my $tree = $self->tree;
if (!defined $trim || $trim) {
$trim = 1;
$_->[1] eq 'pre' and $trim = 0 for $self->_ancestors, $tree;
}
return _text([_nodes($tree)], $recurse, $trim);
}
sub _ancestors {
my ($self, $root) = @_;
return if $self->node eq 'root';
my @ancestors;
my $tree = $self->_parent;
do { push @ancestors, $tree }
while ($tree->[0] eq 'tag') && ($tree = $tree->[3]);
return $root ? $ancestors[-1] : @ancestors[0 .. $#ancestors - 1];
}
sub _build { shift->new->tree(shift)->xml(shift) }
sub _collect {
my $self = shift;
my $xml = $self->xml;
return Mojo::Collection->new(map { _build($self, $_, $xml) } @_);
}
sub _content {
my ($self, $start, $offset, $new) = @_;
my $tree = $self->tree;
unless ($tree->[0] eq 'root' || $tree->[0] eq 'tag') {
my $old = $self->content;
return $self->content($start ? "$old$new" : "$new$old");
}
$start = $start ? ($#$tree + 1) : _start($tree);
$offset = $offset ? $#$tree : 0;
splice @$tree, $start, $offset, _link($self->_parse("$new"), $tree);
return $self;
}
sub _css { Mojo::DOM::CSS->new(tree => shift->tree) }
sub _delegate {
my ($self, $method) = (shift, shift);
return $$self->$method unless @_;
$$self->$method(@_);
return $self;
}
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];
}
return @new;
}
sub _maybe { $_[1] ? _build($_[0], $_[1], $_[0]->xml) : undef }
sub _nodes {
return unless my $tree = shift;
return @$tree[_start($tree) .. $#$tree];
}
sub _offset {
my ($parent, $child) = @_;
my $i = _start($parent);
$_ eq $child ? last : $i++ for @$parent[$i .. $#$parent];
return $i;
}
sub _parent { $_[0]->tree->[$_[0]->node eq 'tag' ? 3 : 2] }
sub _parse { Mojo::DOM::HTML->new(xml => shift->xml)->parse(shift)->tree }
sub _replace {
my ($self, $parent, $tree, $new) = @_;
splice @$parent, _offset($parent, $tree), 1, _link($new, $parent);
return $self->parent;
}
sub _select {
my ($collection, $selector) = @_;
return $collection unless $selector;
return $collection->new(grep { $_->match($selector) } @$collection);
}
sub _siblings {
my ($self, $tags, $all) = @_;
return [] unless my $parent = $self->parent;
my $tree = $self->tree;
my (@before, @after, $match);
for my $node (_nodes($parent->tree)) {
++$match and next if !$match && $node eq $tree;
next if $tags && $node->[0] ne 'tag';
$match ? push @after, $node : push @before, $node;
}
return $all ? [@before, @after] : [$before[-1], $after[0]];
}
sub _start { $_[0][0] eq 'root' ? 1 : 4 }
sub _text {
my ($nodes, $recurse, $trim) = @_;
# Merge successive text nodes
my $i = 0;
while (my $next = $nodes->[$i + 1]) {
++$i and next unless $nodes->[$i][0] eq 'text' && $next->[0] eq 'text';
splice @$nodes, $i, 2, ['text', $nodes->[$i][1] . $next->[1]];
}
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);
}
# Text
elsif ($type eq 'text') { $content = $trim ? squish($n->[1]) : $n->[1] }
# CDATA or raw text
elsif ($type eq 'cdata' || $type eq 'raw') { $content = $n->[1] }
# Add leading whitespace if punctuation allows it
$content = " $content" if $text =~ /\S\z/ && $content =~ /^[^.!?,;:\s]+/;
# Trim whitespace blocks
$text .= $content if $content =~ /\S+/ || !$trim;
}
return $text;
}
sub _wrap {
my ($self, $content, $new) = @_;
$content = 1 if (my $tree = $self->tree)->[0] eq 'root';
$content = 0 if $tree->[0] ne 'root' && $tree->[0] ne 'tag';
# Find innermost tag
my $current;
my $first = $new = $self->_parse("$new");
$current = $first while $first = first { $_->[0] eq 'tag' } _nodes($first);
return $self unless $current;
# Wrap content
if ($content) {
push @$current, _link(['root', _nodes($tree)], $current);
splice @$tree, _start($tree), $#$tree, _link($new, $tree);
return $self;
}
# Wrap element
$self->_replace($self->_parent, $tree, $new);
push @$current, _link(['root', $tree], $current);
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::DOM - Minimalistic HTML/XML DOM parser with CSS selectors
=head1 SYNOPSIS
use Mojo::DOM;
# Parse
my $dom = Mojo::DOM->new('<div><p id="a">Test</p><p id="b">123</p></div>');
# Find
say $dom->at('#b')->text;
say $dom->find('p')->map('text')->join("\n");
say $dom->find('[id]')->map(attr => 'id')->join("\n");
# Iterate
$dom->find('p[id]')->reverse->each(sub { say $_->{id} });
# Loop
for my $e ($dom->find('p[id]')->each) {
say $e->{id}, ':', $e->text;
}
# Modify
$dom->find('div p')->last->append('<p id="c">456</p>');
$dom->find(':not(p)')->map('strip');
# Render
say "$dom";
=head1 DESCRIPTION
L<Mojo::DOM> is a minimalistic and relaxed HTML/XML DOM parser with CSS
selector support. It will even try to interpret broken XML, so you should not
use it for validation.
=head1 CASE SENSITIVITY
L<Mojo::DOM> defaults to HTML semantics, that means all tags and attributes
are lowercased and selectors need to be lowercase as well.
my $dom = Mojo::DOM->new('<P ID="greeting">Hi!</P>');
say $dom->at('p')->text;
If XML processing instructions are found, the parser will automatically switch
into XML mode and everything becomes case sensitive.
my $dom = Mojo::DOM->new('<?xml version="1.0"?><P ID="greeting">Hi!</P>');
say $dom->at('P')->text;
XML detection can also be disabled with the L</"xml"> method.
# Force XML semantics
$dom->xml(1);
# Force HTML semantics
$dom->xml(0);
=head1 METHODS
L<Mojo::DOM> implements the following methods.
=head2 all_contents
my $collection = $dom->all_contents;
Return a L<Mojo::Collection> object containing all nodes in DOM structure as
L<Mojo::DOM> objects.
# "<p><b>123</b></p>"
$dom->parse('<p><!-- Test --><b>123<!-- 456 --></b></p>')->all_contents
->grep(sub { $_->node eq 'comment' })->map('remove')->first;
=head2 all_text
my $trimmed = $dom->all_text;
my $untrimmed = $dom->all_text(0);
Extract all text content from DOM structure, smart whitespace trimming is
enabled by default.
# "foo bar baz"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text;
# "foo\nbarbaz\n"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->all_text(0);
=head2 ancestors
my $collection = $dom->ancestors;
my $collection = $dom->ancestors('div > p');
Find all ancestors of this node matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS/"SELECTORS"> are supported.
# List types of ancestor elements
say $dom->ancestors->map('type')->join("\n");
=head2 append
$dom = $dom->append('<p>I ♥ Mojolicious!</p>');
Append HTML/XML fragment to this node.
# "<div><h1>Test</h1><h2>123</h2></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')
->append('<h2>123</h2>')->root;
# "<p>Test 123</p>"
$dom->parse('<p>Test</p>')->at('p')->contents->first->append(' 123')->root;
=head2 append_content
$dom = $dom->append_content('<p>I ♥ Mojolicious!</p>');
Append HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to this
node's content.
# "<div><h1>Test123</h1></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')
->append_content('123')->root;
# "<!-- Test 123 --><br>"
$dom->parse('<!-- Test --><br>')
->contents->first->append_content('123 ')->root;
# "<p>Test<i>123</i></p>"
$dom->parse('<p>Test</p>')->at('p')->append_content('<i>123</i>')->root;
=head2 at
my $result = $dom->at('html title');
Find first element in DOM structure matching the CSS selector and return it as
a L<Mojo::DOM> object or return C<undef> if none could be found. All selectors
from L<Mojo::DOM::CSS/"SELECTORS"> are supported.
# Find first element with "svg" namespace definition
my $namespace = $dom->at('[xmlns\:svg]')->{'xmlns:svg'};
=head2 attr
my $hash = $dom->attr;
my $foo = $dom->attr('foo');
$dom = $dom->attr({foo => 'bar'});
$dom = $dom->attr(foo => 'bar');
This element's attributes.
# List id attributes
say $dom->find('*')->map(attr => 'id')->compact->join("\n");
=head2 children
my $collection = $dom->children;
my $collection = $dom->children('div > p');
Find all children of this element matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS/"SELECTORS"> are supported.
# Show type of random child element
say $dom->children->shuffle->first->type;
=head2 content
my $str = $dom->content;
$dom = $dom->content('<p>I ♥ Mojolicious!</p>');
Return this node's content or replace it with HTML/XML fragment (for C<root>
and C<tag> nodes) or raw content.
# "<b>Test</b>"
$dom->parse('<div><b>Test</b></div>')->at('div')->content;
# "<div><h1>123</h1></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('123')->root;
# "<p><i>123</i></p>"
$dom->parse('<p>Test</p>')->at('p')->content('<i>123</i>')->root;
# "<div><h1></h1></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->content('')->root;
# " Test "
$dom->parse('<!-- Test --><br>')->contents->first->content;
# "<div><!-- 123 -->456</div>"
$dom->parse('<div><!-- Test -->456</div>')->at('div')
->contents->first->content(' 123 ')->root;
=head2 contents
my $collection = $dom->contents;
Return a L<Mojo::Collection> object containing the child nodes of this element
as L<Mojo::DOM> objects.
# "<p><b>123</b></p>"
$dom->parse('<p>Test<b>123</b></p>')->at('p')->contents->first->remove;
# "<!-- Test -->"
$dom->parse('<!-- Test --><b>123</b>')->contents->first;
=head2 find
my $collection = $dom->find('html title');
Find all elements in DOM structure matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS/"SELECTORS"> are supported.
# Find a specific element and extract information
my $id = $dom->find('div')->[23]{id};
# Extract information from multiple elements
my @headers = $dom->find('h1, h2, h3')->map('text')->each;
# Count all the different tags
my $hash = $dom->find('*')->reduce(sub { $a->{$b->type}++; $a }, {});
# Find elements with a class that contains dots
my @divs = $dom->find('div.foo\.bar')->each;
=head2 match
my $result = $dom->match('html title');
Match the CSS selector against this element and return it as a L<Mojo::DOM>
object or return C<undef> if it didn't match. All selectors from
L<Mojo::DOM::CSS/"SELECTORS"> are supported.
=head2 namespace
my $namespace = $dom->namespace;
Find this element's namespace.
# Find namespace for an element with namespace prefix
my $namespace = $dom->at('svg > svg\:circle')->namespace;
# Find namespace for an element that may or may not have a namespace prefix
my $namespace = $dom->at('svg > circle')->namespace;
=head2 new
my $dom = Mojo::DOM->new;
my $dom = Mojo::DOM->new('<foo bar="baz">I ♥ Mojolicious!</foo>');
Construct a new scalar-based L<Mojo::DOM> object and L</"parse"> HTML/XML
fragment if necessary.
=head2 next
my $sibling = $dom->next;
Return L<Mojo::DOM> object for next sibling element or C<undef> if there are
no more siblings.
# "<h2>123</h2>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h1')->next;
=head2 next_sibling
my $sibling = $dom->next_sibling;
Return L<Mojo::DOM> object for next sibling node or C<undef> if there are no
more siblings.
# "456"
$dom->parse('<p><b>123</b><!-- Test -->456</p>')->at('b')
->next_sibling->next_sibling;
=head2 node
my $type = $dom->node;
This node's type, usually C<cdata>, C<comment>, C<doctype>, C<pi>, C<raw>,
C<root>, C<tag> or C<text>.
=head2 parent
my $parent = $dom->parent;
Return L<Mojo::DOM> object for parent of this node or C<undef> if this node
has no parent.
=head2 parse
$dom = $dom->parse('<foo bar="baz">I ♥ Mojolicious!</foo>');
Parse HTML/XML fragment with L<Mojo::DOM::HTML>.
# Parse XML
my $dom = Mojo::DOM->new->xml(1)->parse($xml);
=head2 prepend
$dom = $dom->prepend('<p>I ♥ Mojolicious!</p>');
Prepend HTML/XML fragment to this node.
# "<div><h1>Test</h1><h2>123</h2></div>"
$dom->parse('<div><h2>123</h2></div>')->at('h2')
->prepend('<h1>Test</h1>')->root;
# "<p>Test 123</p>"
$dom->parse('<p>123</p>')->at('p')->contents->first->prepend('Test ')->root;
=head2 prepend_content
$dom = $dom->prepend_content('<p>I ♥ Mojolicious!</p>');
Prepend HTML/XML fragment (for C<root> and C<tag> nodes) or raw content to
this node's content.
# "<div><h2>Test123</h2></div>"
$dom->parse('<div><h2>123</h2></div>')->at('h2')
->prepend_content('Test')->root;
# "<!-- Test 123 --><br>"
$dom->parse('<!-- 123 --><br>')
->contents->first->prepend_content(' Test')->root;
# "<p><i>123</i>Test</p>"
$dom->parse('<p>Test</p>')->at('p')->prepend_content('<i>123</i>')->root;
=head2 previous
my $sibling = $dom->previous;
Return L<Mojo::DOM> object for previous sibling element or C<undef> if there
are no more siblings.
# "<h1>Test</h1>"
$dom->parse('<div><h1>Test</h1><h2>123</h2></div>')->at('h2')->previous;
=head2 previous_sibling
my $sibling = $dom->previous_sibling;
Return L<Mojo::DOM> object for previous sibling node or C<undef> if there are
no more siblings.
# "123"
$dom->parse('<p>123<!-- Test --><b>456</b></p>')->at('b')
->previous_sibling->previous_sibling;
=head2 remove
my $parent = $dom->remove;
Remove this node and return L</"parent">.
# "<div></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->remove;
# "<p><b>456</b></p>"
$dom->parse('<p>123<b>456</b></p>')->at('p')->contents->first->remove->root;
=head2 replace
my $parent = $dom->replace('<div>I ♥ Mojolicious!</div>');
Replace this node with HTML/XML fragment and return L</"parent">.
# "<div><h2>123</h2></div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->replace('<h2>123</h2>');
# "<p><b>123</b></p>"
$dom->parse('<p>Test</p>')->at('p')
->contents->[0]->replace('<b>123</b>')->root;
=head2 root
my $root = $dom->root;
Return L<Mojo::DOM> object for root node.
=head2 siblings
my $collection = $dom->siblings;
my $collection = $dom->siblings('div > p');
Find all sibling elements of this node matching the CSS selector and return a
L<Mojo::Collection> object containing these elements as L<Mojo::DOM> objects.
All selectors from L<Mojo::DOM::CSS/"SELECTORS"> are supported.
# List types of sibling elements
say $dom->siblings->map('type')->join("\n");
=head2 strip
my $parent = $dom->strip;
Remove this element while preserving its content and return L</"parent">.
# "<div>Test</div>"
$dom->parse('<div><h1>Test</h1></div>')->at('h1')->strip;
=head2 tap
$dom = $dom->tap(sub {...});
Alias for L<Mojo::Base/"tap">.
=head2 text
my $trimmed = $dom->text;
my $untrimmed = $dom->text(0);
Extract text content from this element only (not including child elements),
smart whitespace trimming is enabled by default.
# "foo baz"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text;
# "foo\nbaz\n"
$dom->parse("<div>foo\n<p>bar</p>baz\n</div>")->at('div')->text(0);
=head2 to_string
my $str = $dom->to_string;
Render this node and its content to HTML/XML.
# "<b>Test</b>"
$dom->parse('<div><b>Test</b></div>')->at('div b')->to_string;
=head2 tree
my $tree = $dom->tree;
$dom = $dom->tree(['root']);
Document Object Model. Note that this structure should only be used very
carefully since it is very dynamic.
=head2 type
my $type = $dom->type;
$dom = $dom->type('div');
This element's type.
# List types of child elements
say $dom->children->map('type')->join("\n");
=head2 wrap
$dom = $dom->wrap('<div></div>');
Wrap HTML/XML fragment around this node, placing it as the last child of the
first innermost element.
# "<p>123<b>Test</b></p>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<p>123</p>')->root;
# "<div><p><b>Test</b></p>123</div>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<div><p></p>123</div>')->root;
# "<p><b>Test</b></p><p>123</p>"
$dom->parse('<b>Test</b>')->at('b')->wrap('<p></p><p>123</p>')->root;
# "<p><b>Test</b></p>"
$dom->parse('<p>Test</p>')->at('p')->contents->first->wrap('<b>')->root;
=head2 wrap_content
$dom = $dom->wrap_content('<div></div>');
Wrap HTML/XML fragment around this node's content, placing it as the last
children of the first innermost element.
# "<p><b>123Test</b></p>"
$dom->parse('<p>Test<p>')->at('p')->wrap_content('<b>123</b>')->root;
# "<p><b>Test</b></p><p>123</p>"
$dom->parse('<b>Test</b>')->wrap_content('<p></p><p>123</p>');
=head2 xml
my $bool = $dom->xml;
$dom = $dom->xml($bool);
Disable HTML semantics in parser and activate case sensitivity, defaults to
auto detection based on processing instructions.
=head1 OPERATORS
L<Mojo::DOM> overloads the following operators.
=head2 array
my @nodes = @$dom;
Alias for L</"contents">.
# "<!-- Test -->"
$dom->parse('<!-- Test --><b>123</b>')->[0];
=head2 bool
my $bool = !!$dom;
Always true.
=head2 hash
my %attrs = %$dom;
Alias for L</"attr">.
# "test"
$dom->parse('<div id="test">Test</div>')->at('div')->{id};
=head2 stringify
my $str = "$dom";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_DOM
$fatpacked{"Mojo/DOM/CSS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_DOM_CSS';
package Mojo::DOM::CSS;
use Mojo::Base -base;
has 'tree';
my $ESCAPE_RE = qr/\\[^0-9a-fA-F]|\\[0-9a-fA-F]{1,6}/;
my $ATTR_RE = qr/
\[
((?:$ESCAPE_RE|[\w\-])+) # Key
(?:
(\W)? # Operator
=
(?:"((?:\\"|[^"])*)"|([^\]]+)) # Value
)?
\]
/x;
my $PSEUDO_CLASS_RE = qr/(?::([\w\-]+)(?:\(((?:\([^)]+\)|[^)])+)\))?)/;
my $TOKEN_RE = qr/
(\s*,\s*)? # Separator
((?:[^[\\:\s,]|$ESCAPE_RE\s?)+)? # Element
($PSEUDO_CLASS_RE*)? # Pseudoclass
((?:$ATTR_RE)*)? # Attributes
(?:\s*([>+~]))? # Combinator
/x;
sub match {
my $tree = shift->tree;
return $tree->[0] ne 'tag' ? undef : _match(_compile(shift), $tree, $tree);
}
sub select { _select(0, shift->tree, _compile(@_)) }
sub select_one { _select(1, shift->tree, _compile(@_)) }
sub _ancestor {
my ($selectors, $current, $tree) = @_;
while ($current = $current->[3]) {
return undef if $current->[0] eq 'root' || $current eq $tree;
return 1 if _combinator($selectors, $current, $tree);
}
return undef;
}
sub _attr {
my ($key, $regex, $current) = @_;
# Ignore namespace prefix
my $attrs = $current->[2];
for my $name (keys %$attrs) {
next unless $name =~ /(?:^|:)\Q$key\E$/;
return 1 unless defined $attrs->{$name} && defined $regex;
return 1 if $attrs->{$name} =~ $regex;
}
return undef;
}
sub _combinator {
my ($selectors, $current, $tree) = @_;
# Selector
my @s = @$selectors;
return undef unless my $combinator = shift @s;
if ($combinator->[0] ne 'combinator') {
return undef unless _selector($combinator, $current);
return 1 unless $combinator = shift @s;
}
# ">" (parent only)
my $c = $combinator->[1];
return _parent(\@s, $current, $tree) ? 1 : undef if $c eq '>';
# "~" (preceding siblings)
return _sibling(\@s, $current, $tree, 0) ? 1 : undef if $c eq '~';
# "+" (immediately preceding siblings)
return _sibling(\@s, $current, $tree, 1) ? 1 : undef if $c eq '+';
# " " (ancestor)
return _ancestor(\@s, $current, $tree) ? 1 : undef;
}
sub _compile {
my $css = shift;
my $pattern = [[]];
while ($css =~ /$TOKEN_RE/go) {
my ($separator, $element, $pc, $attrs, $combinator)
= ($1, $2 // '', $3, $6, $11);
next unless $separator || $element || $pc || $attrs || $combinator;
# New selector
push @$pattern, [] if $separator;
my $part = $pattern->[-1];
# Empty combinator
push @$part, [combinator => ' ']
if $part->[-1] && $part->[-1][0] ne 'combinator';
# Tag
push @$part, ['element'];
my $selector = $part->[-1];
my $tag = '*';
$element =~ s/^((?:\\\.|\\\#|[^.#])+)// and $tag = _unescape($1);
push @$selector, ['tag', $tag];
# Class or ID
while ($element =~ /(?:([.#])((?:\\[.\#]|[^\#.])+))/g) {
my ($name, $op) = $1 eq '.' ? ('class', '~') : ('id', '');
push @$selector, ['attr', $name, _regex($op, $2)];
}
# Pseudo classes (":not" contains more selectors)
push @$selector, ['pc', "$1", $1 eq 'not' ? _compile($2) : $2]
while $pc =~ /$PSEUDO_CLASS_RE/go;
# Attributes
push @$selector, ['attr', _unescape($1), _regex($2 // '', $3 // $4)]
while $attrs =~ /$ATTR_RE/go;
# Combinator
push @$part, [combinator => $combinator] if $combinator;
}
return $pattern;
}
sub _equation {
my $equation = shift;
# "even"
return [2, 2] if $equation =~ /^even$/i;
# "odd"
return [2, 1] if $equation =~ /^odd$/i;
# Equation
my $num = [1, 1];
return $num if $equation !~ /(?:(-?(?:\d+)?)?(n))?\s*\+?\s*(-?\s*\d+)?\s*$/i;
$num->[0] = defined($1) && length($1) ? $1 : $2 ? 1 : 0;
$num->[0] = -1 if $num->[0] eq '-';
$num->[1] = $3 // 0;
$num->[1] =~ s/\s+//g;
return $num;
}
sub _match {
my ($pattern, $current, $tree) = @_;
_combinator([reverse @$_], $current, $tree) and return 1 for @$pattern;
return undef;
}
sub _parent {
my ($selectors, $current, $tree) = @_;
return undef unless my $parent = $current->[3];
return undef if $parent->[0] eq 'root';
return _combinator($selectors, $parent, $tree);
}
sub _pc {
my ($class, $args, $current) = @_;
# ":empty"
return !defined $current->[4] if $class eq 'empty';
# ":root"
return $current->[3] && $current->[3][0] eq 'root' if $class eq 'root';
# ":not"
return !_match($args, $current, $current) if $class eq 'not';
# ":checked"
return exists $current->[2]{checked} || exists $current->[2]{selected}
if $class eq 'checked';
# ":first-*" or ":last-*" (rewrite with equation)
($class, $args) = $1 ? ("nth-$class", 1) : ("nth-last-$class", '-n+1')
if $class =~ s/^(?:(first)|last)-//;
# ":nth-*"
if ($class =~ /^nth-/) {
my $type = $class =~ /of-type$/ ? $current->[1] : undef;
my @siblings = @{_siblings($current, $type)};
# ":nth-last-*"
@siblings = reverse @siblings if $class =~ /^nth-last/;
$args = _equation($args) unless ref $args;
for my $i (0 .. $#siblings) {
next if (my $result = $args->[0] * $i + $args->[1]) < 1;
last unless my $sibling = $siblings[$result - 1];
return 1 if $sibling eq $current;
}
}
# ":only-*"
elsif ($class =~ /^only-(?:child|(of-type))$/) {
$_ ne $current and return undef
for @{_siblings($current, $1 ? $current->[1] : undef)};
return 1;
}
return undef;
}
sub _regex {
my ($op, $value) = @_;
return undef unless defined $value;
$value = quotemeta _unescape($value);
# "~=" (word)
return qr/(?:^|.*\s+)$value(?:\s+.*|$)/ if $op eq '~';
# "*=" (contains)
return qr/$value/ if $op eq '*';
# "^=" (begins with)
return qr/^$value/ if $op eq '^';
# "$=" (ends with)
return qr/$value$/ if $op eq '$';
# Everything else
return qr/^$value$/;
}
sub _select {
my ($one, $tree, $pattern) = @_;
my @results;
my @queue = ($tree);
while (my $current = shift @queue) {
my $type = $current->[0];
# Tag
if ($type eq 'tag') {
unshift @queue, @$current[4 .. $#$current];
next unless _match($pattern, $current, $tree);
$one ? return $current : push @results, $current;
}
# Root
elsif ($type eq 'root') { unshift @queue, @$current[1 .. $#$current] }
}
return $one ? undef : \@results;
}
sub _selector {
my ($selector, $current) = @_;
for my $s (@$selector[1 .. $#$selector]) {
my $type = $s->[0];
# Tag (ignore namespace prefix)
if ($type eq 'tag') {
my $tag = $s->[1];
return undef unless $tag eq '*' || $current->[1] =~ /(?:^|:)\Q$tag\E$/;
}
# Attribute
elsif ($type eq 'attr') { return undef unless _attr(@$s[1, 2], $current) }
# Pseudo class
elsif ($type eq 'pc') {
return undef unless _pc(lc $s->[1], $s->[2], $current);
}
}
return 1;
}
sub _sibling {
my ($selectors, $current, $tree, $immediate) = @_;
my $found;
for my $sibling (@{_siblings($current)}) {
return $found if $sibling eq $current;
# "+" (immediately preceding sibling)
if ($immediate) { $found = _combinator($selectors, $sibling, $tree) }
# "~" (preceding sibling)
else { return 1 if _combinator($selectors, $sibling, $tree) }
}
return undef;
}
sub _siblings {
my ($current, $type) = @_;
my $parent = $current->[3];
my @siblings = grep { $_->[0] eq 'tag' }
@$parent[($parent->[0] eq 'root' ? 1 : 4) .. $#$parent];
@siblings = grep { $type eq $_->[1] } @siblings if defined $type;
return \@siblings;
}
sub _unescape {
my $value = shift;
# Remove escaped newlines
$value =~ s/\\\n//g;
# Unescape Unicode characters
$value =~ s/\\([0-9a-fA-F]{1,6})\s?/pack('U', hex $1)/ge;
# Remove backslash
$value =~ s/\\//g;
return $value;
}
1;
=encoding utf8
=head1 NAME
Mojo::DOM::CSS - CSS selector engine
=head1 SYNOPSIS
use Mojo::DOM::CSS;
# Select elements from DOM tree
my $css = Mojo::DOM::CSS->new(tree => $tree);
my $elements = $css->select('h1, h2, h3');
=head1 DESCRIPTION
L<Mojo::DOM::CSS> is the CSS selector engine used by L<Mojo::DOM> and based on
L<Selectors Level 3|http://www.w3.org/TR/css3-selectors/>.
=head1 SELECTORS
All CSS selectors that make sense for a standalone parser are supported.
=head2 *
Any element.
my $all = $css->select('*');
=head2 E
An element of type C<E>.
my $title = $css->select('title');
=head2 E[foo]
An C<E> element with a C<foo> attribute.
my $links = $css->select('a[href]');
=head2 E[foo="bar"]
An C<E> element whose C<foo> attribute value is exactly equal to C<bar>.
my $fields = $css->select('input[name="foo"]');
=head2 E[foo~="bar"]
An C<E> element whose C<foo> attribute value is a list of
whitespace-separated values, one of which is exactly equal to C<bar>.
my $fields = $css->select('input[name~="foo"]');
=head2 E[foo^="bar"]
An C<E> element whose C<foo> attribute value begins exactly with the string
C<bar>.
my $fields = $css->select('input[name^="f"]');
=head2 E[foo$="bar"]
An C<E> element whose C<foo> attribute value ends exactly with the string
C<bar>.
my $fields = $css->select('input[name$="o"]');
=head2 E[foo*="bar"]
An C<E> element whose C<foo> attribute value contains the substring C<bar>.
my $fields = $css->select('input[name*="fo"]');
=head2 E:root
An C<E> element, root of the document.
my $root = $css->select(':root');
=head2 E:checked
A user interface element C<E> which is checked (for instance a radio-button or
checkbox).
my $input = $css->select(':checked');
=head2 E:empty
An C<E> element that has no children (including text nodes).
my $empty = $css->select(':empty');
=head2 E:nth-child(n)
An C<E> element, the C<n-th> child of its parent.
my $third = $css->select('div:nth-child(3)');
my $odd = $css->select('div:nth-child(odd)');
my $even = $css->select('div:nth-child(even)');
my $top3 = $css->select('div:nth-child(-n+3)');
=head2 E:nth-last-child(n)
An C<E> element, the C<n-th> child of its parent, counting from the last one.
my $third = $css->select('div:nth-last-child(3)');
my $odd = $css->select('div:nth-last-child(odd)');
my $even = $css->select('div:nth-last-child(even)');
my $bottom3 = $css->select('div:nth-last-child(-n+3)');
=head2 E:nth-of-type(n)
An C<E> element, the C<n-th> sibling of its type.
my $third = $css->select('div:nth-of-type(3)');
my $odd = $css->select('div:nth-of-type(odd)');
my $even = $css->select('div:nth-of-type(even)');
my $top3 = $css->select('div:nth-of-type(-n+3)');
=head2 E:nth-last-of-type(n)
An C<E> element, the C<n-th> sibling of its type, counting from the last one.
my $third = $css->select('div:nth-last-of-type(3)');
my $odd = $css->select('div:nth-last-of-type(odd)');
my $even = $css->select('div:nth-last-of-type(even)');
my $bottom3 = $css->select('div:nth-last-of-type(-n+3)');
=head2 E:first-child
An C<E> element, first child of its parent.
my $first = $css->select('div p:first-child');
=head2 E:last-child
An C<E> element, last child of its parent.
my $last = $css->select('div p:last-child');
=head2 E:first-of-type
An C<E> element, first sibling of its type.
my $first = $css->select('div p:first-of-type');
=head2 E:last-of-type
An C<E> element, last sibling of its type.
my $last = $css->select('div p:last-of-type');
=head2 E:only-child
An C<E> element, only child of its parent.
my $lonely = $css->select('div p:only-child');
=head2 E:only-of-type
An C<E> element, only sibling of its type.
my $lonely = $css->select('div p:only-of-type');
=head2 E.warning
An C<E> element whose class is "warning".
my $warning = $css->select('div.warning');
=head2 E#myid
An C<E> element with C<ID> equal to "myid".
my $foo = $css->select('div#foo');
=head2 E:not(s)
An C<E> element that does not match simple selector C<s>.
my $others = $css->select('div p:not(:first-child)');
=head2 E F
An C<F> element descendant of an C<E> element.
my $headlines = $css->select('div h1');
=head2 E E<gt> F
An C<F> element child of an C<E> element.
my $headlines = $css->select('html > body > div > h1');
=head2 E + F
An C<F> element immediately preceded by an C<E> element.
my $second = $css->select('h1 + h2');
=head2 E ~ F
An C<F> element preceded by an C<E> element.
my $second = $css->select('h1 ~ h2');
=head2 E, F, G
Elements of type C<E>, C<F> and C<G>.
my $headlines = $css->select('h1, h2, h3');
=head2 E[foo=bar][bar=baz]
An C<E> element whose attributes match all following attribute selectors.
my $links = $css->select('a[foo^=b][foo$=ar]');
=head1 ATTRIBUTES
L<Mojo::DOM::CSS> implements the following attributes.
=head2 tree
my $tree = $css->tree;
$css = $css->tree(['root']);
Document Object Model. Note that this structure should only be used very
carefully since it is very dynamic.
=head1 METHODS
L<Mojo::DOM::CSS> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 match
my $bool = $css->match('head > title');
Match CSS selector against first node in L</"tree">.
=head2 select
my $results = $css->select('head > title');
Run CSS selector against L</"tree">.
=head2 select_one
my $result = $css->select_one('head > title');
Run CSS selector against L</"tree"> and stop as soon as the first node
matched.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_DOM_CSS
$fatpacked{"Mojo/DOM/HTML.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_DOM_HTML';
package Mojo::DOM::HTML;
use Mojo::Base -base;
use Mojo::Util qw(html_unescape xml_escape);
use Scalar::Util 'weaken';
has 'xml';
has tree => sub { ['root'] };
my $ATTR_RE = qr/
([^<>=\s\/]+|\/) # Key
(?:
\s*=\s*
(?:
"([^"]*?)" # Quotation marks
|
'([^']*?)' # Apostrophes
|
([^>\s]*) # Unquoted
)
)?
\s*
/x;
my $TOKEN_RE = qr/
([^<]+)? # Text
(?:
<(?:
!(?:
DOCTYPE(
\s+\w+ # Doctype
(?:(?:\s+\w+)?(?:\s+(?:"[^"]*"|'[^']*'))+)? # External ID
(?:\s+\[.+?\])? # Int Subset
\s*)
|
--(.*?)--\s* # Comment
|
\[CDATA\[(.*?)\]\] # CDATA
)
|
\?(.*?)\? # Processing Instruction
|
(\s*[^<>\s]+ # Tag
\s*(?:(?:$ATTR_RE){0,32766})*+) # Attributes
)>
|
(<) # Runaway "<"
)??
/xis;
# HTML elements that only contain raw text
my %RAW = map { $_ => 1 } qw(script style);
# HTML elements that only contain raw text and entities
my %RCDATA = map { $_ => 1 } qw(title textarea);
# HTML elements with optional end tags
my %END = (body => 'head', optgroup => 'optgroup', option => 'option');
# HTML elements that break paragraphs
map { $END{$_} = 'p' } (
qw(address article aside blockquote dir div dl fieldset footer form h1 h2),
qw(h3 h4 h5 h6 header hr main menu nav ol p pre section table ul)
);
# HTML table elements with optional end tags
my %TABLE = map { $_ => 1 } qw(colgroup tbody td tfoot th thead tr);
# HTML elements with optional end tags and scoping rules
my %CLOSE
= (li => [{li => 1}, {ul => 1, ol => 1}], tr => [{tr => 1}, {table => 1}]);
$CLOSE{$_} = [\%TABLE, {table => 1}] for qw(colgroup tbody tfoot thead);
$CLOSE{$_} = [{dd => 1, dt => 1}, {dl => 1}] for qw(dd dt);
$CLOSE{$_} = [{rp => 1, rt => 1}, {ruby => 1}] for qw(rp rt);
$CLOSE{$_} = [{th => 1, td => 1}, {table => 1}] for qw(td th);
# HTML elements without end tags
my %EMPTY = map { $_ => 1 } (
qw(area base br col embed hr img input keygen link menuitem meta param),
qw(source track wbr)
);
# HTML elements categorized as phrasing content (and obsolete inline elements)
my @PHRASING = (
qw(a abbr area audio b bdi bdo br button canvas cite code data datalist),
qw(del dfn em embed i iframe img input ins kbd keygen label link map mark),
qw(math meta meter noscript object output picture progress q ruby s samp),
qw(script select small span strong sub sup svg template textarea time u),
qw(var video wbr)
);
my @OBSOLETE = qw(acronym applet basefont big font strike tt);
my %PHRASING = map { $_ => 1 } @OBSOLETE, @PHRASING;
# HTML elements that don't get their self-closing flag acknowledged
my %BLOCK = map { $_ => 1 } (
qw(a address applet article aside b big blockquote body button caption),
qw(center code col colgroup dd details dialog dir div dl dt em fieldset),
qw(figcaption figure font footer form frameset h1 h2 h3 h4 h5 h6 head),
qw(header hgroup html i iframe li listing main marquee menu nav nobr),
qw(noembed noframes noscript object ol optgroup option p plaintext pre rp),
qw(rt s script section select small strike strong style summary table),
qw(tbody td template textarea tfoot th thead title tr tt u ul xmp)
);
sub parse {
my ($self, $html) = @_;
my $xml = $self->xml;
my $current = my $tree = ['root'];
while ($html =~ m/\G$TOKEN_RE/gcso) {
my ($text, $doctype, $comment, $cdata, $pi, $tag, $runaway)
= ($1, $2, $3, $4, $5, $6, $11);
# Text (and runaway "<")
$text .= '<' if defined $runaway;
_node($current, 'text', html_unescape $text) if defined $text;
# Tag
if (defined $tag) {
# End
if ($tag =~ /^\s*\/\s*(.+)/) { _end($xml ? $1 : lc $1, $xml, \$current) }
# Start
elsif ($tag =~ m!([^\s/]+)([\s\S]*)!) {
my ($start, $attr) = ($xml ? $1 : lc $1, $2);
# Attributes
my (%attrs, $closing);
while ($attr =~ /$ATTR_RE/go) {
my ($key, $value) = ($xml ? $1 : lc $1, $2 // $3 // $4);
# Empty tag
++$closing and next if $key eq '/';
$attrs{$key} = defined $value ? html_unescape($value) : $value;
}
# "image" is an alias for "img"
$start = 'img' if !$xml && $start eq 'image';
_start($start, \%attrs, $xml, \$current);
# Element without end tag (self-closing)
_end($start, $xml, \$current)
if !$xml && $EMPTY{$start} || ($xml || !$BLOCK{$start}) && $closing;
# Raw text elements
next if $xml || !$RAW{$start} && !$RCDATA{$start};
next unless $html =~ m!\G(.*?)<\s*/\s*$start\s*>!gcsi;
_node($current, 'raw', $RCDATA{$start} ? html_unescape $1 : $1);
_end($start, 0, \$current);
}
}
# DOCTYPE
elsif (defined $doctype) { _node($current, 'doctype', $doctype) }
# Comment
elsif (defined $comment) { _node($current, 'comment', $comment) }
# CDATA
elsif (defined $cdata) { _node($current, 'cdata', $cdata) }
# Processing instruction (try to detect XML)
elsif (defined $pi) {
$self->xml($xml = 1) if !exists $self->{xml} && $pi =~ /xml/i;
_node($current, 'pi', $pi);
}
}
return $self->tree($tree);
}
sub render { _render($_[0]->tree, $_[0]->xml) }
sub _end {
my ($end, $xml, $current) = @_;
# Search stack for start tag
my $next = $$current;
do {
# Ignore useless end tag
return if $next->[0] eq 'root';
# Right tag
return $$current = $next->[3] if $next->[1] eq $end;
# Phrasing content can only cross phrasing content
return if !$xml && $PHRASING{$end} && !$PHRASING{$next->[1]};
} while $next = $next->[3];
}
sub _node {
my ($current, $type, $content) = @_;
push @$current, my $new = [$type, $content, $current];
weaken $new->[2];
}
sub _render {
my ($tree, $xml) = @_;
# Text (escaped)
my $type = $tree->[0];
return xml_escape $tree->[1] if $type eq 'text';
# Raw text
return $tree->[1] if $type eq 'raw';
# DOCTYPE
return '<!DOCTYPE' . $tree->[1] . '>' if $type eq 'doctype';
# Comment
return '<!--' . $tree->[1] . '-->' if $type eq 'comment';
# CDATA
return '<![CDATA[' . $tree->[1] . ']]>' if $type eq 'cdata';
# 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]}) {
# 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];
# Close tag
$result .= '>';
}
# Render whole tree
no warnings 'recursion';
$result .= _render($tree->[$_], $xml)
for ($type eq 'root' ? 1 : 4) .. $#$tree;
# End tag
$result .= '</' . $tree->[1] . '>' if $type eq 'tag';
return $result;
}
sub _start {
my ($start, $attrs, $xml, $current) = @_;
# Autoclose optional HTML elements
if (!$xml && $$current->[0] ne 'root') {
if (my $end = $END{$start}) { _end($end, 0, $current) }
elsif (my $close = $CLOSE{$start}) {
my ($allowed, $scope) = @$close;
# Close allowed parent elements in scope
my $parent = $$current;
while ($parent->[0] ne 'root' && !$scope->{$parent->[1]}) {
_end($parent->[1], 0, $current) if $allowed->{$parent->[1]};
$parent = $parent->[3];
}
}
}
# New tag
push @$$current, my $new = ['tag', $start, $attrs, $$current];
weaken $new->[3];
$$current = $new;
}
1;
=encoding utf8
=head1 NAME
Mojo::DOM::HTML - HTML/XML engine
=head1 SYNOPSIS
use Mojo::DOM::HTML;
# Turn HTML into DOM tree
my $html = Mojo::DOM::HTML->new;
$html->parse('<div><p id="a">Test</p><p id="b">123</p></div>');
my $tree = $html->tree;
=head1 DESCRIPTION
L<Mojo::DOM::HTML> is the HTML/XML engine used by L<Mojo::DOM> and based on
the L<HTML Living Standard|https://html.spec.whatwg.org> as well as the
L<Extensible Markup Language (XML) 1.0|http://www.w3.org/TR/xml/>.
=head1 ATTRIBUTES
L<Mojo::DOM::HTML> implements the following attributes.
=head2 tree
my $tree = $html->tree;
$html = $html->tree(['root']);
Document Object Model. Note that this structure should only be used very
carefully since it is very dynamic.
=head2 xml
my $bool = $html->xml;
$html = $html->xml($bool);
Disable HTML semantics in parser and activate case sensitivity, defaults to
auto detection based on processing instructions.
=head1 METHODS
L<Mojo::DOM::HTML> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 parse
$html = $html->parse('<foo bar="baz">I ♥ Mojolicious!</foo>');
Parse HTML/XML fragment.
=head2 render
my $str = $html->render;
Render DOM to HTML/XML.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_DOM_HTML
$fatpacked{"Mojo/Date.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_DATE';
package Mojo::Date;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Time::Local 1.2 'timegm';
has epoch => sub {time};
my $RFC3339_RE = qr/
^(\d+)-(\d+)-(\d+)\D+(\d+):(\d+):(\d+(?:\.\d+)?) # Date and time
(?:Z|([+-])(\d+):(\d+))?$ # Offset
/xi;
my @DAYS = qw(Sun Mon Tue Wed Thu Fri Sat);
my @MONTHS = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my %MONTHS;
@MONTHS{@MONTHS} = (0 .. 11);
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my ($self, $date) = @_;
# epoch (784111777)
return $self->epoch($date) if $date =~ /^\d+$|^\d+\.\d+$/;
# RFC 822/1123 (Sun, 06 Nov 1994 08:49:37 GMT)
my $offset = 0;
my ($day, $month, $year, $h, $m, $s);
if ($date =~ /^\w+\,\s+(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+GMT$/) {
($day, $month, $year, $h, $m, $s) = ($1, $MONTHS{$2}, $3, $4, $5, $6);
}
# RFC 3339 (1994-11-06T08:49:37Z)
elsif ($date =~ $RFC3339_RE) {
($year, $month, $day, $h, $m, $s) = ($1, $2 - 1, $3, $4, $5, $6);
$offset = (($8 * 3600) + ($9 * 60)) * ($7 eq '+' ? -1 : 1) if $7;
}
# RFC 850/1036 (Sunday, 06-Nov-94 08:49:37 GMT)
elsif ($date =~ /^\w+\,\s+(\d+)-(\w+)-(\d+)\s+(\d+):(\d+):(\d+)\s+GMT$/) {
($day, $month, $year, $h, $m, $s) = ($1, $MONTHS{$2}, $3, $4, $5, $6);
}
# ANSI C asctime() (Sun Nov 6 08:49:37 1994)
elsif ($date =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\d+)$/) {
($month, $day, $h, $m, $s, $year) = ($MONTHS{$1}, $2, $3, $4, $5, $6);
}
# Invalid
else { return $self->epoch(undef) }
# Prevent crash
my $epoch = eval { timegm $s, $m, $h, $day, $month, $year };
return $self->epoch(
(defined $epoch && ($epoch += $offset) >= 0) ? $epoch : undef);
}
sub to_datetime {
# RFC 3339 (1994-11-06T08:49:37Z)
my ($s, $m, $h, $day, $month, $year) = gmtime(my $epoch = shift->epoch);
my $str = sprintf '%04d-%02d-%02dT%02d:%02d:%02d', $year + 1900, $month + 1,
$day, $h, $m, $s;
return $str . ($epoch =~ /(\.\d+)$/ ? "$1Z" : 'Z');
}
sub to_string {
# RFC 7231 (Sun, 06 Nov 1994 08:49:37 GMT)
my ($s, $m, $h, $mday, $month, $year, $wday) = gmtime shift->epoch;
return sprintf '%s, %02d %s %04d %02d:%02d:%02d GMT', $DAYS[$wday], $mday,
$MONTHS[$month], $year + 1900, $h, $m, $s;
}
1;
=encoding utf8
=head1 NAME
Mojo::Date - HTTP date
=head1 SYNOPSIS
use Mojo::Date;
# Parse
my $date = Mojo::Date->new('Sun, 06 Nov 1994 08:49:37 GMT');
say $date->epoch;
# Build
my $date = Mojo::Date->new(time + 60);
say "$date";
=head1 DESCRIPTION
L<Mojo::Date> implements HTTP date and time functions based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230>,
L<RFC 7231|http://tools.ietf.org/html/rfc7231> and
L<RFC 3339|http://tools.ietf.org/html/rfc3339>.
=head1 ATTRIBUTES
L<Mojo::Date> implements the following attributes.
=head2 epoch
my $epoch = $date->epoch;
$date = $date->epoch(784111777);
Epoch seconds, defaults to the current time.
=head1 METHODS
L<Mojo::Date> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 new
my $date = Mojo::Date->new;
my $date = Mojo::Date->new('Sun Nov 6 08:49:37 1994');
Construct a new L<Mojo::Date> object and L</"parse"> date if necessary.
=head2 parse
$date = $date->parse('Sun Nov 6 08:49:37 1994');
Parse date.
# Epoch
say Mojo::Date->new('784111777')->epoch;
say Mojo::Date->new('784111777.21')->epoch;
# RFC 822/1123
say Mojo::Date->new('Sun, 06 Nov 1994 08:49:37 GMT')->epoch;
# RFC 850/1036
say Mojo::Date->new('Sunday, 06-Nov-94 08:49:37 GMT')->epoch;
# Ansi C asctime()
say Mojo::Date->new('Sun Nov 6 08:49:37 1994')->epoch;
# RFC 3339
say Mojo::Date->new('1994-11-06T08:49:37Z')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37.21Z')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37+01:00')->epoch;
say Mojo::Date->new('1994-11-06T08:49:37-01:00')->epoch;
=head2 to_datetime
my $str = $date->to_datetime;
Render L<RFC 3339|http://tools.ietf.org/html/rfc3339> date and time.
# "1994-11-06T08:49:37Z"
Mojo::Date->new(784111777)->to_datetime;
# "1994-11-06T08:49:37.21Z"
Mojo::Date->new(784111777.21)->to_datetime;
=head2 to_string
my $str = $date->to_string;
Render date suitable for HTTP messages.
# "Sun, 06 Nov 1994 08:49:37 GMT"
Mojo::Date->new(784111777)->to_string;
=head1 OPERATORS
L<Mojo::Date> overloads the following operators.
=head2 bool
my $bool = !!$date;
Always true.
=head2 stringify
my $str = "$date";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_DATE
$fatpacked{"Mojo/EventEmitter.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_EVENTEMITTER';
package Mojo::EventEmitter;
use Mojo::Base -base;
use Mojo::Util 'deprecated';
use Scalar::Util qw(blessed weaken);
use constant DEBUG => $ENV{MOJO_EVENTEMITTER_DEBUG} || 0;
sub catch { $_[0]->on(error => $_[1]) and return $_[0] }
sub emit {
my ($self, $name) = (shift, shift);
if (my $s = $self->{events}{$name}) {
warn "-- Emit $name in @{[blessed $self]} (@{[scalar @$s]})\n" if DEBUG;
for my $cb (@$s) { $self->$cb(@_) }
}
else {
warn "-- Emit $name in @{[blessed $self]} (0)\n" if DEBUG;
die "@{[blessed $self]}: $_[0]" if $name eq 'error';
}
return $self;
}
# DEPRECATED in Tiger Face!
sub emit_safe {
deprecated 'Mojo::EventEmitter::emit_safe is DEPRECATED';
my ($self, $name) = (shift, shift);
if (my $s = $self->{events}{$name}) {
for my $cb (@$s) {
$self->emit(error => qq{Event "$name" failed: $@})
unless eval { $self->$cb(@_); 1 };
}
}
else { die "@{[blessed $self]}: $_[0]" if $name eq 'error' }
return $self;
}
sub has_subscribers { !!@{shift->{events}{shift()} || []} }
sub on {
my ($self, $name, $cb) = @_;
push @{$self->{events}{$name} ||= []}, $cb;
return $cb;
}
sub once {
my ($self, $name, $cb) = @_;
weaken $self;
my $wrapper;
$wrapper = sub {
$self->unsubscribe($name => $wrapper);
$cb->(@_);
};
$self->on($name => $wrapper);
weaken $wrapper;
return $wrapper;
}
sub subscribers { shift->{events}{shift()} || [] }
sub unsubscribe {
my ($self, $name, $cb) = @_;
# One
if ($cb) {
$self->{events}{$name} = [grep { $cb ne $_ } @{$self->{events}{$name}}];
delete $self->{events}{$name} unless @{$self->{events}{$name}};
}
# All
else { delete $self->{events}{$name} }
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::EventEmitter - Event emitter base class
=head1 SYNOPSIS
package Cat;
use Mojo::Base 'Mojo::EventEmitter';
# Emit events
sub poke {
my $self = shift;
$self->emit(roar => 3);
}
package main;
# Subscribe to events
my $tiger = Cat->new;
$tiger->on(roar => sub {
my ($tiger, $times) = @_;
say 'RAWR!' for 1 .. $times;
});
$tiger->poke;
=head1 DESCRIPTION
L<Mojo::EventEmitter> is a simple base class for event emitting objects.
=head1 EVENTS
L<Mojo::EventEmitter> can emit the following events.
=head2 error
$e->on(error => sub {
my ($e, $err) = @_;
...
});
This is a special event for errors, it will not be emitted directly by this
class but is fatal if unhandled.
$e->on(error => sub {
my ($e, $err) = @_;
say "This looks bad: $err";
});
=head1 METHODS
L<Mojo::EventEmitter> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 catch
$e = $e->catch(sub {...});
Subscribe to L</"error"> event.
# Longer version
$e->on(error => sub {...});
=head2 emit
$e = $e->emit('foo');
$e = $e->emit('foo', 123);
Emit event.
=head2 has_subscribers
my $bool = $e->has_subscribers('foo');
Check if event has subscribers.
=head2 on
my $cb = $e->on(foo => sub {...});
Subscribe to event.
$e->on(foo => sub {
my ($e, @args) = @_;
...
});
=head2 once
my $cb = $e->once(foo => sub {...});
Subscribe to event and unsubscribe again after it has been emitted once.
$e->once(foo => sub {
my ($e, @args) = @_;
...
});
=head2 subscribers
my $subscribers = $e->subscribers('foo');
All subscribers for event.
# Unsubscribe last subscriber
$e->unsubscribe(foo => $e->subscribers('foo')->[-1]);
=head2 unsubscribe
$e = $e->unsubscribe('foo');
$e = $e->unsubscribe(foo => $cb);
Unsubscribe from event.
=head1 DEBUGGING
You can set the C<MOJO_EVENTEMITTER_DEBUG> environment variable to get some
advanced diagnostics information printed to C<STDERR>.
MOJO_EVENTEMITTER_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_EVENTEMITTER
$fatpacked{"Mojo/Exception.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_EXCEPTION';
package Mojo::Exception;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Scalar::Util 'blessed';
has [qw(frames line lines_before lines_after)] => sub { [] };
has message => 'Exception!';
has 'verbose';
sub new {
my $self = shift->SUPER::new;
return @_ ? $self->_detect(@_) : $self;
}
sub throw { die shift->new->trace(2)->_detect(@_) }
sub to_string {
my $self = shift;
return $self->message unless $self->verbose;
my $str = $self->message ? $self->message : '';
# Before
$str .= $_->[0] . ': ' . $_->[1] . "\n" for @{$self->lines_before};
# Line
$str .= ($self->line->[0] . ': ' . $self->line->[1] . "\n")
if $self->line->[0];
# After
$str .= $_->[0] . ': ' . $_->[1] . "\n" for @{$self->lines_after};
return $str;
}
sub trace {
my ($self, $start) = @_;
$start //= 1;
my @frames;
while (my @trace = caller($start++)) { push @frames, \@trace }
return $self->frames(\@frames);
}
sub _append {
my ($stack, $line) = @_;
chomp $line;
push @$stack, $line;
}
sub _context {
my ($self, $num, $lines) = @_;
# Line
return unless defined $lines->[0][$num - 1];
$self->line([$num]);
_append($self->line, $_->[$num - 1]) for @$lines;
# Before
for my $i (2 .. 6) {
last if ((my $previous = $num - $i) < 0);
unshift @{$self->lines_before}, [$previous + 1];
_append($self->lines_before->[0], $_->[$previous]) for @$lines;
}
# After
for my $i (0 .. 4) {
next if ((my $next = $num + $i) < 0);
next unless defined $lines->[0][$next];
push @{$self->lines_after}, [$next + 1];
_append($self->lines_after->[-1], $_->[$next]) for @$lines;
}
}
sub _detect {
my ($self, $msg, $files) = @_;
return $msg if blessed $msg && $msg->isa('Mojo::Exception');
$self->message($msg);
# Extract file and line from message
my @trace;
while ($msg =~ /at\s+(.+?)\s+line\s+(\d+)/g) { unshift @trace, [$1, $2] }
# Extract file and line from stacktrace
my $first = $self->frames->[0];
push @trace, [$first->[1], $first->[2]] if $first;
# Search for context in files
for my $frame (@trace) {
next unless -r $frame->[0] && open my $handle, '<:utf8', $frame->[0];
$self->_context($frame->[1], [[<$handle>]]);
return $self;
}
# More context
$self->_context($trace[-1][1], [map { [split "\n"] } @$files]) if $files;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Exception - Exceptions with context
=head1 SYNOPSIS
use Mojo::Exception;
# Throw exception
Mojo::Exception->throw('Not again!');
# Customize exception
die Mojo::Exception->new('Not again!')->trace(2)->verbose(1);
=head1 DESCRIPTION
L<Mojo::Exception> is a container for exceptions with context information.
=head1 ATTRIBUTES
L<Mojo::Exception> implements the following attributes.
=head2 frames
my $frames = $e->frames;
$e = $e->frames($frames);
Stacktrace.
=head2 line
my $line = $e->line;
$e = $e->line([3 => 'foo']);
The line where the exception occurred.
=head2 lines_after
my $lines = $e->lines_after;
$e = $e->lines_after([[1 => 'bar'], [2 => 'baz']]);
Lines after the line where the exception occurred.
=head2 lines_before
my $lines = $e->lines_before;
$e = $e->lines_before([[4 => 'bar'], [5 => 'baz']]);
Lines before the line where the exception occurred.
=head2 message
my $msg = $e->message;
$e = $e->message('Oops!');
Exception message.
=head2 verbose
my $bool = $e->verbose;
$e = $e->verbose($bool);
Render exception with context.
=head1 METHODS
L<Mojo::Exception> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 new
my $e = Mojo::Exception->new('Oops!');
my $e = Mojo::Exception->new('Oops!', $files);
Construct a new L<Mojo::Exception> object.
=head2 throw
Mojo::Exception->throw('Oops!');
Mojo::Exception->throw('Oops!', $files);
Throw exception with stacktrace.
=head2 to_string
my $str = $e->to_string;
Render exception.
=head2 trace
$e = $e->trace;
$e = $e->trace(2);
Store stacktrace.
=head1 OPERATORS
L<Mojo::Exception> overloads the following operators.
=head2 bool
my $bool = !!$e;
Always true.
=head2 stringify
my $str = "$e";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_EXCEPTION
$fatpacked{"Mojo/Headers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_HEADERS';
package Mojo::Headers;
use Mojo::Base -base;
use Mojo::Util 'monkey_patch';
has max_line_size => sub { $ENV{MOJO_MAX_LINE_SIZE} || 10240 };
# Common headers
my %NORMALCASE = map { lc($_) => $_ } (
qw(Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges),
qw(Access-Control-Allow-Origin Allow Authorization Cache-Control Connection),
qw(Content-Disposition Content-Encoding Content-Language Content-Length),
qw(Content-Location Content-Range Content-Type Cookie DNT Date ETag Expect),
qw(Expires Host If-Modified-Since If-None-Match Last-Modified Link Location),
qw(Origin Proxy-Authenticate Proxy-Authorization Range Sec-WebSocket-Accept),
qw(Sec-WebSocket-Extensions Sec-WebSocket-Key Sec-WebSocket-Protocol),
qw(Sec-WebSocket-Version Server Set-Cookie Status Strict-Transport-Security),
qw(TE Trailer Transfer-Encoding Upgrade User-Agent Vary WWW-Authenticate)
);
for my $header (values %NORMALCASE) {
my $name = lc $header;
$name =~ y/-/_/;
monkey_patch __PACKAGE__, $name, sub { shift->header($header => @_) };
}
sub add {
my ($self, $name) = (shift, shift);
# Make sure we have a normal case entry for name
my $key = lc $name;
$self->{normalcase}{$key} //= $name unless $NORMALCASE{$key};
push @{$self->{headers}{$key}}, @_;
return $self;
}
sub append {
my ($self, $name, $value) = @_;
my $old = $self->header($name);
return $self->header($name => defined $old ? "$old, $value" : $value);
}
sub clone { $_[0]->new->from_hash($_[0]->to_hash(1)) }
sub from_hash {
my ($self, $hash) = @_;
# Empty hash deletes all headers
delete $self->{headers} if keys %{$hash} == 0;
# Merge
for my $header (keys %$hash) {
my $value = $hash->{$header};
$self->add($header => ref $value eq 'ARRAY' ? @$value : $value);
}
return $self;
}
sub header {
my ($self, $name) = (shift, shift);
# Replace
return $self->remove($name)->add($name, @_) if @_;
return undef unless my $headers = $self->{headers}{lc $name};
return join ', ', @$headers;
}
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub leftovers { delete shift->{buffer} }
sub names {
my $self = shift;
return [map { $NORMALCASE{$_} || $self->{normalcase}{$_} || $_ }
keys %{$self->{headers}}];
}
sub parse {
my $self = shift;
$self->{state} = 'headers';
$self->{buffer} .= shift // '';
my $headers = $self->{cache} ||= [];
my $max = $self->max_line_size;
while ($self->{buffer} =~ s/^(.*?)\x0d?\x0a//) {
my $line = $1;
# Check line size limit
if (length $line > $max) {
@$self{qw(state limit)} = ('finished', 1);
return $self;
}
# New header
if ($line =~ /^(\S[^:]*)\s*:\s*(.*)$/) { push @$headers, $1, $2 }
# Multiline
elsif (@$headers && $line =~ s/^\s+//) { $headers->[-1] .= " $line" }
# Empty line
else {
$self->add(splice @$headers, 0, 2) while @$headers;
$self->{state} = 'finished';
return $self;
}
}
# Check line size limit
@$self{qw(state limit)} = ('finished', 1) if length $self->{buffer} > $max;
return $self;
}
sub referrer { shift->header(Referer => @_) }
sub remove {
my ($self, $name) = @_;
delete $self->{headers}{lc $name};
return $self;
}
sub to_hash {
my ($self, $multi) = @_;
return {map { $_ => $multi ? $self->{headers}{lc $_} : $self->header($_) }
@{$self->names}};
}
sub to_string {
my $self = shift;
# Make sure multiline values are formatted correctly
my @headers;
for my $name (@{$self->names}) {
push @headers, "$name: $_" for @{$self->{headers}{lc $name}};
}
return join "\x0d\x0a", @headers;
}
1;
=encoding utf8
=head1 NAME
Mojo::Headers - Headers
=head1 SYNOPSIS
use Mojo::Headers;
# Parse
my $headers = Mojo::Headers->new;
$headers->parse("Content-Length: 42\x0d\x0a");
$headers->parse("Content-Type: text/html\x0d\x0a\x0d\x0a");
say $headers->content_length;
say $headers->content_type;
# Build
my $headers = Mojo::Headers->new;
$headers->content_length(42);
$headers->content_type('text/plain');
say $headers->to_string;
=head1 DESCRIPTION
L<Mojo::Headers> is a container for HTTP headers based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
=head1 ATTRIBUTES
L<Mojo::Headers> implements the following attributes.
=head2 max_line_size
my $size = $headers->max_line_size;
$headers = $headers->max_line_size(1024);
Maximum header line size in bytes, defaults to the value of the
C<MOJO_MAX_LINE_SIZE> environment variable or C<10240> (10KB).
=head1 METHODS
L<Mojo::Headers> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 accept
my $accept = $headers->accept;
$headers = $headers->accept('application/json');
Shortcut for the C<Accept> header.
=head2 accept_charset
my $charset = $headers->accept_charset;
$headers = $headers->accept_charset('UTF-8');
Shortcut for the C<Accept-Charset> header.
=head2 accept_encoding
my $encoding = $headers->accept_encoding;
$headers = $headers->accept_encoding('gzip');
Shortcut for the C<Accept-Encoding> header.
=head2 accept_language
my $language = $headers->accept_language;
$headers = $headers->accept_language('de, en');
Shortcut for the C<Accept-Language> header.
=head2 accept_ranges
my $ranges = $headers->accept_ranges;
$headers = $headers->accept_ranges('bytes');
Shortcut for the C<Accept-Ranges> header.
=head2 access_control_allow_origin
my $origin = $headers->access_control_allow_origin;
$headers = $headers->access_control_allow_origin('*');
Shortcut for the C<Access-Control-Allow-Origin> header from
L<Cross-Origin Resource Sharing|http://www.w3.org/TR/cors/>.
=head2 add
$headers = $headers->add(Foo => 'one value');
$headers = $headers->add(Foo => 'first value', 'second value');
Add one or more header values with one or more lines.
# "Vary: Accept"
# "Vary: Accept-Encoding"
$headers->vary('Accept')->add(Vary => 'Accept-Encoding')->to_string;
=head2 allow
my $allow = $headers->allow;
$headers = $headers->allow('GET, POST');
Shortcut for the C<Allow> header.
=head2 append
$headers = $headers->append(Vary => 'Accept-Encoding');
Append value to header and flatten it if necessary.
# "Vary: Accept"
$headers->append(Vary => 'Accept')->to_string;
# "Vary: Accept, Accept-Encoding"
$headers->vary('Accept')->append(Vary => 'Accept-Encoding')->to_string;
=head2 authorization
my $authorization = $headers->authorization;
$headers = $headers->authorization('Basic Zm9vOmJhcg==');
Shortcut for the C<Authorization> header.
=head2 cache_control
my $cache_control = $headers->cache_control;
$headers = $headers->cache_control('max-age=1, no-cache');
Shortcut for the C<Cache-Control> header.
=head2 clone
my $clone = $headers->clone;
Clone headers.
=head2 connection
my $connection = $headers->connection;
$headers = $headers->connection('close');
Shortcut for the C<Connection> header.
=head2 content_disposition
my $disposition = $headers->content_disposition;
$headers = $headers->content_disposition('foo');
Shortcut for the C<Content-Disposition> header.
=head2 content_encoding
my $encoding = $headers->content_encoding;
$headers = $headers->content_encoding('gzip');
Shortcut for the C<Content-Encoding> header.
=head2 content_language
my $language = $headers->content_language;
$headers = $headers->content_language('en');
Shortcut for the C<Content-Language> header.
=head2 content_length
my $len = $headers->content_length;
$headers = $headers->content_length(4000);
Shortcut for the C<Content-Length> header.
=head2 content_location
my $location = $headers->content_location;
$headers = $headers->content_location('http://127.0.0.1/foo');
Shortcut for the C<Content-Location> header.
=head2 content_range
my $range = $headers->content_range;
$headers = $headers->content_range('bytes 2-8/100');
Shortcut for the C<Content-Range> header.
=head2 content_type
my $type = $headers->content_type;
$headers = $headers->content_type('text/plain');
Shortcut for the C<Content-Type> header.
=head2 cookie
my $cookie = $headers->cookie;
$headers = $headers->cookie('f=b');
Shortcut for the C<Cookie> header from
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head2 date
my $date = $headers->date;
$headers = $headers->date('Sun, 17 Aug 2008 16:27:35 GMT');
Shortcut for the C<Date> header.
=head2 dnt
my $dnt = $headers->dnt;
$headers = $headers->dnt(1);
Shortcut for the C<DNT> (Do Not Track) header, which has no specification yet,
but is very commonly used.
=head2 etag
my $etag = $headers->etag;
$headers = $headers->etag('"abc321"');
Shortcut for the C<ETag> header.
=head2 expect
my $expect = $headers->expect;
$headers = $headers->expect('100-continue');
Shortcut for the C<Expect> header.
=head2 expires
my $expires = $headers->expires;
$headers = $headers->expires('Thu, 01 Dec 1994 16:00:00 GMT');
Shortcut for the C<Expires> header.
=head2 from_hash
$headers = $headers->from_hash({'Cookie' => 'a=b'});
$headers = $headers->from_hash({'Cookie' => ['a=b', 'c=d']});
$headers = $headers->from_hash({});
Parse headers from a hash reference, an empty hash removes all headers.
=head2 header
my $value = $headers->header('Foo');
$headers = $headers->header(Foo => 'one value');
$headers = $headers->header(Foo => 'first value', 'second value');
Get or replace the current header values.
=head2 host
my $host = $headers->host;
$headers = $headers->host('127.0.0.1');
Shortcut for the C<Host> header.
=head2 if_modified_since
my $date = $headers->if_modified_since;
$headers = $headers->if_modified_since('Sun, 17 Aug 2008 16:27:35 GMT');
Shortcut for the C<If-Modified-Since> header.
=head2 if_none_match
my $etag = $headers->if_none_match;
$headers = $headers->if_none_match('"abc321"');
Shortcut for the C<If-None-Match> header.
=head2 is_finished
my $bool = $headers->is_finished;
Check if header parser is finished.
=head2 is_limit_exceeded
my $bool = $headers->is_limit_exceeded;
Check if a header has exceeded C<max_line_size>.
=head2 last_modified
my $date = $headers->last_modified;
$headers = $headers->last_modified('Sun, 17 Aug 2008 16:27:35 GMT');
Shortcut for the C<Last-Modified> header.
=head2 leftovers
my $bytes = $headers->leftovers;
Get leftover data from header parser.
=head2 link
my $link = $headers->link;
$headers = $headers->link('<http://127.0.0.1/foo/3>; rel="next"');
Shortcut for the C<Link> header from
L<RFC 5988|http://tools.ietf.org/html/rfc5988>.
=head2 location
my $location = $headers->location;
$headers = $headers->location('http://127.0.0.1/foo');
Shortcut for the C<Location> header.
=head2 names
my $names = $headers->names;
Return a list of all currently defined headers.
# Names of all headers
say for @{$headers->names};
=head2 origin
my $origin = $headers->origin;
$headers = $headers->origin('http://example.com');
Shortcut for the C<Origin> header from
L<RFC 6454|http://tools.ietf.org/html/rfc6454>.
=head2 parse
$headers = $headers->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
Parse formatted headers.
=head2 proxy_authenticate
my $authenticate = $headers->proxy_authenticate;
$headers = $headers->proxy_authenticate('Basic "realm"');
Shortcut for the C<Proxy-Authenticate> header.
=head2 proxy_authorization
my $authorization = $headers->proxy_authorization;
$headers = $headers->proxy_authorization('Basic Zm9vOmJhcg==');
Shortcut for the C<Proxy-Authorization> header.
=head2 range
my $range = $headers->range;
$headers = $headers->range('bytes=2-8');
Shortcut for the C<Range> header.
=head2 referrer
my $referrer = $headers->referrer;
$headers = $headers->referrer('http://example.com');
Shortcut for the C<Referer> header, there was a typo in
L<RFC 2068|http://tools.ietf.org/html/rfc2068> which resulted in C<Referer>
becoming an official header.
=head2 remove
$headers = $headers->remove('Foo');
Remove a header.
=head2 sec_websocket_accept
my $accept = $headers->sec_websocket_accept;
$headers = $headers->sec_websocket_accept('s3pPLMBiTxaQ9kYGzzhZRbK+xOo=');
Shortcut for the C<Sec-WebSocket-Accept> header from
L<RFC 6455|http://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_extensions
my $extensions = $headers->sec_websocket_extensions;
$headers = $headers->sec_websocket_extensions('foo');
Shortcut for the C<Sec-WebSocket-Extensions> header from
L<RFC 6455|http://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_key
my $key = $headers->sec_websocket_key;
$headers = $headers->sec_websocket_key('dGhlIHNhbXBsZSBub25jZQ==');
Shortcut for the C<Sec-WebSocket-Key> header from
L<RFC 6455|http://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_protocol
my $proto = $headers->sec_websocket_protocol;
$headers = $headers->sec_websocket_protocol('sample');
Shortcut for the C<Sec-WebSocket-Protocol> header from
L<RFC 6455|http://tools.ietf.org/html/rfc6455>.
=head2 sec_websocket_version
my $version = $headers->sec_websocket_version;
$headers = $headers->sec_websocket_version(13);
Shortcut for the C<Sec-WebSocket-Version> header from
L<RFC 6455|http://tools.ietf.org/html/rfc6455>.
=head2 server
my $server = $headers->server;
$headers = $headers->server('Mojo');
Shortcut for the C<Server> header.
=head2 set_cookie
my $cookie = $headers->set_cookie;
$headers = $headers->set_cookie('f=b; path=/');
Shortcut for the C<Set-Cookie> header from
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head2 status
my $status = $headers->status;
$headers = $headers->status('200 OK');
Shortcut for the C<Status> header from
L<RFC 3875|http://tools.ietf.org/html/rfc3875>.
=head2 strict_transport_security
my $policy = $headers->strict_transport_security;
$headers = $headers->strict_transport_security('max-age=31536000');
Shortcut for the C<Strict-Transport-Security> header from
L<RFC 6797|http://tools.ietf.org/html/rfc6797>.
=head2 te
my $te = $headers->te;
$headers = $headers->te('chunked');
Shortcut for the C<TE> header.
=head2 to_hash
my $single = $headers->to_hash;
my $multi = $headers->to_hash(1);
Turn headers into hash reference, array references to represent multiple
headers with the same name are disabled by default.
say $headers->to_hash->{DNT};
=head2 to_string
my $str = $headers->to_string;
Turn headers into a string, suitable for HTTP messages.
=head2 trailer
my $trailer = $headers->trailer;
$headers = $headers->trailer('X-Foo');
Shortcut for the C<Trailer> header.
=head2 transfer_encoding
my $encoding = $headers->transfer_encoding;
$headers = $headers->transfer_encoding('chunked');
Shortcut for the C<Transfer-Encoding> header.
=head2 upgrade
my $upgrade = $headers->upgrade;
$headers = $headers->upgrade('websocket');
Shortcut for the C<Upgrade> header.
=head2 user_agent
my $agent = $headers->user_agent;
$headers = $headers->user_agent('Mojo/1.0');
Shortcut for the C<User-Agent> header.
=head2 vary
my $vary = $headers->vary;
$headers = $headers->vary('*');
Shortcut for the C<Vary> header.
=head2 www_authenticate
my $authenticate = $headers->www_authenticate;
$headers = $headers->www_authenticate('Basic realm="realm"');
Shortcut for the C<WWW-Authenticate> header.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_HEADERS
$fatpacked{"Mojo/HelloWorld.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_HELLOWORLD';
package Mojo::HelloWorld;
use Mojolicious::Lite;
app->log->level('error')->path(undef);
any '/*whatever' => {whatever => '', text => 'Your Mojo is working!'};
1;
=encoding utf8
=head1 NAME
Mojo::HelloWorld - Hello World!
=head1 SYNOPSIS
use Mojo::HelloWorld;
my $hello = Mojo::HelloWorld->new;
$hello->start;
=head1 DESCRIPTION
L<Mojo::HelloWorld> is the default L<Mojolicious> application, used mostly
for testing.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_HELLOWORLD
$fatpacked{"Mojo/Home.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_HOME';
package Mojo::Home;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Cwd 'abs_path';
use File::Basename 'dirname';
use File::Find 'find';
use File::Spec::Functions qw(abs2rel catdir catfile splitdir);
use FindBin;
use Mojo::Util qw(class_to_path slurp);
has parts => sub { [] };
sub detect {
my $self = shift;
# Environment variable
return $self->parts([splitdir(abs_path $ENV{MOJO_HOME})]) if $ENV{MOJO_HOME};
# Try to find home from lib directory
if (my $class = @_ ? shift : 'Mojo::HelloWorld') {
my $file = class_to_path $class;
if (my $path = $INC{$file}) {
$path =~ s/\Q$file\E$//;
my @home = splitdir $path;
# Remove "lib" and "blib"
pop @home while @home && ($home[-1] =~ /^b?lib$/ || $home[-1] eq '');
# Turn into absolute path
return $self->parts([splitdir(abs_path(catdir(@home) || '.'))]);
}
}
# FindBin fallback
return $self->parts([split '/', $FindBin::Bin]);
}
sub lib_dir {
my $path = catdir @{shift->parts}, 'lib';
return -d $path ? $path : undef;
}
sub list_files {
my ($self, $dir) = @_;
$dir = catdir @{$self->parts}, split '/', ($dir // '');
return [] unless -d $dir;
my @files;
find {
wanted => sub {
my @parts = splitdir(abs2rel($File::Find::name, $dir));
push @files, join '/', @parts unless grep {/^\./} @parts;
},
no_chdir => 1
}, $dir;
return [sort @files];
}
sub mojo_lib_dir { catdir(dirname(__FILE__), '..') }
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse { shift->parts([splitdir shift]) }
sub rel_dir { catdir(@{shift->parts}, split '/', shift) }
sub rel_file { catfile(@{shift->parts}, split '/', shift) }
sub to_string { catdir(@{shift->parts}) }
1;
=encoding utf8
=head1 NAME
Mojo::Home - Home sweet home!
=head1 SYNOPSIS
use Mojo::Home;
# Find and manage the project root directory
my $home = Mojo::Home->new;
$home->detect;
say $home->lib_dir;
say $home->rel_file('templates/layouts/default.html.ep');
say "$home";
=head1 DESCRIPTION
L<Mojo::Home> is a container for home directories.
=head1 ATTRIBUTES
L<Mojo::Home> implements the following attributes.
=head2 parts
my $parts = $home->parts;
$home = $home->parts([]);
Home directory parts.
=head1 METHODS
L<Mojo::Home> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 detect
$home = $home->detect;
$home = $home->detect('My::App');
Detect home directory from the value of the C<MOJO_HOME> environment variable
or application class.
=head2 lib_dir
my $path = $home->lib_dir;
Path to C<lib> directory of application.
=head2 list_files
my $files = $home->list_files;
my $files = $home->list_files('foo/bar');
Portably list all files recursively in directory relative to the home
directory.
say $home->rel_file($home->list_files('templates/layouts')->[1]);
=head2 mojo_lib_dir
my $path = $home->mojo_lib_dir;
Path to C<lib> directory in which L<Mojolicious> is installed.
=head2 new
my $home = Mojo::Home->new;
my $home = Mojo::Home->new('/home/sri/myapp');
Construct a new L<Mojo::Home> object and L</"parse"> home directory if
necessary.
=head2 parse
$home = $home->parse('/home/sri/myapp');
Parse home directory.
=head2 rel_dir
my $path = $home->rel_dir('foo/bar');
Portably generate an absolute path for a directory relative to the home
directory.
=head2 rel_file
my $path = $home->rel_file('foo/bar.html');
Portably generate an absolute path for a file relative to the home directory.
=head2 to_string
my $str = $home->to_string;
Home directory.
=head1 OPERATORS
L<Mojo::Home> overloads the following operators.
=head2 bool
my $bool = !!$home;
Always true.
=head2 stringify
my $str = "$home";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_HOME
$fatpacked{"Mojo/IOLoop.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_IOLOOP';
package Mojo::IOLoop;
use Mojo::Base -base;
# "Professor: Amy, technology isn't intrinsically good or evil. It's how it's
# used. Like the death ray."
use Carp 'croak';
use Mojo::IOLoop::Client;
use Mojo::IOLoop::Delay;
use Mojo::IOLoop::Server;
use Mojo::IOLoop::Stream;
use Mojo::Reactor::Poll;
use Mojo::Util qw(md5_sum steady_time);
use Scalar::Util qw(blessed weaken);
use constant DEBUG => $ENV{MOJO_IOLOOP_DEBUG} || 0;
has accept_interval => 0.025;
has [qw(lock unlock)];
has max_accepts => 0;
has max_connections => 1000;
has multi_accept => 50;
has reactor => sub {
my $class = Mojo::Reactor::Poll->detect;
warn "-- Reactor initialized ($class)\n" if DEBUG;
return $class->new->catch(sub { warn "@{[blessed $_[0]]}: $_[1]" });
};
# Ignore PIPE signal
$SIG{PIPE} = 'IGNORE';
# Initialize singleton reactor early
__PACKAGE__->singleton->reactor;
sub acceptor {
my ($self, $acceptor) = (_instance(shift), @_);
# Find acceptor for id
return $self->{acceptors}{$acceptor} unless ref $acceptor;
# Connect acceptor with reactor
my $id = $self->_id;
$self->{acceptors}{$id} = $acceptor;
weaken $acceptor->reactor($self->reactor)->{reactor};
$self->{accepts} = $self->max_accepts if $self->max_accepts;
# Allow new acceptor to get picked up
$self->_not_accepting;
return $id;
}
sub client {
my ($self, $cb) = (_instance(shift), pop);
# Make sure timers are running
$self->_recurring;
my $id = $self->_id;
my $client = $self->{connections}{$id}{client} = Mojo::IOLoop::Client->new;
weaken $client->reactor($self->reactor)->{reactor};
weaken $self;
$client->on(
connect => sub {
delete $self->{connections}{$id}{client};
my $stream = Mojo::IOLoop::Stream->new(pop);
$self->_stream($stream => $id);
$self->$cb(undef, $stream);
}
);
$client->on(
error => sub {
$self->_remove($id);
$self->$cb(pop, undef);
}
);
$client->connect(@_);
return $id;
}
sub delay {
my $delay = Mojo::IOLoop::Delay->new;
weaken $delay->ioloop(_instance(shift))->{ioloop};
return @_ ? $delay->steps(@_) : $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 recurring { shift->_timer(recurring => @_) }
sub remove {
my ($self, $id) = (_instance(shift), @_);
my $c = $self->{connections}{$id};
if ($c && (my $stream = $c->{stream})) { return $stream->close_gracefully }
$self->_remove($id);
}
sub reset {
my $self = _instance(shift);
$self->_remove($_)
for keys %{$self->{acceptors}}, keys %{$self->{connections}};
$self->reactor->reset;
$self->$_ for qw(_stop stop);
}
sub server {
my ($self, $cb) = (_instance(shift), pop);
my $server = Mojo::IOLoop::Server->new;
weaken $self;
$server->on(
accept => sub {
my $stream = Mojo::IOLoop::Stream->new(pop);
$self->$cb($stream, $self->stream($stream));
}
);
$server->listen(@_);
return $self->acceptor($server);
}
sub singleton { state $loop = shift->SUPER::new }
sub start {
my $self = shift;
croak 'Mojo::IOLoop already running' if $self->is_running;
_instance($self)->reactor->start;
}
sub stop { _instance(shift)->reactor->stop }
sub stream {
my ($self, $stream) = (_instance(shift), @_);
# Find stream for id
return ($self->{connections}{$stream} || {})->{stream} unless ref $stream;
# Release accept mutex
$self->_not_accepting;
# Enforce connection limit (randomize to improve load balancing)
$self->max_connections(0)
if defined $self->{accepts} && ($self->{accepts} -= int(rand 2) + 1) <= 0;
return $self->_stream($stream, $self->_id);
}
sub timer { shift->_timer(timer => @_) }
sub _accepting {
my $self = shift;
# Check if we have acceptors
my $acceptors = $self->{acceptors} ||= {};
return $self->_remove(delete $self->{accept}) unless keys %$acceptors;
# Check connection limit
my $i = keys %{$self->{connections}};
my $max = $self->max_connections;
return unless $i < $max;
# Acquire accept mutex
if (my $cb = $self->lock) { return unless $cb->(!$i) }
$self->_remove(delete $self->{accept});
# Check if multi-accept is desirable
my $multi = $self->multi_accept;
$_->multi_accept($max < $multi ? 1 : $multi)->start for values %$acceptors;
$self->{accepting}++;
}
sub _id {
my $self = shift;
my $id;
do { $id = md5_sum('c' . steady_time . rand 999) }
while $self->{connections}{$id} || $self->{acceptors}{$id};
return $id;
}
sub _instance { ref $_[0] ? $_[0] : $_[0]->singleton }
sub _not_accepting {
my $self = shift;
# Make sure timers are running
$self->_recurring;
# Release accept mutex
return unless delete $self->{accepting};
return unless my $cb = $self->unlock;
$cb->();
$_->stop for values %{$self->{acceptors} || {}};
}
sub _recurring {
my $self = shift;
$self->{accept} ||= $self->recurring($self->accept_interval => \&_accepting);
$self->{stop} ||= $self->recurring(1 => \&_stop);
}
sub _remove {
my ($self, $id) = @_;
# Timer
return unless my $reactor = $self->reactor;
return if $reactor->remove($id);
# Acceptor
if (delete $self->{acceptors}{$id}) { $self->_not_accepting }
# Connection
else { delete $self->{connections}{$id} }
}
sub _stop {
my $self = shift;
return if keys %{$self->{connections}};
$self->stop if $self->max_connections == 0;
return if keys %{$self->{acceptors}};
$self->{$_} && $self->_remove(delete $self->{$_}) for qw(accept stop);
}
sub _stream {
my ($self, $stream, $id) = @_;
# Make sure timers are running
$self->_recurring;
# Connect stream with reactor
$self->{connections}{$id}{stream} = $stream;
weaken $stream->reactor($self->reactor)->{reactor};
weaken $self;
$stream->on(close => sub { $self && $self->_remove($id) });
$stream->start;
return $id;
}
sub _timer {
my ($self, $method, $after, $cb) = (_instance(shift), @_);
weaken $self;
return $self->reactor->$method($after => sub { $self->$cb });
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop - Minimalistic event loop
=head1 SYNOPSIS
use Mojo::IOLoop;
# Listen on port 3000
Mojo::IOLoop->server({port => 3000} => sub {
my ($loop, $stream) = @_;
$stream->on(read => sub {
my ($stream, $bytes) = @_;
# Process input chunk
say $bytes;
# Write response
$stream->write('HTTP/1.1 200 OK');
});
});
# Connect to port 3000
my $id = Mojo::IOLoop->client({port => 3000} => sub {
my ($loop, $err, $stream) = @_;
$stream->on(read => sub {
my ($stream, $bytes) = @_;
# Process input
say "Input: $bytes";
});
# Write request
$stream->write("GET / HTTP/1.1\x0d\x0a\x0d\x0a");
});
# Add a timer
Mojo::IOLoop->timer(5 => sub {
my $loop = shift;
$loop->remove($id);
});
# Start event loop if necessary
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop> is a very minimalistic event loop based on L<Mojo::Reactor>,
it has been reduced to the absolute minimal feature set required to build
solid and scalable non-blocking TCP clients and servers.
Depending on operating system, the default per-process and system-wide file
descriptor limits are often very low and need to be tuned for better
scalability. The C<LIBEV_FLAGS> environment variable should also be used to
select the best possible L<EV> backend, which usually defaults to the not very
scalable C<select>.
LIBEV_FLAGS=1 # select
LIBEV_FLAGS=2 # poll
LIBEV_FLAGS=4 # epoll (Linux)
LIBEV_FLAGS=8 # kqueue (*BSD, OS X)
The event loop will be resilient to time jumps if a monotonic clock is
available through L<Time::HiRes>. A TLS certificate and key are also built
right in, to make writing test servers as easy as possible. Also note that for
convenience the C<PIPE> signal will be set to C<IGNORE> when L<Mojo::IOLoop>
is loaded.
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"REAL-TIME WEB"> for more.
=head1 ATTRIBUTES
L<Mojo::IOLoop> implements the following attributes.
=head2 accept_interval
my $interval = $loop->accept_interval;
$loop = $loop->accept_interval(0.5);
Interval in seconds for trying to reacquire the accept mutex, defaults to
C<0.025>. Note that changing this value can affect performance and idle CPU
usage.
=head2 lock
my $cb = $loop->lock;
$loop = $loop->lock(sub {...});
A callback for acquiring the accept mutex, used to sync multiple server
processes. The callback should return true or false. Note that exceptions in
this callback are not captured.
$loop->lock(sub {
my $blocking = shift;
# Got the accept mutex, start accepting new connections
return 1;
});
=head2 max_accepts
my $max = $loop->max_accepts;
$loop = $loop->max_accepts(1000);
The maximum number of connections this event loop is allowed to accept before
shutting down gracefully without interrupting existing connections, defaults
to C<0>. Setting the value to C<0> will allow this event loop to accept new
connections indefinitely. Note that up to half of this value can be subtracted
randomly to improve load balancing between multiple server processes.
=head2 max_connections
my $max = $loop->max_connections;
$loop = $loop->max_connections(1000);
The maximum number of concurrent connections this event loop is allowed to
handle before stopping to accept new incoming connections, defaults to
C<1000>. Setting the value to C<0> will make this event loop stop accepting
new connections and allow it to shut down gracefully without interrupting
existing connections.
=head2 multi_accept
my $multi = $loop->multi_accept;
$loop = $loop->multi_accept(100);
Number of connections to accept at once, defaults to C<50>.
=head2 reactor
my $reactor = $loop->reactor;
$loop = $loop->reactor(Mojo::Reactor->new);
Low-level event reactor, usually a L<Mojo::Reactor::Poll> or
L<Mojo::Reactor::EV> object with a default subscriber to the event
L<Mojo::Reactor/"error">.
# Watch if handle becomes readable or writable
$loop->reactor->io($handle => sub {
my ($reactor, $writable) = @_;
say $writable ? 'Handle is writable' : 'Handle is readable';
});
# Change to watching only if handle becomes writable
$loop->reactor->watch($handle, 0, 1);
=head2 unlock
my $cb = $loop->unlock;
$loop = $loop->unlock(sub {...});
A callback for releasing the accept mutex, used to sync multiple server
processes. Note that exceptions in this callback are not captured.
=head1 METHODS
L<Mojo::IOLoop> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 acceptor
my $server = Mojo::IOLoop->acceptor($id);
my $server = $loop->acceptor($id);
my $id = $loop->acceptor(Mojo::IOLoop::Server->new);
Get L<Mojo::IOLoop::Server> object for id or turn object into an acceptor.
=head2 client
my $id
= Mojo::IOLoop->client(address => '127.0.0.1', port => 3000, sub {...});
my $id = $loop->client(address => '127.0.0.1', port => 3000, sub {...});
my $id = $loop->client({address => '127.0.0.1', port => 3000} => sub {...});
Open TCP connection with L<Mojo::IOLoop::Client>, takes the same arguments as
L<Mojo::IOLoop::Client/"connect">.
# Connect to localhost on port 3000
Mojo::IOLoop->client({port => 3000} => sub {
my ($loop, $err, $stream) = @_;
...
});
=head2 delay
my $delay = Mojo::IOLoop->delay;
my $delay = $loop->delay;
my $delay = $loop->delay(sub {...});
my $delay = $loop->delay(sub {...}, sub {...});
Build L<Mojo::IOLoop::Delay> object to manage callbacks and control the flow
of events for this event loop, which can help you avoid deep nested closures
and memory leaks that often result from continuation-passing style. Callbacks
will be passed along to L<Mojo::IOLoop::Delay/"steps">.
# Synchronize multiple events
my $delay = Mojo::IOLoop->delay(sub { say 'BOOM!' });
for my $i (1 .. 10) {
my $end = $delay->begin;
Mojo::IOLoop->timer($i => sub {
say 10 - $i;
$end->();
});
}
$delay->wait;
# Sequentialize multiple events
Mojo::IOLoop->delay(
# First step (simple timer)
sub {
my $delay = shift;
Mojo::IOLoop->timer(2 => $delay->begin);
say 'Second step in 2 seconds.';
},
# Second step (concurrent timers)
sub {
my $delay = shift;
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say 'Third step in 3 seconds.';
},
# Third step (the end)
sub { say 'And done after 5 seconds total.' }
)->wait;
# Handle exceptions in all steps
Mojo::IOLoop->delay(
sub {
my $delay = shift;
die 'Intentional error';
},
sub {
my ($delay, @args) = @_;
say 'Never actually reached.';
}
)->catch(sub {
my ($delay, $err) = @_;
say "Something went wrong: $err";
})->wait;
=head2 is_running
my $bool = Mojo::IOLoop->is_running;
my $bool = $loop->is_running;
Check if event loop is running.
exit unless Mojo::IOLoop->is_running;
=head2 next_tick
my $undef = Mojo::IOLoop->next_tick(sub {...});
my $undef = $loop->next_tick(sub {...});
Invoke callback as soon as possible, but not before returning, always returns
C<undef>.
# Perform operation on next reactor tick
Mojo::IOLoop->next_tick(sub {
my $loop = shift;
...
});
=head2 one_tick
Mojo::IOLoop->one_tick;
$loop->one_tick;
Run event loop until an event occurs. Note that this method can recurse back
into the reactor, so you need to be careful.
# Don't block longer than 0.5 seconds
my $id = Mojo::IOLoop->timer(0.5 => sub {});
Mojo::IOLoop->one_tick;
Mojo::IOLoop->remove($id);
=head2 recurring
my $id = Mojo::IOLoop->recurring(3 => sub {...});
my $id = $loop->recurring(0 => sub {...});
my $id = $loop->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given
amount of time in seconds.
# Perform operation every 5 seconds
Mojo::IOLoop->recurring(5 => sub {
my $loop = shift;
...
});
=head2 remove
Mojo::IOLoop->remove($id);
$loop->remove($id);
Remove anything with an id, connections will be dropped gracefully by allowing
them to finish writing all data in their write buffers.
=head2 reset
Mojo::IOLoop->reset;
$loop->reset;
Remove everything and stop the event loop.
=head2 server
my $id = Mojo::IOLoop->server(port => 3000, sub {...});
my $id = $loop->server(port => 3000, sub {...});
my $id = $loop->server({port => 3000} => sub {...});
Accept TCP connections with L<Mojo::IOLoop::Server>, takes the same arguments
as L<Mojo::IOLoop::Server/"listen">.
# Listen on port 3000
Mojo::IOLoop->server({port => 3000} => sub {
my ($loop, $stream, $id) = @_;
...
});
# Listen on random port
my $id = Mojo::IOLoop->server({address => '127.0.0.1'} => sub {
my ($loop, $stream, $id) = @_;
...
});
my $port = Mojo::IOLoop->acceptor($id)->handle->sockport;
=head2 singleton
my $loop = Mojo::IOLoop->singleton;
The global L<Mojo::IOLoop> singleton, used to access a single shared event
loop object from everywhere inside the process.
# Many methods also allow you to take shortcuts
Mojo::IOLoop->timer(2 => sub { Mojo::IOLoop->stop });
Mojo::IOLoop->start;
# Restart active timer
my $id = Mojo::IOLoop->timer(3 => sub { say 'Timeout!' });
Mojo::IOLoop->singleton->reactor->again($id);
=head2 start
Mojo::IOLoop->start;
$loop->start;
Start the event loop, this will block until L</"stop"> is called. Note that
some reactors stop automatically if there are no events being watched anymore.
# Start event loop only if it is not running already
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 stop
Mojo::IOLoop->stop;
$loop->stop;
Stop the event loop, this will not interrupt any existing connections and the
event loop can be restarted by running L</"start"> again.
=head2 stream
my $stream = Mojo::IOLoop->stream($id);
my $stream = $loop->stream($id);
my $id = $loop->stream(Mojo::IOLoop::Stream->new);
Get L<Mojo::IOLoop::Stream> object for id or turn object into a connection.
# Increase inactivity timeout for connection to 300 seconds
Mojo::IOLoop->stream($id)->timeout(300);
=head2 timer
my $id = Mojo::IOLoop->timer(3 => sub {...});
my $id = $loop->timer(0 => sub {...});
my $id = $loop->timer(0.25 => sub {...});
Create a new timer, invoking the callback after a given amount of time in
seconds.
# Perform operation in 5 seconds
Mojo::IOLoop->timer(5 => sub {
my $loop = shift;
...
});
=head1 DEBUGGING
You can set the C<MOJO_IOLOOP_DEBUG> environment variable to get some advanced
diagnostics information printed to C<STDERR>.
MOJO_IOLOOP_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_IOLOOP
$fatpacked{"Mojo/IOLoop/Client.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_IOLOOP_CLIENT';
package Mojo::IOLoop::Client;
use Mojo::Base 'Mojo::EventEmitter';
use Errno 'EINPROGRESS';
use IO::Socket::IP;
use Mojo::IOLoop;
use Scalar::Util 'weaken';
use Socket qw(IPPROTO_TCP TCP_NODELAY);
# Non-blocking name resolution requires Net::DNS::Native
use constant NDN => $ENV{MOJO_NO_NDN}
? 0
: eval 'use Net::DNS::Native 0.12 (); 1';
my $NDN = NDN ? Net::DNS::Native->new(pool => 5, extra_thread => 1) : undef;
# TLS support requires IO::Socket::SSL
use constant TLS => $ENV{MOJO_NO_TLS}
? 0
: eval 'use IO::Socket::SSL 1.84 (); 1';
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
# SOCKS support requires IO::Socket::Socks
use constant SOCKS => $ENV{MOJO_NO_SOCKS}
? 0
: eval 'use IO::Socket::Socks 0.64 (); 1';
use constant SOCKS_READ => SOCKS ? IO::Socket::Socks::SOCKS_WANT_READ() : 0;
use constant SOCKS_WRITE => SOCKS ? IO::Socket::Socks::SOCKS_WANT_WRITE() : 0;
has reactor => sub { Mojo::IOLoop->singleton->reactor };
sub DESTROY { shift->_cleanup }
sub connect {
my $self = shift;
my $args = ref $_[0] ? $_[0] : {@_};
# Timeout
weaken $self;
my $reactor = $self->reactor;
$self->{timer} = $reactor->timer($args->{timeout} || 10,
sub { $self->emit(error => 'Connect timeout') });
# Blocking name resolution
$_ && s/[[\]]//g for @$args{qw(address socks_address)};
my $address = $args->{socks_address} || ($args->{address} ||= 'localhost');
return $reactor->next_tick(sub { $self && $self->_connect($args) })
unless NDN && $address ne 'localhost' && !$args->{handle};
# Non-blocking name resolution
my $handle = $self->{dns}
= $NDN->getaddrinfo($address, _port($args), {protocol => IPPROTO_TCP});
$reactor->io(
$handle => sub {
my $reactor = shift;
$reactor->remove($self->{dns});
my ($err, @res) = $NDN->get_result(delete $self->{dns});
return $self->emit(error => "Can't resolve: $err") if $err;
$args->{addr_info} = \@res;
$self->_connect($args);
}
)->watch($handle, 1, 0);
}
sub _cleanup {
my $self = shift;
return $self unless my $reactor = $self->reactor;
$NDN->timedout($self->{dns}) if $self->{dns};
$self->{$_} && $reactor->remove(delete $self->{$_}) for qw(dns timer handle);
return $self;
}
sub _connect {
my ($self, $args) = @_;
my $handle;
my $address = $args->{socks_address} || $args->{address};
unless ($handle = $self->{handle} = $args->{handle}) {
my %options = (
PeerAddr => $address eq 'localhost' ? '127.0.0.1' : $address,
PeerPort => _port($args)
);
%options = (PeerAddrInfo => $args->{addr_info}) if $args->{addr_info};
$options{Blocking} = 0;
$options{LocalAddr} = $args->{local_address} if $args->{local_address};
return $self->emit(error => "Can't connect: $@")
unless $self->{handle} = $handle = IO::Socket::IP->new(%options);
}
$handle->blocking(0);
# Wait for handle to become writable
weaken $self;
$self->reactor->io($handle => sub { $self->_ready($args) })
->watch($handle, 0, 1);
}
sub _ready {
my ($self, $args) = @_;
# Retry or handle exceptions
my $handle = $self->{handle};
return $! == EINPROGRESS ? undef : $self->emit(error => $!)
if $handle->isa('IO::Socket::IP') && !$handle->connect;
return $self->emit(error => $! || 'Not connected') unless $handle->connected;
# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
$self->_try_socks($args);
}
sub _port { $_[0]->{socks_port} || $_[0]->{port} || ($_[0]->{tls} ? 443 : 80) }
sub _socks {
my ($self, $args) = @_;
# Connected
my $handle = $self->{handle};
return $self->_try_tls($args) if $handle->ready;
# Switch between reading and writing
my $err = $IO::Socket::Socks::SOCKS_ERROR;
if ($err == SOCKS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == SOCKS_WRITE) { $self->reactor->watch($handle, 1, 1) }
else { $self->emit(error => $err) }
}
sub _tls {
my $self = shift;
# Connected
my $handle = $self->{handle};
return $self->_cleanup->emit(connect => $handle) if $handle->connect_SSL;
# Switch between reading and writing
my $err = $IO::Socket::SSL::SSL_ERROR;
if ($err == TLS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}
sub _try_socks {
my ($self, $args) = @_;
my $handle = $self->{handle};
return $self->_try_tls($args) unless $args->{socks_address};
return $self->emit(
error => 'IO::Socket::Socks 0.64 required for SOCKS support')
unless SOCKS;
my %options
= (ConnectAddr => $args->{address}, ConnectPort => $args->{port});
@options{qw(AuthType Username Password)}
= ('userpass', @$args{qw(socks_user socks_pass)})
if $args->{socks_user};
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'SOCKS upgrade failed')
unless IO::Socket::Socks->start_SOCKS($handle, %options);
weaken $self;
$reactor->io($handle => sub { $self->_socks($args) })->watch($handle, 0, 1);
}
sub _try_tls {
my ($self, $args) = @_;
my $handle = $self->{handle};
return $self->_cleanup->emit(connect => $handle)
if !$args->{tls} || $handle->isa('IO::Socket::SSL');
return $self->emit(error => 'IO::Socket::SSL 1.84 required for TLS support')
unless TLS;
# Upgrade
weaken $self;
my %options = (
SSL_ca_file => $args->{tls_ca}
&& -T $args->{tls_ca} ? $args->{tls_ca} : undef,
SSL_cert_file => $args->{tls_cert},
SSL_error_trap => sub { $self->emit(error => $_[1]) },
SSL_hostname => IO::Socket::SSL->can_client_sni ? $args->{address} : '',
SSL_key_file => $args->{tls_key},
SSL_startHandshake => 0,
SSL_verify_mode => $args->{tls_ca} ? 0x01 : 0x00,
SSL_verifycn_name => $args->{address},
SSL_verifycn_scheme => $args->{tls_ca} ? 'http' : undef
);
my $reactor = $self->reactor;
$reactor->remove($handle);
return $self->emit(error => 'TLS upgrade failed')
unless IO::Socket::SSL->start_SSL($handle, %options);
$reactor->io($handle => sub { $self->_tls })->watch($handle, 0, 1);
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Client - Non-blocking TCP client
=head1 SYNOPSIS
use Mojo::IOLoop::Client;
# Create socket connection
my $client = Mojo::IOLoop::Client->new;
$client->on(connect => sub {
my ($client, $handle) = @_;
...
});
$client->on(error => sub {
my ($client, $err) = @_;
...
});
$client->connect(address => 'example.com', port => 80);
# Start reactor if necessary
$client->reactor->start unless $client->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Client> opens TCP connections for L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Client> inherits all events from L<Mojo::EventEmitter> and can
emit the following new ones.
=head2 connect
$client->on(connect => sub {
my ($client, $handle) = @_;
...
});
Emitted once the connection is established.
=head2 error
$client->on(error => sub {
my ($client, $err) = @_;
...
});
Emitted if an error occurs on the connection, fatal if unhandled.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Client> implements the following attributes.
=head2 reactor
my $reactor = $client->reactor;
$client = $client->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the
global L<Mojo::IOLoop> singleton.
=head1 METHODS
L<Mojo::IOLoop::Client> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 connect
$client->connect(address => '127.0.0.1', port => 3000);
Open a socket connection to a remote host. Note that non-blocking name
resolution depends on L<Net::DNS::Native> (0.12+) and TLS support on
L<IO::Socket::SSL> (1.84+).
These options are currently available:
=over 2
=item address
address => 'mojolicio.us'
Address or host name of the peer to connect to, defaults to C<localhost>.
=item handle
handle => $handle
Use an already prepared handle.
=item local_address
local_address => '127.0.0.1'
Local address to bind to.
=item port
port => 80
Port to connect to, defaults to C<80> or C<443> with C<tls> option.
=item socks_address
socks_address => '127.0.0.1'
Address or host name of SOCKS5 proxy server to use for connection.
=item socks_pass
socks_pass => 'secr3t'
Password to use for SOCKS5 authentication.
=item socks_port
socks_port => 9050
Port of SOCKS5 proxy server to use for connection.
=item socks_user
socks_user => 'sri'
Username to use for SOCKS5 authentication.
=item timeout
timeout => 15
Maximum amount of time in seconds establishing connection may take before
getting canceled, defaults to C<10>.
=item tls
tls => 1
Enable TLS.
=item tls_ca
tls_ca => '/etc/tls/ca.crt'
Path to TLS certificate authority file. Also activates hostname verification.
=item tls_cert
tls_cert => '/etc/tls/client.crt'
Path to the TLS certificate file.
=item tls_key
tls_key => '/etc/tls/client.key'
Path to the TLS key file.
=back
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_IOLOOP_CLIENT
$fatpacked{"Mojo/IOLoop/Delay.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_IOLOOP_DELAY';
package Mojo::IOLoop::Delay;
use Mojo::Base 'Mojo::EventEmitter';
use Mojo::IOLoop;
use Mojo::Util;
use Hash::Util::FieldHash 'fieldhash';
has ioloop => sub { Mojo::IOLoop->singleton };
fieldhash my %REMAINING;
sub begin {
my ($self, $offset, $len) = @_;
$self->{pending}++;
my $id = $self->{counter}++;
return sub { $self->_step($id, $offset // 1, $len, @_) };
}
sub data { Mojo::Util::_stash(data => @_) }
sub pass { $_[0]->begin->(@_) }
sub remaining {
my $self = shift;
return $REMAINING{$self} //= [] unless @_;
$REMAINING{$self} = shift;
return $self;
}
sub steps {
my $self = shift->remaining([@_]);
$self->ioloop->next_tick($self->begin);
return $self;
}
sub wait {
my $self = shift;
return if $self->ioloop->is_running;
$self->once(error => \&_die);
$self->once(finish => sub { shift->ioloop->stop });
$self->ioloop->start;
}
sub _die { $_[0]->has_subscribers('error') ? $_[0]->ioloop->stop : die $_[1] }
sub _step {
my ($self, $id, $offset, $len) = (shift, shift, shift, shift);
$self->{args}[$id]
= [@_ ? defined $len ? splice @_, $offset, $len : splice @_, $offset : ()];
return $self if $self->{fail} || --$self->{pending} || $self->{lock};
local $self->{lock} = 1;
my @args = map {@$_} @{delete $self->{args}};
$self->{counter} = 0;
if (my $cb = shift @{$self->remaining}) {
eval { $self->$cb(@args); 1 }
or (++$self->{fail} and return $self->remaining([])->emit(error => $@));
}
return $self->remaining([])->emit(finish => @args) unless $self->{counter};
$self->ioloop->next_tick($self->begin) unless $self->{pending};
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Delay - Manage callbacks and control the flow of events
=head1 SYNOPSIS
use Mojo::IOLoop::Delay;
# Synchronize multiple events
my $delay = Mojo::IOLoop::Delay->new;
$delay->steps(sub { say 'BOOM!' });
for my $i (1 .. 10) {
my $end = $delay->begin;
Mojo::IOLoop->timer($i => sub {
say 10 - $i;
$end->();
});
}
$delay->wait;
# Sequentialize multiple events
Mojo::IOLoop::Delay->new->steps(
# First step (simple timer)
sub {
my $delay = shift;
Mojo::IOLoop->timer(2 => $delay->begin);
say 'Second step in 2 seconds.';
},
# Second step (concurrent timers)
sub {
my ($delay, @args) = @_;
Mojo::IOLoop->timer(1 => $delay->begin);
Mojo::IOLoop->timer(3 => $delay->begin);
say 'Third step in 3 seconds.';
},
# Third step (the end)
sub {
my ($delay, @args) = @_;
say 'And done after 5 seconds total.';
}
)->wait;
# Handle exceptions in all steps
Mojo::IOLoop::Delay->new->steps(
sub {
my $delay = shift;
die 'Intentional error';
},
sub {
my ($delay, @args) = @_;
say 'Never actually reached.';
}
)->catch(sub {
my ($delay, $err) = @_;
say "Something went wrong: $err";
})->wait;
=head1 DESCRIPTION
L<Mojo::IOLoop::Delay> manages callbacks and controls the flow of events for
L<Mojo::IOLoop>, which can help you avoid deep nested closures and memory
leaks that often result from continuation-passing style.
=head1 EVENTS
L<Mojo::IOLoop::Delay> inherits all events from L<Mojo::EventEmitter> and can
emit the following new ones.
=head2 error
$delay->on(error => sub {
my ($delay, $err) = @_;
...
});
Emitted if an exception gets thrown in one of the steps, breaking the chain,
fatal if unhandled.
=head2 finish
$delay->on(finish => sub {
my ($delay, @args) = @_;
...
});
Emitted once the active event counter reaches zero and there are no more
steps.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Delay> implements the following attributes.
=head2 ioloop
my $ioloop = $delay->ioloop;
$delay = $delay->ioloop(Mojo::IOLoop->new);
Event loop object to control, defaults to the global L<Mojo::IOLoop>
singleton.
=head1 METHODS
L<Mojo::IOLoop::Delay> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 begin
my $cb = $delay->begin;
my $cb = $delay->begin($offset);
my $cb = $delay->begin($offset, $len);
Increment active event counter, the returned callback can be used to decrement
the active event counter again. Arguments passed to the callback are spliced
and queued in the right order for the next step or L</"finish"> event and
L</"wait"> method, the argument offset defaults to C<1> with no default
length.
# Capture all arguments except for the first one (invocant)
my $delay = Mojo::IOLoop->delay(sub {
my ($delay, $err, $stream) = @_;
...
});
Mojo::IOLoop->client({port => 3000} => $delay->begin);
$delay->wait;
# Capture all arguments
my $delay = Mojo::IOLoop->delay(sub {
my ($delay, $loop, $err, $stream) = @_;
...
});
Mojo::IOLoop->client({port => 3000} => $delay->begin(0));
$delay->wait;
# Capture only the second argument
my $delay = Mojo::IOLoop->delay(sub {
my ($delay, $err) = @_;
...
});
Mojo::IOLoop->client({port => 3000} => $delay->begin(1, 1));
$delay->wait;
=head2 data
my $hash = $delay->data;
my $foo = $delay->data('foo');
$delay = $delay->data({foo => 'bar'});
$delay = $delay->data(foo => 'bar');
Data shared between all L</"steps">.
# Remove value
my $foo = delete $delay->data->{foo};
=head2 pass
$delay = $delay->pass;
$delay = $delay->pass(@args);
Increment active event counter and decrement it again right away to pass
values to the next step.
# Longer version
$delay->begin(0)->(@args);
=head2 remaining
my $remaining = $delay->remaining;
$delay = $delay->remaining([]);
Remaining L</"steps"> in chain, stored outside the object to protect from
circular references.
=head2 steps
$delay = $delay->steps(sub {...}, sub {...});
Sequentialize multiple events, every time the active event counter reaches
zero a callback will run, the first one automatically runs during the next
reactor tick unless it is delayed by incrementing the active event counter.
This chain will continue until there are no more callbacks, a callback does
not increment the active event counter or an exception gets thrown in a
callback.
=head2 wait
$delay->wait;
Start L</"ioloop"> and stop it again once an L</"error"> or L</"finish"> event
gets emitted, does nothing when L</"ioloop"> is already running.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_IOLOOP_DELAY
$fatpacked{"Mojo/IOLoop/Server.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_IOLOOP_SERVER';
package Mojo::IOLoop::Server;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use File::Basename 'dirname';
use File::Spec::Functions 'catfile';
use IO::Socket::IP;
use Mojo::IOLoop;
use Scalar::Util 'weaken';
use Socket qw(IPPROTO_TCP TCP_NODELAY);
# TLS support requires IO::Socket::SSL
use constant TLS => $ENV{MOJO_NO_TLS}
? 0
: eval 'use IO::Socket::SSL 1.84 (); 1';
use constant TLS_READ => TLS ? IO::Socket::SSL::SSL_WANT_READ() : 0;
use constant TLS_WRITE => TLS ? IO::Socket::SSL::SSL_WANT_WRITE() : 0;
# To regenerate the certificate run this command (18.04.2012)
# openssl req -new -x509 -keyout server.key -out server.crt -nodes -days 7300
my $CERT = catfile dirname(__FILE__), 'server.crt';
my $KEY = catfile dirname(__FILE__), 'server.key';
has multi_accept => 50;
has reactor => sub { Mojo::IOLoop->singleton->reactor };
sub DESTROY {
my $self = shift;
$ENV{MOJO_REUSE} =~ s/(?:^|\,)\Q$self->{reuse}\E// if $self->{reuse};
return unless my $reactor = $self->reactor;
$self->stop if $self->{handle};
$reactor->remove($_) for values %{$self->{handles}};
}
sub generate_port {
IO::Socket::IP->new(Listen => 5, LocalAddr => '127.0.0.1')->sockport;
}
sub handle { shift->{handle} }
sub listen {
my $self = shift;
my $args = ref $_[0] ? $_[0] : {@_};
# Look for reusable file descriptor
my $address = $args->{address} || '0.0.0.0';
my $port = $args->{port};
$ENV{MOJO_REUSE} ||= '';
my $fd;
$fd = $1 if $port && $ENV{MOJO_REUSE} =~ /(?:^|\,)\Q$address:$port\E:(\d+)/;
# Allow file descriptor inheritance
local $^F = 1000;
# Reuse file descriptor
my $handle;
if (defined $fd) {
$handle = IO::Socket::IP->new_from_fd($fd, 'r')
or croak "Can't open file descriptor $fd: $!";
}
# New socket
else {
my %options = (
Listen => $args->{backlog} // SOMAXCONN,
LocalAddr => $address,
ReuseAddr => 1,
ReusePort => $args->{reuse},
Type => SOCK_STREAM
);
$options{LocalPort} = $port if $port;
$options{LocalAddr} =~ s/[\[\]]//g;
$handle = IO::Socket::IP->new(%options)
or croak "Can't create listen socket: $@";
$fd = fileno $handle;
my $reuse = $self->{reuse} = join ':', $address, $handle->sockport, $fd;
$ENV{MOJO_REUSE} .= length $ENV{MOJO_REUSE} ? ",$reuse" : "$reuse";
}
$handle->blocking(0);
$self->{handle} = $handle;
return unless $args->{tls};
croak "IO::Socket::SSL 1.84 required for TLS support" unless TLS;
weaken $self;
my $tls = $self->{tls} = {
SSL_cert_file => $args->{tls_cert} || $CERT,
SSL_error_trap => sub {
return unless my $handle = delete $self->{handles}{shift()};
$self->reactor->remove($handle);
close $handle;
},
SSL_honor_cipher_order => 1,
SSL_key_file => $args->{tls_key} || $KEY,
SSL_startHandshake => 0,
SSL_verify_mode => $args->{tls_verify} // ($args->{tls_ca} ? 0x03 : 0x00)
};
$tls->{SSL_ca_file} = $args->{tls_ca}
if $args->{tls_ca} && -T $args->{tls_ca};
$tls->{SSL_cipher_list} = $args->{tls_ciphers} if $args->{tls_ciphers};
}
sub start {
my $self = shift;
weaken $self;
$self->reactor->io($self->{handle} => sub { $self->_accept });
}
sub stop { $_[0]->reactor->remove($_[0]{handle}) }
sub _accept {
my $self = shift;
# Greedy accept
for (1 .. $self->multi_accept) {
return unless my $handle = $self->{handle}->accept;
$handle->blocking(0);
# Disable Nagle's algorithm
setsockopt $handle, IPPROTO_TCP, TCP_NODELAY, 1;
# Start TLS handshake
$self->emit(accept => $handle) and next unless my $tls = $self->{tls};
$self->_handshake($self->{handles}{$handle} = $handle)
if $handle = IO::Socket::SSL->start_SSL($handle, %$tls, SSL_server => 1);
}
}
sub _handshake {
my ($self, $handle) = @_;
weaken $self;
$self->reactor->io($handle => sub { $self->_tls($handle) });
}
sub _tls {
my ($self, $handle) = @_;
# Accepted
if ($handle->accept_SSL) {
$self->reactor->remove($handle);
return $self->emit(accept => delete $self->{handles}{$handle});
}
# Switch between reading and writing
my $err = $IO::Socket::SSL::SSL_ERROR;
if ($err == TLS_READ) { $self->reactor->watch($handle, 1, 0) }
elsif ($err == TLS_WRITE) { $self->reactor->watch($handle, 1, 1) }
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Server - Non-blocking TCP server
=head1 SYNOPSIS
use Mojo::IOLoop::Server;
# Create listen socket
my $server = Mojo::IOLoop::Server->new;
$server->on(accept => sub {
my ($server, $handle) = @_;
...
});
$server->listen(port => 3000);
# Start and stop accepting connections
$server->start;
$server->stop;
# Start reactor if necessary
$server->reactor->start unless $server->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Server> accepts TCP connections for L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Server> inherits all events from L<Mojo::EventEmitter> and can
emit the following new ones.
=head2 accept
$server->on(accept => sub {
my ($server, $handle) = @_;
...
});
Emitted for each accepted connection.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Server> implements the following attributes.
=head2 multi_accept
my $multi = $server->multi_accept;
$server = $server->multi_accept(100);
Number of connections to accept at once, defaults to C<50>.
=head2 reactor
my $reactor = $server->reactor;
$server = $server->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the
global L<Mojo::IOLoop> singleton.
=head1 METHODS
L<Mojo::IOLoop::Server> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 generate_port
my $port = $server->generate_port;
Find a free TCP port, primarily used for tests.
=head2 handle
my $handle = $server->handle;
Get handle for server.
=head2 listen
$server->listen(port => 3000);
Create a new listen socket. Note that TLS support depends on
L<IO::Socket::SSL> (1.84+).
These options are currently available:
=over 2
=item address
address => '127.0.0.1'
Local address to listen on, defaults to all.
=item backlog
backlog => 128
Maximum backlog size, defaults to C<SOMAXCONN>.
=item port
port => 80
Port to listen on, defaults to a random port.
=item reuse
reuse => 1
Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket
option.
=item tls
tls => 1
Enable TLS.
=item tls_ca
tls_ca => '/etc/tls/ca.crt'
Path to TLS certificate authority file.
=item tls_cert
tls_cert => '/etc/tls/server.crt'
Path to the TLS cert file, defaults to a built-in test certificate.
=item tls_ciphers
tls_ciphers => 'AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH'
Cipher specification string.
=item tls_key
tls_key => '/etc/tls/server.key'
Path to the TLS key file, defaults to a built-in test key.
=item tls_verify
tls_verify => 0x00
TLS verification mode, defaults to C<0x03>.
=back
=head2 start
$server->start;
Start accepting connections.
=head2 stop
$server->stop;
Stop accepting connections.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_IOLOOP_SERVER
$fatpacked{"Mojo/IOLoop/Stream.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_IOLOOP_STREAM';
package Mojo::IOLoop::Stream;
use Mojo::Base 'Mojo::EventEmitter';
use Errno qw(EAGAIN ECONNRESET EINTR EPIPE EWOULDBLOCK);
use Mojo::IOLoop;
use Scalar::Util 'weaken';
has reactor => sub { Mojo::IOLoop->singleton->reactor };
sub DESTROY { shift->close }
sub close {
my $self = shift;
return unless my $reactor = $self->reactor;
return unless my $handle = delete $self->timeout(0)->{handle};
$reactor->remove($handle);
close $handle;
$self->emit('close');
}
sub close_gracefully {
my $self = shift;
return $self->{graceful} = 1 if $self->is_writing;
$self->close;
}
sub handle { shift->{handle} }
sub is_readable {
my $self = shift;
$self->_again;
return $self->{handle} && $self->reactor->is_readable($self->{handle});
}
sub is_writing {
my $self = shift;
return undef unless $self->{handle};
return !!length($self->{buffer}) || $self->has_subscribers('drain');
}
sub new { shift->SUPER::new(handle => shift, buffer => '', timeout => 15) }
sub start {
my $self = shift;
# Resume
my $reactor = $self->reactor;
return $reactor->watch($self->{handle}, 1, $self->is_writing)
if delete $self->{paused};
weaken $self;
my $cb = sub { pop() ? $self->_write : $self->_read };
$reactor->io($self->timeout($self->{timeout})->{handle} => $cb);
}
sub stop {
my $self = shift;
$self->reactor->watch($self->{handle}, 0, $self->is_writing)
unless $self->{paused}++;
}
sub steal_handle {
my $self = shift;
$self->reactor->remove($self->{handle});
return delete $self->{handle};
}
sub timeout {
my $self = shift;
return $self->{timeout} unless @_;
my $reactor = $self->reactor;
$reactor->remove(delete $self->{timer}) if $self->{timer};
return $self unless my $timeout = $self->{timeout} = shift;
weaken $self;
$self->{timer}
= $reactor->timer($timeout => sub { $self->emit('timeout')->close });
return $self;
}
sub write {
my ($self, $chunk, $cb) = @_;
$self->{buffer} .= $chunk;
if ($cb) { $self->once(drain => $cb) }
elsif (!length $self->{buffer}) { return $self }
$self->reactor->watch($self->{handle}, !$self->{paused}, 1)
if $self->{handle};
return $self;
}
sub _again { $_[0]->reactor->again($_[0]{timer}) if $_[0]{timer} }
sub _error {
my $self = shift;
# Retry
return if $! == EAGAIN || $! == EINTR || $! == EWOULDBLOCK;
# Closed
return $self->close if $! == ECONNRESET || $! == EPIPE;
# Error
$self->emit(error => $!)->close;
}
sub _read {
my $self = shift;
my $read = $self->{handle}->sysread(my $buffer, 131072, 0);
return $self->_error unless defined $read;
return $self->close if $read == 0;
$self->emit(read => $buffer)->_again;
}
sub _write {
my $self = shift;
my $handle = $self->{handle};
if (length $self->{buffer}) {
my $written = $handle->syswrite($self->{buffer});
return $self->_error unless defined $written;
$self->emit(write => substr($self->{buffer}, 0, $written, ''))->_again;
}
$self->emit('drain') if !length $self->{buffer};
return if $self->is_writing;
return $self->close if $self->{graceful};
$self->reactor->watch($handle, !$self->{paused}, 0) if $self->{handle};
}
1;
=encoding utf8
=head1 NAME
Mojo::IOLoop::Stream - Non-blocking I/O stream
=head1 SYNOPSIS
use Mojo::IOLoop::Stream;
# Create stream
my $stream = Mojo::IOLoop::Stream->new($handle);
$stream->on(read => sub {
my ($stream, $bytes) = @_;
...
});
$stream->on(close => sub {
my $stream = shift;
...
});
$stream->on(error => sub {
my ($stream, $err) = @_;
...
});
# Start and stop watching for new data
$stream->start;
$stream->stop;
# Start reactor if necessary
$stream->reactor->start unless $stream->reactor->is_running;
=head1 DESCRIPTION
L<Mojo::IOLoop::Stream> is a container for I/O streams used by
L<Mojo::IOLoop>.
=head1 EVENTS
L<Mojo::IOLoop::Stream> inherits all events from L<Mojo::EventEmitter> and can
emit the following new ones.
=head2 close
$stream->on(close => sub {
my $stream = shift;
...
});
Emitted if the stream gets closed.
=head2 drain
$stream->on(drain => sub {
my $stream = shift;
...
});
Emitted once all data has been written.
=head2 error
$stream->on(error => sub {
my ($stream, $err) = @_;
...
});
Emitted if an error occurs on the stream, fatal if unhandled.
=head2 read
$stream->on(read => sub {
my ($stream, $bytes) = @_;
...
});
Emitted if new data arrives on the stream.
=head2 timeout
$stream->on(timeout => sub {
my $stream = shift;
...
});
Emitted if the stream has been inactive for too long and will get closed
automatically.
=head2 write
$stream->on(write => sub {
my ($stream, $bytes) = @_;
...
});
Emitted if new data has been written to the stream.
=head1 ATTRIBUTES
L<Mojo::IOLoop::Stream> implements the following attributes.
=head2 reactor
my $reactor = $stream->reactor;
$stream = $stream->reactor(Mojo::Reactor::Poll->new);
Low-level event reactor, defaults to the C<reactor> attribute value of the
global L<Mojo::IOLoop> singleton.
=head1 METHODS
L<Mojo::IOLoop::Stream> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 close
$stream->close;
Close stream immediately.
=head2 close_gracefully
$stream->close_gracefully;
Close stream gracefully.
=head2 handle
my $handle = $stream->handle;
Get handle for stream.
=head2 is_readable
my $bool = $stream->is_readable;
Quick non-blocking check if stream is readable, useful for identifying tainted
sockets.
=head2 is_writing
my $bool = $stream->is_writing;
Check if stream is writing.
=head2 new
my $stream = Mojo::IOLoop::Stream->new($handle);
Construct a new L<Mojo::IOLoop::Stream> object.
=head2 start
$stream->start;
Start watching for new data on the stream.
=head2 stop
$stream->stop;
Stop watching for new data on the stream.
=head2 steal_handle
my $handle = $stream->steal_handle;
Steal handle from stream and prevent it from getting closed automatically.
=head2 timeout
my $timeout = $stream->timeout;
$stream = $stream->timeout(45);
Maximum amount of time in seconds stream can be inactive before getting closed
automatically, defaults to C<15>. Setting the value to C<0> will allow this
stream to be inactive indefinitely.
=head2 write
$stream = $stream->write($bytes);
$stream = $stream->write($bytes => sub {...});
Write data to stream, the optional drain callback will be invoked once all
data has been written.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_IOLOOP_STREAM
$fatpacked{"Mojo/JSON.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_JSON';
package Mojo::JSON;
use Mojo::Base -base;
use B;
use Carp 'croak';
use Exporter 'import';
use Mojo::Util 'deprecated';
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';
# Escaped special character map (with u2028 and u2029)
my %ESCAPE = (
'"' => '"',
'\\' => '\\',
'/' => '/',
'b' => "\x08",
'f' => "\x0c",
'n' => "\x0a",
'r' => "\x0d",
't' => "\x09",
'u2028' => "\x{2028}",
'u2029' => "\x{2029}"
);
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 = _catch(\my $value, pop));
return defined $err ? undef : $value;
}
sub decode_json {
my $err = _catch(\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}
sub from_json {
my $err = _catch(\my $value, shift, 1);
return defined $err ? croak $err : $value;
}
sub j {
return encode_json($_[0]) if ref $_[0] eq 'ARRAY' || ref $_[0] eq 'HASH';
return eval { _decode($_[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}
sub _catch {
my $valueref = shift;
eval { $$valueref = _decode(@_); 1 } ? return undef : chomp $@;
return $@;
}
sub _decode {
# Missing input
die "Missing or empty input\n" unless length(local $_ = shift);
# UTF-8
$_ = Mojo::Util::decode 'UTF-8', $_ unless shift;
die "Input is not UTF-8 encoded\n" unless defined;
# Value
my $value = _decode_value();
# Leftover data
_throw('Unexpected data') unless m/\G[\x20\x09\x0a\x0d]*\z/gc;
return $value;
}
sub _decode_array {
my @array;
until (m/\G[\x20\x09\x0a\x0d]*\]/gc) {
# Value
push @array, _decode_value();
# Separator
redo if m/\G[\x20\x09\x0a\x0d]*,/gc;
# End
last if m/\G[\x20\x09\x0a\x0d]*\]/gc;
# Invalid character
_throw('Expected comma or right square bracket while parsing array');
}
return \@array;
}
sub _decode_object {
my %hash;
until (m/\G[\x20\x09\x0a\x0d]*\}/gc) {
# Quote
m/\G[\x20\x09\x0a\x0d]*"/gc
or _throw('Expected string while parsing object');
# Key
my $key = _decode_string();
# Colon
m/\G[\x20\x09\x0a\x0d]*:/gc
or _throw('Expected colon while parsing object');
# Value
$hash{$key} = _decode_value();
# Separator
redo if m/\G[\x20\x09\x0a\x0d]*,/gc;
# End
last if m/\G[\x20\x09\x0a\x0d]*\}/gc;
# Invalid character
_throw('Expected comma or right curly bracket while parsing object');
}
return \%hash;
}
sub _decode_string {
my $pos = pos;
# Extract string with escaped characters
m!\G((?:(?:[^\x00-\x1f\\"]|\\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})){0,32766})*)!gc;
my $str = $1;
# Invalid character
unless (m/\G"/gc) {
_throw('Unexpected character or invalid escape while parsing string')
if m/\G[\x00-\x1f\\]/;
_throw('Unterminated string');
}
# Unescape popular characters
if (index($str, '\\u') < 0) {
$str =~ s!\\(["\\/bfnrt])!$ESCAPE{$1}!gs;
return $str;
}
# Unescape everything else
my $buffer = '';
while ($str =~ m/\G([^\\]*)\\(?:([^u])|u(.{4}))/gc) {
$buffer .= $1;
# Popular character
if ($2) { $buffer .= $ESCAPE{$2} }
# Escaped
else {
my $ord = hex $3;
# Surrogate pair
if (($ord & 0xf800) == 0xd800) {
# High surrogate
($ord & 0xfc00) == 0xd800
or pos($_) = $pos + pos($str), _throw('Missing high-surrogate');
# Low surrogate
$str =~ m/\G\\u([Dd][C-Fc-f]..)/gc
or pos($_) = $pos + pos($str), _throw('Missing low-surrogate');
$ord = 0x10000 + ($ord - 0xd800) * 0x400 + (hex($1) - 0xdc00);
}
# Character
$buffer .= pack 'U', $ord;
}
}
# The rest
return $buffer . substr $str, pos($str), length($str);
}
sub _decode_value {
# Leading whitespace
m/\G[\x20\x09\x0a\x0d]*/gc;
# String
return _decode_string() if m/\G"/gc;
# Object
return _decode_object() if m/\G\{/gc;
# Array
return _decode_array() if m/\G\[/gc;
# Number
return 0 + $1
if m/\G([-]?(?:0|[1-9][0-9]*)(?:\.[0-9]*)?(?:[eE][+-]?[0-9]+)?)/gc;
# True
return $TRUE if m/\Gtrue/gc;
# False
return $FALSE if m/\Gfalse/gc;
# Null
return undef if m/\Gnull/gc;
# Invalid character
_throw('Expected string, array, object, number, boolean or null');
}
sub _encode_array {
'[' . join(',', map { _encode_value($_) } @{$_[0]}) . ']';
}
sub _encode_object {
my $object = shift;
my @pairs = map { _encode_string($_) . ':' . _encode_value($object->{$_}) }
keys %$object;
return '{' . join(',', @pairs) . '}';
}
sub _encode_string {
my $str = shift;
$str =~ s!([\x00-\x1f\x{2028}\x{2029}\\"])!$REVERSE{$1}!gs;
return "\"$str\"";
}
sub _encode_value {
my $value = shift;
# Reference
if (my $ref = ref $value) {
# Object
return _encode_object($value) if $ref eq 'HASH';
# Array
return _encode_array($value) if $ref eq 'ARRAY';
# True or false
return $$value ? 'true' : 'false' if $ref eq 'SCALAR';
return $value ? 'true' : 'false' if $ref eq 'Mojo::JSON::_Bool';
# Blessed reference with TO_JSON method
if (blessed $value && (my $sub = $value->can('TO_JSON'))) {
return _encode_value($value->$sub);
}
}
# Null
return 'null' unless defined $value;
# Number
return $value
if B::svref_2object(\$value)->FLAGS & (B::SVp_IOK | B::SVp_NOK)
&& 0 + $value eq $value
&& $value * 0 == 0;
# String
return _encode_string($value);
}
sub _throw {
# Leading whitespace
m/\G[\x20\x09\x0a\x0d]*/gc;
# Context
my $context = 'Malformed JSON: ' . shift;
if (m/\G\z/gc) { $context .= ' before end of data' }
else {
my @lines = split "\n", substr($_, 0, pos);
$context .= ' at line ' . @lines . ', offset ' . length(pop @lines || '');
}
die "$context\n";
}
# Emulate boolean type
package Mojo::JSON::_Bool;
use overload '0+' => sub { ${$_[0]} }, '""' => sub { ${$_[0]} }, fallback => 1;
1;
=encoding utf8
=head1 NAME
Mojo::JSON - Minimalistic JSON
=head1 SYNOPSIS
use Mojo::JSON qw(decode_json encode_json);
my $bytes = encode_json {foo => [1, 2], bar => 'hello!', baz => \1};
my $hash = decode_json $bytes;
=head1 DESCRIPTION
L<Mojo::JSON> is a minimalistic and possibly the fastest pure-Perl
implementation of L<RFC 7159|http://tools.ietf.org/html/rfc7159>.
It supports normal Perl data types like scalar, array reference, hash
reference and will try to call the C<TO_JSON> method on blessed references, or
stringify them if it doesn't exist. Differentiating between strings and
numbers in Perl is hard, depending on how it has been used, a scalar can be
both at the same time. The string value has a higher precedence unless both
representations are equivalent.
[1, -2, 3] -> [1, -2, 3]
{"foo": "bar"} -> {foo => 'bar'}
Literal names will be translated to and from L<Mojo::JSON> constants or a
similar native Perl value.
true -> Mojo::JSON->true
false -> Mojo::JSON->false
null -> undef
In addition scalar references will be used to generate booleans, based on if
their values are true or false.
\1 -> true
\0 -> false
The two Unicode whitespace characters C<u2028> and C<u2029> will always be
escaped to make JSONP easier.
=head1 FUNCTIONS
L<Mojo::JSON> implements the following functions, which can be imported
individually.
=head2 decode_json
my $value = decode_json $bytes;
Decode JSON to Perl value and die if decoding fails.
=head2 encode_json
my $bytes = encode_json {i => '♥ mojolicious'};
Encode Perl value to JSON.
=head2 false
my $false = false;
False value, used because Perl has no native equivalent.
=head2 from_json
my $value = from_json $chars;
Decode JSON text that is not C<UTF-8> encoded to Perl value and die if
decoding fails.
=head2 j
my $bytes = j [1, 2, 3];
my $bytes = j {i => '♥ mojolicious'};
my $value = j $bytes;
Encode Perl data structure (which may only be an array reference or hash
reference) or decode JSON, an C<undef> return value indicates a bare C<null>
or that decoding failed.
=head2 to_json
my $chars = to_json {i => '♥ mojolicious'};
Encode Perl value to JSON text without C<UTF-8> encoding it.
=head2 true
my $true = true;
True value, used because Perl has no native equivalent.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_JSON
$fatpacked{"Mojo/JSON/Pointer.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_JSON_POINTER';
package Mojo::JSON::Pointer;
use Mojo::Base -base;
use Mojo::Util 'deprecated';
has 'data';
sub contains { shift->_pointer(1, @_) }
sub get { shift->_pointer(0, @_) }
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];
return $data unless $pointer =~ s!^/!!;
for my $p ($pointer eq '' ? ($pointer) : (split '/', $pointer)) {
$p =~ s/~0/~/g;
$p =~ s!~1!/!g;
# Hash
if (ref $data eq 'HASH' && exists $data->{$p}) { $data = $data->{$p} }
# Array
elsif (ref $data eq 'ARRAY' && $p =~ /^\d+$/ && @$data > $p) {
$data = $data->[$p];
}
# Nothing
else { return undef }
}
return $contains ? 1 : $data;
}
1;
=encoding utf8
=head1 NAME
Mojo::JSON::Pointer - JSON Pointers
=head1 SYNOPSIS
use Mojo::JSON::Pointer;
my $pointer = Mojo::JSON::Pointer->new({foo => [23, 'bar']});
say $pointer->get('/foo/1');
say 'Contains "/foo".' if $pointer->contains('/foo');
=head1 DESCRIPTION
L<Mojo::JSON::Pointer> is a relaxed implementation of
L<RFC 6901|http://tools.ietf.org/html/rfc6901>.
=head1 ATTRIBUTES
L<Mojo::JSON::Pointer> implements the following attributes.
=head2 data
my $data = $pointer->data;
$pointer = $pointer->data({foo => 'bar'});
Data structure to be processed.
=head1 METHODS
L<Mojo::JSON::Pointer> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 contains
my $bool = $pointer->contains('/foo/1');
Check if L</"data"> contains a value that can be identified with the given
JSON Pointer.
# True
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->contains('/♥');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/foo');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/baz/1');
# False
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->contains('/☃');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/bar');
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5]})->contains('/baz/9');
=head2 get
my $value = $pointer->get('/foo/bar');
Extract value from L</"data"> identified by the given JSON Pointer.
# "mojolicious"
Mojo::JSON::Pointer->new({'♥' => 'mojolicious'})->get('/♥');
# "bar"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/foo');
# "4"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/baz/0');
# "6"
Mojo::JSON::Pointer->new({foo => 'bar', baz => [4, 5, 6]})->get('/baz/2');
=head2 new
my $pointer = Mojo::JSON::Pointer->new;
my $pointer = Mojo::JSON::Pointer->new({foo => 'bar'});
Build new L<Mojo::JSON::Pointer> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_JSON_POINTER
$fatpacked{"Mojo/Loader.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_LOADER';
package Mojo::Loader;
use Mojo::Base -base;
use File::Basename 'fileparse';
use File::Spec::Functions qw(catdir catfile splitdir);
use Mojo::Exception;
use Mojo::Util qw(b64_decode class_to_path);
my (%BIN, %CACHE);
sub data { $_[1] ? $_[2] ? _all($_[1])->{$_[2]} : _all($_[1]) : undef }
sub is_binary { keys %{_all($_[1])} ? !!$BIN{$_[1]}{$_[2]} : undef }
sub load {
my ($self, $module) = @_;
# Check module name
return 1 if !$module || $module !~ /^\w(?:[\w:']*\w)?$/;
# Load
return undef if $module->can('new') || eval "require $module; 1";
# Exists
return 1 if $@ =~ /^Can't locate \Q@{[class_to_path $module]}\E in \@INC/;
# Real error
return Mojo::Exception->new($@);
}
sub search {
my ($self, $ns) = @_;
my %modules;
for my $directory (@INC) {
next unless -d (my $path = catdir $directory, split(/::|'/, $ns));
# List "*.pm" files in directory
opendir(my $dir, $path);
for my $file (grep /\.pm$/, readdir $dir) {
next if -d catfile splitdir($path), $file;
$modules{"${ns}::" . fileparse $file, qr/\.pm/}++;
}
}
return [keys %modules];
}
sub _all {
my $class = shift;
return $CACHE{$class} if $CACHE{$class};
my $handle = do { no strict 'refs'; \*{"${class}::DATA"} };
return {} unless fileno $handle;
seek $handle, 0, 0;
my $data = join '', <$handle>;
# Ignore everything before __DATA__ (some versions seek to start of file)
$data =~ s/^.*\n__DATA__\r?\n/\n/s;
# Ignore everything after __END__
$data =~ s/\n__END__\r?\n.*$/\n/s;
# Split files
(undef, my @files) = split /^@@\s*(.+?)\s*\r?\n/m, $data;
# Find data
my $all = $CACHE{$class} = {};
while (@files) {
my ($name, $data) = splice @files, 0, 2;
$all->{$name} = $name =~ s/\s*\(\s*base64\s*\)$//
&& ++$BIN{$class}{$name} ? b64_decode($data) : $data;
}
return $all;
}
1;
=encoding utf8
=head1 NAME
Mojo::Loader - Loader
=head1 SYNOPSIS
use Mojo::Loader;
# Find modules in a namespace
my $loader = Mojo::Loader->new;
for my $module (@{$loader->search('Some::Namespace')}) {
# Load them safely
my $e = $loader->load($module);
warn qq{Loading "$module" failed: $e} and next if ref $e;
# And extract files from the DATA section
say $loader->data($module, 'some_file.txt');
}
=head1 DESCRIPTION
L<Mojo::Loader> is a class loader and plugin framework.
=head1 METHODS
L<Mojo::Loader> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 data
my $all = $loader->data('Foo::Bar');
my $index = $loader->data('Foo::Bar', 'index.html');
Extract embedded file from the C<DATA> section of a class, all files will be
cached once they have been accessed for the first time.
say for keys %{$loader->data('Foo::Bar')};
=head2 is_binary
my $bool = $loader->is_binary('Foo::Bar', 'test.png');
Check if embedded file from the C<DATA> section of a class was Base64 encoded.
=head2 load
my $e = $loader->load('Foo::Bar');
Load a class and catch exceptions. Note that classes are checked for a C<new>
method to see if they are already loaded.
if (my $e = $loader->load('Foo::Bar')) {
die ref $e ? "Exception: $e" : 'Not found!';
}
=head2 search
my $modules = $loader->search('MyApp::Namespace');
Search for modules in a namespace non-recursively.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_LOADER
$fatpacked{"Mojo/Log.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_LOG';
package Mojo::Log;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Fcntl ':flock';
use Mojo::Util 'encode';
has format => sub { \&_format };
has handle => sub {
# STDERR
return \*STDERR unless my $path = shift->path;
# File
croak qq{Can't open log file "$path": $!} unless open my $file, '>>', $path;
return $file;
};
has history => sub { [] };
has level => 'debug';
has max_history_size => 10;
has 'path';
# Supported log level
my $LEVEL = {debug => 1, info => 2, warn => 3, error => 4, fatal => 5};
sub append {
my ($self, $msg) = @_;
return unless my $handle = $self->handle;
flock $handle, LOCK_EX;
$handle->print(encode('UTF-8', $msg)) or croak "Can't write to log: $!";
flock $handle, LOCK_UN;
}
sub debug { shift->log(debug => @_) }
sub error { shift->log(error => @_) }
sub fatal { shift->log(fatal => @_) }
sub info { shift->log(info => @_) }
sub is_debug { shift->is_level('debug') }
sub is_error { shift->is_level('error') }
sub is_fatal { shift->is_level('fatal') }
sub is_info { shift->is_level('info') }
sub is_level {
$LEVEL->{lc pop} >= $LEVEL->{$ENV{MOJO_LOG_LEVEL} || shift->level};
}
sub is_warn { shift->is_level('warn') }
sub log { shift->emit('message', lc shift, @_) }
sub new {
my $self = shift->SUPER::new(@_);
$self->on(message => \&_message);
return $self;
}
sub warn { shift->log(warn => @_) }
sub _format {
'[' . localtime(shift) . '] [' . shift() . '] ' . join("\n", @_, '');
}
sub _message {
my ($self, $level) = (shift, shift);
return unless $self->is_level($level);
my $max = $self->max_history_size;
my $history = $self->history;
push @$history, my $msg = [time, $level, @_];
shift @$history while @$history > $max;
$self->append($self->format->(@$msg));
}
1;
=encoding utf8
=head1 NAME
Mojo::Log - Simple logger
=head1 SYNOPSIS
use Mojo::Log;
# Log to STDERR
my $log = Mojo::Log->new;
# Customize log file location and minimum log level
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!');
=head1 DESCRIPTION
L<Mojo::Log> is a simple logger for L<Mojo> projects.
=head1 EVENTS
L<Mojo::Log> inherits all events from L<Mojo::EventEmitter> and can emit the
following new ones.
=head2 message
$log->on(message => sub {
my ($log, $level, @lines) = @_;
...
});
Emitted when a new message gets logged.
$log->unsubscribe('message');
$log->on(message => sub {
my ($log, $level, @lines) = @_;
say "$level: ", @lines;
});
=head1 ATTRIBUTES
L<Mojo::Log> implements the following attributes.
=head2 format
my $cb = $log->format;
$log = $log->format(sub {...});
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";
});
=head2 handle
my $handle = $log->handle;
$log = $log->handle(IO::Handle->new);
Log filehandle used by default L</"message"> event, defaults to opening
L</"path"> or C<STDERR>.
=head2 history
my $history = $log->history;
$log = $log->history([[time, 'debug', 'That went wrong.']]);
The last few logged messages.
=head2 level
my $level = $log->level;
$log = $log->level('debug');
Active log level, defaults to C<debug>. Available log levels are C<debug>,
C<info>, C<warn>, C<error> and C<fatal>, in that order. Note that the
C<MOJO_LOG_LEVEL> environment variable can override this value.
=head2 max_history_size
my $size = $log->max_history_size;
$log = $log->max_history_size(5);
Maximum number of logged messages to store in L</"history">, defaults to
C<10>.
=head2 path
my $path = $log->path
$log = $log->path('/var/log/mojo.log');
Log file path used by L</"handle">.
=head1 METHODS
L<Mojo::Log> inherits all methods from L<Mojo::EventEmitter> and implements
the following new ones.
=head2 append
$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 debug message.
=head2 error
$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 fatal message.
=head2 info
$log = $log->info('You are bad, but you prolly know already.');
$log = $log->info('Ok', 'then!');
Log info message.
=head2 is_level
my $bool = $log->is_level('debug');
Check log level.
=head2 is_debug
my $bool = $log->is_debug;
Check for debug log level.
=head2 is_error
my $bool = $log->is_error;
Check for error log level.
=head2 is_fatal
my $bool = $log->is_fatal;
Check for fatal log level.
=head2 is_info
my $bool = $log->is_info;
Check for info log level.
=head2 is_warn
my $bool = $log->is_warn;
Check for warn log level.
=head2 log
$log = $log->log(debug => 'This should work.');
$log = $log->log(debug => 'This', 'too!');
Emit L</"message"> event.
=head2 new
my $log = Mojo::Log->new;
Construct a new L<Mojo::Log> object and subscribe to L</"message"> event with
default logger.
=head2 warn
$log = $log->warn('Dont do that Dave...');
$log = $log->warn('No', 'really!');
Log warn message.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_LOG
$fatpacked{"Mojo/Message.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_MESSAGE';
package Mojo::Message;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Mojo::Asset::Memory;
use Mojo::Content::Single;
use Mojo::DOM;
use Mojo::JSON 'j';
use Mojo::JSON::Pointer;
use Mojo::Parameters;
use Mojo::Upload;
use Mojo::Util qw(decode split_header);
has content => sub { Mojo::Content::Single->new };
has default_charset => 'UTF-8';
has max_line_size => sub { $ENV{MOJO_MAX_LINE_SIZE} || 10240 };
has max_message_size => sub { $ENV{MOJO_MAX_MESSAGE_SIZE} // 10485760 };
has version => '1.1';
sub body {
my $self = shift;
# Downgrade multipart content
my $content = $self->content;
$content = $self->content(Mojo::Content::Single->new)->content
if $content->is_multipart;
# Get
return $content->asset->slurp unless @_;
# Set
$content->asset(Mojo::Asset::Memory->new->add_chunk(@_));
return $self;
}
sub body_params {
my $self = shift;
return $self->{body_params} if $self->{body_params};
my $params = $self->{body_params} = Mojo::Parameters->new;
$params->charset($self->content->charset || $self->default_charset);
# "application/x-www-form-urlencoded"
my $type = $self->headers->content_type // '';
if ($type =~ m!application/x-www-form-urlencoded!i) {
$params->parse($self->content->asset->slurp);
}
# "multipart/form-data"
elsif ($type =~ m!multipart/form-data!i) {
$params->append(@$_[0, 1]) for @{$self->_parse_formdata};
}
return $params;
}
sub body_size { shift->content->body_size }
sub build_body { shift->_build('get_body_chunk') }
sub build_headers { shift->_build('get_header_chunk') }
sub build_start_line { shift->_build('get_start_line_chunk') }
sub cookie { shift->_cache('cookie', 0, @_) }
sub cookies { croak 'Method "cookies" not implemented by subclass' }
sub dom {
my $self = shift;
return undef if $self->content->is_multipart;
my $dom = $self->{dom} ||= Mojo::DOM->new($self->text);
return @_ ? $dom->find(@_) : $dom;
}
sub error {
my $self = shift;
return $self->{error} unless @_;
$self->{error} = shift;
return $self->finish;
}
sub every_cookie { shift->_cache('cookie', 1, @_) }
sub every_upload { shift->_cache('upload', 1, @_) }
sub extract_start_line {
croak 'Method "extract_start_line" not implemented by subclass';
}
sub finish {
my $self = shift;
$self->{state} = 'finished';
return $self->{finished}++ ? $self : $self->emit('finish');
}
sub fix_headers {
my $self = shift;
return $self if $self->{fix}++;
# Content-Length or Connection (unless chunked transfer encoding is used)
my $content = $self->content;
my $headers = $content->headers;
return $self if $content->is_chunked || $headers->content_length;
if ($content->is_dynamic) { $headers->connection('close') }
else { $headers->content_length($self->body_size) }
return $self;
}
sub get_body_chunk {
my ($self, $offset) = @_;
$self->emit('progress', 'body', $offset);
my $chunk = $self->content->get_body_chunk($offset);
return $chunk if !defined $chunk || length $chunk;
$self->finish;
return $chunk;
}
sub get_header_chunk {
my ($self, $offset) = @_;
$self->emit('progress', 'headers', $offset);
return $self->fix_headers->content->get_header_chunk($offset);
}
sub get_start_line_chunk {
croak 'Method "get_start_line_chunk" not implemented by subclass';
}
sub header_size { shift->fix_headers->content->header_size }
sub headers { shift->content->headers }
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_limit_exceeded { !!shift->{limit} }
sub json {
my ($self, $pointer) = @_;
return undef if $self->content->is_multipart;
my $data = $self->{json} //= j($self->body);
return $pointer ? Mojo::JSON::Pointer->new($data)->get($pointer) : $data;
}
sub parse {
my ($self, $chunk) = @_;
# Check message size
my $max = $self->max_message_size;
return $self->_limit('Maximum message size exceeded', 413)
if $max && ($self->{raw_size} += length($chunk //= '')) > $max;
$self->{buffer} .= $chunk;
# Start line
unless ($self->{state}) {
# Check line size
my $len = index $self->{buffer}, "\x0a";
$len = length $self->{buffer} if $len < 0;
return $self->_limit('Maximum line size exceeded', 431)
if $len > $self->max_line_size;
$self->{state} = 'content' if $self->extract_start_line(\$self->{buffer});
}
# Content
my $state = $self->{state} // '';
$self->content($self->content->parse(delete $self->{buffer}))
if $state eq 'content' || $state eq 'finished';
# Check line size
return $self->_limit('Maximum line size exceeded', 431)
if $self->headers->is_limit_exceeded;
# Check buffer size
return $self->error(
{message => 'Maximum buffer size exceeded', advice => 400})
if $self->content->is_limit_exceeded;
return $self->emit('progress')->content->is_finished ? $self->finish : $self;
}
sub start_line_size { length shift->build_start_line }
sub text {
my $self = shift;
my $body = $self->body;
my $charset = $self->content->charset;
return $charset ? decode($charset, $body) // $body : $body;
}
sub to_string {
my $self = shift;
return $self->build_start_line . $self->build_headers . $self->build_body;
}
sub upload { shift->_cache('upload', 0, @_) }
sub uploads {
my $self = shift;
my @uploads;
for my $data (@{$self->_parse_formdata(1)}) {
my $upload = Mojo::Upload->new(
name => $data->[0],
filename => $data->[2],
asset => $data->[1]->asset,
headers => $data->[1]->headers
);
push @uploads, $upload;
}
return \@uploads;
}
sub _build {
my ($self, $method) = @_;
my $buffer = '';
my $offset = 0;
while (1) {
# No chunk yet, try again
next unless defined(my $chunk = $self->$method($offset));
# End of part
last unless my $len = length $chunk;
$offset += $len;
$buffer .= $chunk;
}
return $buffer;
}
sub _cache {
my ($self, $method, $all, $name) = @_;
# Multiple names
return map { $self->$method($_) } @$name if ref $name eq 'ARRAY';
# Cache objects by name
$method .= 's';
unless ($self->{$method}) {
$self->{$method} = {};
push @{$self->{$method}{$_->name}}, $_ for @{$self->$method};
}
my $objects = $self->{$method}{$name} || [];
return $all ? $objects : $objects->[-1];
}
sub _limit {
my ($self, $msg, $code) = @_;
$self->{limit} = 1;
return $self->error({message => $msg, advice => $code});
}
sub _parse_formdata {
my ($self, $upload) = @_;
my @formdata;
my $content = $self->content;
return \@formdata unless $content->is_multipart;
my $charset = $content->charset || $self->default_charset;
# Check all parts recursively
my @parts = ($content);
while (my $part = shift @parts) {
if ($part->is_multipart) {
unshift @parts, @{$part->parts};
next;
}
next unless my $disposition = $part->headers->content_disposition;
my ($filename) = $disposition =~ /[; ]filename="((?:\\"|[^"])*)"/;
next if $upload && !defined $filename || !$upload && defined $filename;
my ($name) = $disposition =~ /[; ]name="((?:\\"|[^;"])*)"/;
$part = $part->asset->slurp unless $upload;
if ($charset) {
$name = decode($charset, $name) // $name if $name;
$filename = decode($charset, $filename) // $filename if $filename;
$part = decode($charset, $part) // $part unless $upload;
}
push @formdata, [$name, $part, $filename];
}
return \@formdata;
}
1;
=encoding utf8
=head1 NAME
Mojo::Message - HTTP message base class
=head1 SYNOPSIS
package Mojo::Message::MyMessage;
use Mojo::Base 'Mojo::Message';
sub cookies {...}
sub extract_start_line {...}
sub get_start_line_chunk {...}
=head1 DESCRIPTION
L<Mojo::Message> is an abstract base class for HTTP messages based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230>,
L<RFC 7231|http://tools.ietf.org/html/rfc7231> and
L<RFC 2388|http://tools.ietf.org/html/rfc2388>.
=head1 EVENTS
L<Mojo::Message> inherits all events from L<Mojo::EventEmitter> and can emit
the following new ones.
=head2 finish
$msg->on(finish => sub {
my $msg = shift;
...
});
Emitted after message building or parsing is finished.
my $before = time;
$msg->on(finish => sub {
my $msg = shift;
$msg->headers->header('X-Parser-Time' => time - $before);
});
=head2 progress
$msg->on(progress => sub {
my $msg = shift;
...
});
Emitted when message building or parsing makes progress.
# Building
$msg->on(progress => sub {
my ($msg, $state, $offset) = @_;
say qq{Building "$state" at offset $offset};
});
# Parsing
$msg->on(progress => sub {
my $msg = shift;
return unless my $len = $msg->headers->content_length;
my $size = $msg->content->progress;
say 'Progress: ', $size == $len ? 100 : int($size / ($len / 100)), '%';
});
=head1 ATTRIBUTES
L<Mojo::Message> implements the following attributes.
=head2 content
my $msg = $msg->content;
$msg = $msg->content(Mojo::Content::Single->new);
Message content, defaults to a L<Mojo::Content::Single> object.
=head2 default_charset
my $charset = $msg->default_charset;
$msg = $msg->default_charset('UTF-8');
Default charset used for form-data parsing, defaults to C<UTF-8>.
=head2 max_line_size
my $size = $msg->max_line_size;
$msg = $msg->max_line_size(1024);
Maximum start line size in bytes, defaults to the value of the
C<MOJO_MAX_LINE_SIZE> environment variable or C<10240> (10KB).
=head2 max_message_size
my $size = $msg->max_message_size;
$msg = $msg->max_message_size(1024);
Maximum message size in bytes, defaults to the value of the
C<MOJO_MAX_MESSAGE_SIZE> environment variable or C<10485760> (10MB). Setting
the value to C<0> will allow messages of indefinite size. Note that increasing
this value can also drastically increase memory usage, should you for example
attempt to parse an excessively large message body with the L</"body_params">,
L</"dom"> or L</"json"> methods.
=head2 version
my $version = $msg->version;
$msg = $msg->version('1.1');
HTTP version of message, defaults to C<1.1>.
=head1 METHODS
L<Mojo::Message> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 body
my $bytes = $msg->body;
$msg = $msg->body('Hello!');
Slurp or replace L</"content">, L<Mojo::Content::MultiPart> will be
automatically downgraded to L<Mojo::Content::Single>.
=head2 body_params
my $params = $msg->body_params;
C<POST> parameters extracted from C<application/x-www-form-urlencoded> or
C<multipart/form-data> message body, usually a L<Mojo::Parameters> object.
Note that this method caches all data, so it should not be called before the
entire message body has been received. Parts of the message body need to be
loaded into memory to parse C<POST> parameters, so you have to make sure it is
not excessively large, there's a 10MB limit by default.
# Get POST parameter value
say $msg->body_params->param('foo');
=head2 body_size
my $size = $msg->body_size;
Content size in bytes.
=head2 build_body
my $bytes = $msg->build_body;
Render whole body.
=head2 build_headers
my $bytes = $msg->build_headers;
Render all headers.
=head2 build_start_line
my $bytes = $msg->build_start_line;
Render start line.
=head2 cookie
my $cookie = $msg->cookie('foo');
my ($foo, $bar) = $msg->cookie(['foo', 'bar']);
Access message cookies, usually L<Mojo::Cookie::Request> or
L<Mojo::Cookie::Response> objects. If there are multiple cookies sharing the
same name, and you want to access more than just the last one, you can use
L</"every_cookie">. Note that this method caches all data, so it should not be
called before all headers have been received.
# Get cookie value
say $msg->cookie('foo')->value;
=head2 cookies
my $cookies = $msg->cookies;
Access message cookies. Meant to be overloaded in a subclass.
=head2 dom
my $dom = $msg->dom;
my $collection = $msg->dom('a[href]');
Turns message body into a L<Mojo::DOM> object and takes an optional selector
to call the method L<Mojo::DOM/"find"> on it right away, which returns a
L<Mojo::Collection> object. Note that this method caches all data, so it
should not be called before the entire message body has been received. The
whole message body needs to be loaded into memory to parse it, so you have to
make sure it is not excessively large, there's a 10MB limit by default.
# Perform "find" right away
say $msg->dom('h1, h2, h3')->map('text')->join("\n");
# Use everything else Mojo::DOM has to offer
say $msg->dom->at('title')->text;
say $msg->dom->at('body')->children->map('type')->uniq->join("\n");
=head2 error
my $err = $msg->error;
$msg = $msg->error({message => 'Parser error', advice => 500});
Get or set message error, an C<undef> return value indicates that there is no
error.
=head2 every_cookie
my $cookies = $msg->every_cookie('foo');
Similar to L</"cookie">, but returns all message cookies sharing the same name
as an array reference.
# Get first cookie value
say $msg->every_cookie('foo')->[0]->value;
=head2 every_upload
my $uploads = $msg->every_upload('foo');
Similar to L</"upload">, but returns all file uploads sharing the same name as
an array reference.
# Get content of first uploaded file
say $msg->every_upload('foo')->[0]->asset->slurp;
=head2 extract_start_line
my $bool = $msg->extract_start_line(\$str);
Extract start line from string. Meant to be overloaded in a subclass.
=head2 finish
$msg = $msg->finish;
Finish message parser/generator.
=head2 fix_headers
$msg = $msg->fix_headers;
Make sure message has all required headers.
=head2 get_body_chunk
my $bytes = $msg->get_body_chunk($offset);
Get a chunk of body data starting from a specific position.
=head2 get_header_chunk
my $bytes = $msg->get_header_chunk($offset);
Get a chunk of header data, starting from a specific position.
=head2 get_start_line_chunk
my $bytes = $msg->get_start_line_chunk($offset);
Get a chunk of start line data starting from a specific position. Meant to be
overloaded in a subclass.
=head2 header_size
my $size = $msg->header_size;
Size of headers in bytes.
=head2 headers
my $headers = $msg->headers;
Message headers, usually a L<Mojo::Headers> object.
=head2 is_finished
my $bool = $msg->is_finished;
Check if message parser/generator is finished.
=head2 is_limit_exceeded
my $bool = $msg->is_limit_exceeded;
Check if message has exceeded L</"max_line_size"> or L</"max_message_size">.
=head2 json
my $value = $msg->json;
my $value = $msg->json('/foo/bar');
Decode JSON message body directly using L<Mojo::JSON> if possible, an C<undef>
return value indicates a bare C<null> or that decoding failed. An optional
JSON Pointer can be used to extract a specific value with
L<Mojo::JSON::Pointer>. Note that this method caches all data, so it should
not be called before the entire message body has been received. The whole
message body needs to be loaded into memory to parse it, so you have to make
sure it is not excessively large, there's a 10MB limit by default.
# Extract JSON values
say $msg->json->{foo}{bar}[23];
say $msg->json('/foo/bar/23');
=head2 parse
$msg = $msg->parse('HTTP/1.1 200 OK...');
Parse message chunk.
=head2 start_line_size
my $size = $msg->start_line_size;
Size of the start line in bytes.
=head2 text
my $str = $msg->text;
Retrieve L</"body"> and try to decode it if a charset could be extracted with
L<Mojo::Content/"charset">.
=head2 to_string
my $str = $msg->to_string;
Render whole message.
=head2 upload
my $upload = $msg->upload('foo');
my ($foo, $bar) = $msg->upload(['foo', 'bar']);
Access C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects.
If there are multiple uploads sharing the same name, and you want to access
more than just the last one, you can use L</"every_upload">. Note that this
method caches all data, so it should not be called before the entire message
body has been received.
# Get content of uploaded file
say $msg->upload('foo')->asset->slurp;
=head2 uploads
my $uploads = $msg->uploads;
All C<multipart/form-data> file uploads, usually L<Mojo::Upload> objects.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_MESSAGE
$fatpacked{"Mojo/Message/Request.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_MESSAGE_REQUEST';
package Mojo::Message::Request;
use Mojo::Base 'Mojo::Message';
use Mojo::Cookie::Request;
use Mojo::Util qw(b64_encode b64_decode);
use Mojo::URL;
has env => sub { {} };
has method => 'GET';
has url => sub { Mojo::URL->new };
has 'reverse_proxy';
my $START_LINE_RE = qr/
^([a-zA-Z]+) # Method
\s+([0-9a-zA-Z!#\$\%&'()*+,\-.\/:;=?\@[\\\]^_`\{|\}~]+) # URL
\s+HTTP\/(\d\.\d)$ # Version
/x;
sub clone {
my $self = shift;
# Dynamic requests cannot be cloned
return undef unless my $content = $self->content->clone;
my $clone = $self->new(
content => $content,
method => $self->method,
url => $self->url->clone,
version => $self->version
);
$clone->{proxy} = $self->{proxy}->clone if $self->{proxy};
return $clone;
}
sub cookies {
my $self = shift;
# Parse cookies
my $headers = $self->headers;
return [map { @{Mojo::Cookie::Request->parse($_)} } $headers->cookie]
unless @_;
# Add cookies
my @cookies = $headers->cookie || ();
for my $cookie (@_) {
$cookie = Mojo::Cookie::Request->new($cookie) if ref $cookie eq 'HASH';
push @cookies, $cookie;
}
$headers->cookie(join('; ', @cookies));
return $self;
}
sub every_param { shift->params->every_param(@_) }
sub extract_start_line {
my ($self, $bufref) = @_;
# Ignore any leading empty lines
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})
unless $1 =~ $START_LINE_RE;
my $url = $self->method($1)->version($3)->url;
return !!($1 eq 'CONNECT' ? $url->authority($2) : $url->parse($2));
}
sub fix_headers {
my $self = shift;
$self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
# Host
my $url = $self->url;
my $headers = $self->headers;
$headers->host($url->host_port) unless $headers->host;
# Basic authentication
if ((my $info = $url->userinfo) && !$headers->authorization) {
$headers->authorization('Basic ' . b64_encode($info, ''));
}
# Basic proxy authentication
return $self unless my $proxy = $self->proxy;
return $self unless my $info = $proxy->userinfo;
$headers->proxy_authorization('Basic ' . b64_encode($info, ''))
unless $headers->proxy_authorization;
return $self;
}
sub get_start_line_chunk {
my ($self, $offset) = @_;
unless (defined $self->{start_buffer}) {
# Path
my $url = $self->url;
my $path = $url->path_query;
$path = "/$path" unless $path =~ m!^/!;
# CONNECT
my $method = uc $self->method;
if ($method eq 'CONNECT') {
my $port = $url->port || ($url->protocol eq 'https' ? '443' : '80');
$path = $url->ihost . ":$port";
}
# Proxy
elsif ($self->proxy && $url->protocol ne 'https') {
$path = $url->clone->userinfo(undef) unless $self->is_handshake;
}
$self->{start_buffer} = "$method $path HTTP/@{[$self->version]}\x0d\x0a";
}
$self->emit(progress => 'start_line', $offset);
return substr $self->{start_buffer}, $offset, 131072;
}
sub is_handshake { lc($_[0]->headers->upgrade // '') eq 'websocket' }
sub is_secure {
my $url = shift->url;
return ($url->protocol || $url->base->protocol) eq 'https';
}
sub is_xhr {
(shift->headers->header('X-Requested-With') // '') =~ /XMLHttpRequest/i;
}
sub param { shift->params->param(@_) }
sub params {
my $self = shift;
return $self->{params}
||= $self->body_params->clone->merge($self->query_params);
}
sub parse {
my $self = shift;
# Parse CGI environment
my $env = @_ > 1 ? {@_} : ref $_[0] eq 'HASH' ? $_[0] : undef;
$self->env($env)->_parse_env($env) if $env;
# Parse normal message
my @args = $env ? () : @_;
if (($self->{state} // '') ne 'cgi') { $self->SUPER::parse(@args) }
# Parse CGI content
else { $self->content($self->content->parse_body(@args))->SUPER::parse }
# Check if we can fix things that require all headers
return $self unless $self->is_finished;
# Base URL
my $base = $self->url->base;
$base->scheme('http') unless $base->scheme;
my $headers = $self->headers;
if (!$base->host && (my $host = $headers->host)) { $base->authority($host) }
# Basic authentication
my $auth = _parse_basic_auth($headers->authorization);
$base->userinfo($auth) if $auth;
# Basic proxy authentication
my $proxy_auth = _parse_basic_auth($headers->proxy_authorization);
$self->proxy(Mojo::URL->new->userinfo($proxy_auth)) if $proxy_auth;
# "X-Forwarded-Proto"
$base->scheme('https')
if $self->reverse_proxy
&& ($headers->header('X-Forwarded-Proto') // '') eq 'https';
return $self;
}
sub proxy {
my $self = shift;
return $self->{proxy} unless @_;
$self->{proxy} = !$_[0] || ref $_[0] ? shift : Mojo::URL->new(shift);
return $self;
}
sub query_params { shift->url->query }
sub _parse_basic_auth {
return undef unless my $header = shift;
return $header =~ /Basic (.+)$/ ? b64_decode($1) : undef;
}
sub _parse_env {
my ($self, $env) = @_;
# Extract headers
my $headers = $self->headers;
my $url = $self->url;
my $base = $url->base;
for my $name (keys %$env) {
my $value = $env->{$name};
next unless $name =~ s/^HTTP_//i;
$name =~ y/_/-/;
$headers->header($name => $value);
# Host/Port
if ($name eq 'HOST') {
my ($host, $port) = ($value, undef);
($host, $port) = ($1, $2) if $host =~ /^([^:]*):?(.*)$/;
$base->host($host)->port($port);
}
}
# Content-Type is a special case on some servers
$headers->content_type($env->{CONTENT_TYPE}) if $env->{CONTENT_TYPE};
# Content-Length is a special case on some servers
$headers->content_length($env->{CONTENT_LENGTH}) if $env->{CONTENT_LENGTH};
# Query
$url->query->parse($env->{QUERY_STRING}) if $env->{QUERY_STRING};
# Method
$self->method($env->{REQUEST_METHOD}) if $env->{REQUEST_METHOD};
# Scheme/Version
$base->scheme($1) and $self->version($2)
if ($env->{SERVER_PROTOCOL} // '') =~ m!^([^/]+)/([^/]+)$!;
# HTTPS
$base->scheme('https') if uc($env->{HTTPS} // '') eq 'ON';
# Path
my $path = $url->path->parse($env->{PATH_INFO} ? $env->{PATH_INFO} : '');
# Base path
if (my $value = $env->{SCRIPT_NAME}) {
# Make sure there is a trailing slash (important for merging)
$base->path->parse($value =~ m!/$! ? $value : "$value/");
# Remove SCRIPT_NAME prefix if necessary
my $buffer = $path->to_string;
$value =~ s!^/|/$!!g;
$buffer =~ s!^/?\Q$value\E/?!!;
$buffer =~ s!^/!!;
$path->parse($buffer);
}
# Bypass normal message parser
$self->{state} = 'cgi';
}
1;
=encoding utf8
=head1 NAME
Mojo::Message::Request - HTTP request
=head1 SYNOPSIS
use Mojo::Message::Request;
# Parse
my $req = Mojo::Message::Request->new;
$req->parse("GET /foo HTTP/1.0\x0d\x0a");
$req->parse("Content-Length: 12\x0d\x0a");
$req->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
$req->parse('Hello World!');
say $req->method;
say $req->headers->content_type;
say $req->body;
# Build
my $req = Mojo::Message::Request->new;
$req->url->parse('http://127.0.0.1/foo/bar');
$req->method('GET');
say $req->to_string;
=head1 DESCRIPTION
L<Mojo::Message::Request> is a container for HTTP requests based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230>,
L<RFC 7231|http://tools.ietf.org/html/rfc7235>,
L<RFC 7231|http://tools.ietf.org/html/rfc7235> and
L<RFC 2817|http://tools.ietf.org/html/rfc2817>.
=head1 EVENTS
L<Mojo::Message::Request> inherits all events from L<Mojo::Message>.
=head1 ATTRIBUTES
L<Mojo::Message::Request> inherits all attributes from L<Mojo::Message> and
implements the following new ones.
=head2 env
my $env = $req->env;
$req = $req->env({});
Direct access to the C<CGI> or C<PSGI> environment hash if available.
# Check CGI version
my $version = $req->env->{GATEWAY_INTERFACE};
# Check PSGI version
my $version = $req->env->{'psgi.version'};
=head2 method
my $method = $req->method;
$req = $req->method('POST');
HTTP request method, defaults to C<GET>.
=head2 url
my $url = $req->url;
$req = $req->url(Mojo::URL->new);
HTTP request URL, defaults to a L<Mojo::URL> object.
# Get request information
say $req->url->to_abs->userinfo;
say $req->url->to_abs->host;
say $req->url->to_abs->path;
=head2 reverse_proxy
my $bool = $req->reverse_proxy;
$req = $req->reverse_proxy($bool);
Request has been performed through a reverse proxy.
=head1 METHODS
L<Mojo::Message::Request> inherits all methods from L<Mojo::Message> and
implements the following new ones.
=head2 clone
my $clone = $req->clone;
Clone request if possible, otherwise return C<undef>.
=head2 cookies
my $cookies = $req->cookies;
$req = $req->cookies(Mojo::Cookie::Request->new);
$req = $req->cookies({name => 'foo', value => 'bar'});
Access request cookies, usually L<Mojo::Cookie::Request> objects.
=head2 every_param
my $values = $req->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an
array reference.
# Get first value
say $req->every_param('foo')->[0];
=head2 extract_start_line
my $bool = $req->extract_start_line(\$str);
Extract request line from string.
=head2 fix_headers
$req = $req->fix_headers;
Make sure request has all required headers.
=head2 get_start_line_chunk
my $bytes = $req->get_start_line_chunk($offset);
Get a chunk of request line data starting from a specific position.
=head2 is_handshake
my $bool = $req->is_handshake;
Check C<Upgrade> header for C<websocket> value.
=head2 is_secure
my $bool = $req->is_secure;
Check if connection is secure.
=head2 is_xhr
my $bool = $req->is_xhr;
Check C<X-Requested-With> header for C<XMLHttpRequest> value.
=head2 param
my @names = $req->param;
my $value = $req->param('foo');
my ($foo, $bar) = $req->param(['foo', 'bar']);
Access C<GET> and C<POST> parameters extracted from the query string and
C<application/x-www-form-urlencoded> or C<multipart/form-data> message body.
If there are multiple values sharing the same name, and you want to access
more than just the last one, you can use L</"every_param">. Note that this
method caches all data, so it should not be called before the entire request
body has been received. Parts of the request body need to be loaded into
memory to parse C<POST> parameters, so you have to make sure it is not
excessively large, there's a 10MB limit by default.
=head2 params
my $params = $req->params;
All C<GET> and C<POST> parameters extracted from the query string and
C<application/x-www-form-urlencoded> or C<multipart/form-data> message body,
usually a L<Mojo::Parameters> object. Note that this method caches all data,
so it should not be called before the entire request body has been received.
Parts of the request body need to be loaded into memory to parse C<POST>
parameters, so you have to make sure it is not excessively large, there's a
10MB limit by default.
# Get parameter value
say $req->params->param('foo');
=head2 parse
$req = $req->parse('GET /foo/bar HTTP/1.1');
$req = $req->parse(REQUEST_METHOD => 'GET');
$req = $req->parse({REQUEST_METHOD => 'GET'});
Parse HTTP request chunks or environment hash.
=head2 proxy
my $proxy = $req->proxy;
$req = $req->proxy('http://foo:bar@127.0.0.1:3000');
$req = $req->proxy(Mojo::URL->new('http://127.0.0.1:3000'));
Proxy URL for request.
# Disable proxy
$req->proxy(0);
=head2 query_params
my $params = $req->query_params;
All C<GET> parameters, usually a L<Mojo::Parameters> object.
# Turn GET parameters to hash and extract value
say $req->query_params->to_hash->{foo};
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_MESSAGE_REQUEST
$fatpacked{"Mojo/Message/Response.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_MESSAGE_RESPONSE';
package Mojo::Message::Response;
use Mojo::Base 'Mojo::Message';
use Mojo::Cookie::Response;
use Mojo::Date;
has [qw(code message)];
# Umarked codes are from RFC 7231
my %MESSAGES = (
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', # RFC 2518 (WebDAV)
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status', # RFC 2518 (WebDAV)
208 => 'Already Reported', # RFC 5842
226 => 'IM Used', # RFC 3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect', # RFC 7238
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Request Range Not Satisfiable',
417 => 'Expectation Failed',
418 => "I'm a teapot", # RFC 2324 :)
422 => 'Unprocessable Entity', # RFC 2518 (WebDAV)
423 => 'Locked', # RFC 2518 (WebDAV)
424 => 'Failed Dependency', # RFC 2518 (WebDAV)
425 => 'Unordered Colection', # RFC 3648 (WebDAV)
426 => 'Upgrade Required', # RFC 2817
428 => 'Precondition Required', # RFC 6585
429 => 'Too Many Requests', # RFC 6585
431 => 'Request Header Fields Too Large', # RFC 6585
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', # RFC 2295
507 => 'Insufficient Storage', # RFC 2518 (WebDAV)
508 => 'Loop Detected', # RFC 5842
509 => 'Bandwidth Limit Exceeded', # Unofficial
510 => 'Not Extended', # RFC 2774
511 => 'Network Authentication Required' # RFC 6585
);
sub cookies {
my $self = shift;
# Parse cookies
my $headers = $self->headers;
return [map { @{Mojo::Cookie::Response->parse($_)} } $headers->set_cookie]
unless @_;
# Add cookies
for my $cookie (@_) {
$cookie = Mojo::Cookie::Response->new($cookie) if ref $cookie eq 'HASH';
$headers->add('Set-Cookie' => "$cookie");
}
return $self;
}
sub default_message { $MESSAGES{$_[1] || $_[0]->code // 404} || '' }
sub extract_start_line {
my ($self, $bufref) = @_;
# We have a full response line
return undef unless $$bufref =~ s/^(.*?)\x0d?\x0a//;
return !$self->error({message => 'Bad response start line'})
unless $1 =~ m!^\s*HTTP/(\d\.\d)\s+(\d\d\d)\s*(.+)?$!;
my $content = $self->content;
$content->skip_body(1) if $self->code($2)->is_empty;
defined $content->$_ or $content->$_(1) for qw(auto_decompress auto_relax);
$content->expect_close(1) if $1 eq '1.0';
return !!$self->version($1)->message($3);
}
sub fix_headers {
my $self = shift;
$self->{fix} ? return $self : $self->SUPER::fix_headers(@_);
# Date
my $headers = $self->headers;
$headers->date(Mojo::Date->new->to_string) unless $headers->date;
return $self;
}
sub get_start_line_chunk {
my ($self, $offset) = @_;
unless (defined $self->{start_buffer}) {
my $code = $self->code || 404;
my $msg = $self->message || $self->default_message;
$self->{start_buffer} = "HTTP/@{[$self->version]} $code $msg\x0d\x0a";
}
$self->emit(progress => 'start_line', $offset);
return substr $self->{start_buffer}, $offset, 131072;
}
sub is_empty {
my $self = shift;
return undef unless my $code = $self->code;
return $self->is_status_class(100) || $code == 204 || $code == 304;
}
sub is_status_class {
my ($self, $class) = @_;
return undef unless my $code = $self->code;
return $code >= $class && $code < ($class + 100);
}
1;
=encoding utf8
=head1 NAME
Mojo::Message::Response - HTTP response
=head1 SYNOPSIS
use Mojo::Message::Response;
# Parse
my $res = Mojo::Message::Response->new;
$res->parse("HTTP/1.0 200 OK\x0d\x0a");
$res->parse("Content-Length: 12\x0d\x0a");
$res->parse("Content-Type: text/plain\x0d\x0a\x0d\x0a");
$res->parse('Hello World!');
say $res->code;
say $res->headers->content_type;
say $res->body;
# Build
my $res = Mojo::Message::Response->new;
$res->code(200);
$res->headers->content_type('text/plain');
$res->body('Hello World!');
say $res->to_string;
=head1 DESCRIPTION
L<Mojo::Message::Response> is a container for HTTP responses based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Message::Response> inherits all events from L<Mojo::Message>.
=head1 ATTRIBUTES
L<Mojo::Message::Response> inherits all attributes from L<Mojo::Message> and
implements the following new ones.
=head2 code
my $code = $res->code;
$res = $res->code(200);
HTTP response status code.
=head2 message
my $msg = $res->message;
$res = $res->message('OK');
HTTP response status message.
=head1 METHODS
L<Mojo::Message::Response> inherits all methods from L<Mojo::Message> and
implements the following new ones.
=head2 cookies
my $cookies = $res->cookies;
$res = $res->cookies(Mojo::Cookie::Response->new);
$res = $res->cookies({name => 'foo', value => 'bar'});
Access response cookies, usually L<Mojo::Cookie::Response> objects.
=head2 default_message
my $msg = $res->default_message;
my $msg = $res->default_message(418);
Generate default response message for status code, defaults to using
L</"code">.
=head2 extract_start_line
my $bool = $res->extract_start_line(\$str);
Extract status line from string.
=head2 fix_headers
$res = $res->fix_headers;
Make sure response has all required headers.
=head2 get_start_line_chunk
my $bytes = $res->get_start_line_chunk($offset);
Get a chunk of status line data starting from a specific position.
=head2 is_empty
my $bool = $res->is_empty;
Check if this is a C<1xx>, C<204> or C<304> response.
=head2 is_status_class
my $bool = $res->is_status_class(200);
Check response status class.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_MESSAGE_RESPONSE
$fatpacked{"Mojo/Parameters.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_PARAMETERS';
package Mojo::Parameters;
use Mojo::Base -base;
use overload
'@{}' => sub { shift->params },
bool => sub {1},
'""' => sub { shift->to_string },
fallback => 1;
use Mojo::Util qw(decode encode url_escape url_unescape);
has charset => 'UTF-8';
sub append {
my $self = shift;
my $params = $self->params;
while (my ($name, $value) = splice @_, 0, 2) {
# Single value
if (ref $value ne 'ARRAY') { push @$params, $name => $value }
# Multiple values
else { push @$params, $name => (defined $_ ? "$_" : '') for @$value }
}
return $self;
}
sub clone {
my $self = shift;
my $clone = $self->new;
if (exists $self->{charset}) { $clone->{charset} = $self->{charset} }
if (defined $self->{string}) { $clone->{string} = $self->{string} }
else { $clone->{params} = [@{$self->params}] }
return $clone;
}
sub every_param { shift->_param(@_) }
sub merge {
my $self = shift;
push @{$self->params}, @{$_->params} for @_;
return $self;
}
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';
# Last value
return $self->_param($name)->[-1] unless @_;
# Replace values
$self->remove($name) if defined $_[0];
return $self->append($name => ref $_[0] eq 'ARRAY' ? $_[0] : [@_]);
}
sub params {
my $self = shift;
# Replace parameters
if (@_) {
$self->{params} = shift;
delete $self->{string};
return $self;
}
# Parse string
if (defined(my $str = delete $self->{string})) {
my $params = $self->{params} = [];
return $params unless length $str;
my $charset = $self->charset;
for my $pair (split '&', $str) {
next unless $pair =~ /^([^=]+)(?:=(.*))?$/;
my ($name, $value) = ($1, $2 // '');
# Replace "+" with whitespace, unescape and decode
s/\+/ /g for $name, $value;
$name = url_unescape $name;
$name = decode($charset, $name) // $name if $charset;
$value = url_unescape $value;
$value = decode($charset, $value) // $value if $charset;
push @$params, $name, $value;
}
}
return $self->{params} ||= [];
}
sub parse {
my $self = shift;
# Pairs
return $self->append(@_) if @_ > 1;
# String
$self->{string} = shift;
return $self;
}
sub remove {
my ($self, $name) = @_;
my $params = $self->params;
my $i = 0;
$params->[$i] eq $name ? splice @$params, $i, 2 : ($i += 2)
while $i < @$params;
return $self;
}
sub to_hash {
my $self = shift;
my %hash;
my $params = $self->params;
for (my $i = 0; $i < @$params; $i += 2) {
my ($name, $value) = @{$params}[$i, $i + 1];
# Array
if (exists $hash{$name}) {
$hash{$name} = [$hash{$name}] if ref $hash{$name} ne 'ARRAY';
push @{$hash{$name}}, $value;
}
# String
else { $hash{$name} = $value }
}
return \%hash;
}
sub to_string {
my $self = shift;
# String
my $charset = $self->charset;
if (defined(my $str = $self->{string})) {
$str = encode $charset, $str if $charset;
return url_escape $str, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/?';
}
# Build pairs
my $params = $self->params;
return '' unless @$params;
my @pairs;
for (my $i = 0; $i < @$params; $i += 2) {
my ($name, $value) = @{$params}[$i, $i + 1];
# Escape and replace whitespace with "+"
$name = encode $charset, $name if $charset;
$name = url_escape $name, '^A-Za-z0-9\-._~!$\'()*,:@/?';
$value = encode $charset, $value if $charset;
$value = url_escape $value, '^A-Za-z0-9\-._~!$\'()*,:@/?';
s/\%20/\+/g for $name, $value;
push @pairs, "$name=$value";
}
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
=head1 NAME
Mojo::Parameters - Parameters
=head1 SYNOPSIS
use Mojo::Parameters;
# Parse
my $params = Mojo::Parameters->new('foo=bar&baz=23');
say $params->param('baz');
# Build
my $params = Mojo::Parameters->new(foo => 'bar', baz => 23);
push @$params, i => '♥ mojolicious';
say "$params";
=head1 DESCRIPTION
L<Mojo::Parameters> is a container for form parameters used by L<Mojo::URL>
and based on L<RFC 3986|http://tools.ietf.org/html/rfc3986> as well as the
L<HTML Living Standard|https://html.spec.whatwg.org>.
=head1 ATTRIBUTES
L<Mojo::Parameters> implements the following attributes.
=head2 charset
my $charset = $params->charset;
$params = $params->charset('UTF-8');
Charset used for encoding and decoding parameters, defaults to C<UTF-8>.
# Disable encoding and decoding
$params->charset(undef);
=head1 METHODS
L<Mojo::Parameters> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 append
$params = $params->append(foo => 'ba&r');
$params = $params->append(foo => ['ba&r', 'baz']);
$params = $params->append(foo => ['bar', 'baz'], bar => 23);
Append parameters. Note that this method will normalize the parameters.
# "foo=bar&foo=baz"
Mojo::Parameters->new('foo=bar')->append(foo => 'baz');
# "foo=bar&foo=baz&foo=yada"
Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada']);
# "foo=bar&foo=baz&foo=yada&bar=23"
Mojo::Parameters->new('foo=bar')->append(foo => ['baz', 'yada'], bar => 23);
=head2 clone
my $params2 = $params->clone;
Clone parameters.
=head2 every_param
my $values = $params->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an
array reference. Note that this method will normalize the parameters.
# Get first value
say $params->every_param('foo')->[0];
=head2 merge
$params = $params->merge(Mojo::Parameters->new(foo => 'b&ar', baz => 23));
Merge L<Mojo::Parameters> objects. Note that this method will normalize the
parameters.
=head2 new
my $params = Mojo::Parameters->new;
my $params = Mojo::Parameters->new('foo=b%3Bar&baz=23');
my $params = Mojo::Parameters->new(foo => 'b&ar');
my $params = Mojo::Parameters->new(foo => ['ba&r', 'baz']);
my $params = Mojo::Parameters->new(foo => ['bar', 'baz'], bar => 23);
Construct a new L<Mojo::Parameters> object and L</"parse"> parameters if
necessary.
=head2 param
my @names = $params->param;
my $value = $params->param('foo');
my ($foo, $bar) = $params->param(['foo', 'bar']);
$params = $params->param(foo => 'ba&r');
$params = $params->param(foo => qw(ba&r baz));
$params = $params->param(foo => ['ba;r', 'baz']);
Access parameter values. If there are multiple values sharing the same name,
and you want to access more than just the last one, you can use
L</"every_param">. Note that this method will normalize the parameters.
=head2 params
my $array = $params->params;
$params = $params->params([foo => 'b&ar', baz => 23]);
Parsed parameters. Note that this method will normalize the parameters.
=head2 parse
$params = $params->parse('foo=b%3Bar&baz=23');
Parse parameters.
=head2 remove
$params = $params->remove('foo');
Remove parameters. Note that this method will normalize the parameters.
# "bar=yada"
Mojo::Parameters->new('foo=bar&foo=baz&bar=yada')->remove('foo');
=head2 to_hash
my $hash = $params->to_hash;
Turn parameters into a hash reference. Note that this method will normalize
the parameters.
# "baz"
Mojo::Parameters->new('foo=bar&foo=baz')->to_hash->{foo}[1];
=head2 to_string
my $str = $params->to_string;
Turn parameters into a string.
=head1 OPERATORS
L<Mojo::Parameters> overloads the following operators.
=head2 array
my @params = @$params;
Alias for L</"params">. Note that this will normalize the parameters.
say $params->[0];
say for @$params;
=head2 bool
my $bool = !!$params;
Always true.
=head2 stringify
my $str = "$params";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_PARAMETERS
$fatpacked{"Mojo/Path.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_PATH';
package Mojo::Path;
use Mojo::Base -base;
use overload
'@{}' => sub { shift->parts },
bool => sub {1},
'""' => sub { shift->to_string },
fallback => 1;
use Mojo::Util qw(decode encode url_escape url_unescape);
has charset => 'UTF-8';
sub canonicalize {
my $self = shift;
my $parts = $self->parts;
for (my $i = 0; $i <= $#$parts;) {
if ($parts->[$i] eq '.' || $parts->[$i] eq '') { splice @$parts, $i, 1 }
elsif ($i < 1 || $parts->[$i] ne '..' || $parts->[$i - 1] eq '..') { $i++ }
else { splice @$parts, --$i, 2 }
}
return @$parts ? $self : $self->trailing_slash(undef);
}
sub clone {
my $self = shift;
my $clone = $self->new;
if (exists $self->{charset}) { $clone->{charset} = $self->{charset} }
if (my $parts = $self->{parts}) {
$clone->{$_} = $self->{$_} for qw(leading_slash trailing_slash);
$clone->{parts} = [@$parts];
}
else { $clone->{path} = $self->{path} }
return $clone;
}
sub contains { $_[1] eq '/' || $_[0]->to_route =~ m!^\Q$_[1]\E(?:/|$)! }
sub leading_slash { shift->_parse(leading_slash => @_) }
sub merge {
my ($self, $path) = @_;
# Replace
return $self->parse($path) if $path =~ m!^/!;
# Merge
pop @{$self->parts} unless $self->trailing_slash;
$path = $self->new($path);
push @{$self->parts}, @{$path->parts};
return $self->trailing_slash($path->trailing_slash);
}
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my $self = shift;
$self->{path} = shift;
delete @$self{qw(leading_slash parts trailing_slash)};
return $self;
}
sub parts { shift->_parse(parts => @_) }
sub to_abs_string {
my $path = shift->to_string;
return $path =~ m!^/! ? $path : "/$path";
}
sub to_dir {
my $clone = shift->clone;
pop @{$clone->parts} unless $clone->trailing_slash;
return $clone->trailing_slash(!!@{$clone->parts});
}
sub to_route {
my $clone = shift->clone;
return '/' . join '/', @{$clone->parts}, $clone->trailing_slash ? '' : ();
}
sub to_string {
my $self = shift;
# Path
my $charset = $self->charset;
if (defined(my $path = $self->{path})) {
$path = encode $charset, $path if $charset;
return url_escape $path, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/';
}
# Build path
my @parts = @{$self->parts};
@parts = map { encode $charset, $_ } @parts if $charset;
my $path = join '/',
map { url_escape $_, '^A-Za-z0-9\-._~!$&\'()*+,;=:@' } @parts;
$path = "/$path" if $self->leading_slash;
$path = "$path/" if $self->trailing_slash;
return $path;
}
sub trailing_slash { shift->_parse(trailing_slash => @_) }
sub _parse {
my ($self, $name) = (shift, shift);
unless ($self->{parts}) {
my $path = url_unescape delete($self->{path}) // '';
my $charset = $self->charset;
$path = decode($charset, $path) // $path if $charset;
$self->{leading_slash} = $path =~ s!^/!!;
$self->{trailing_slash} = $path =~ s!/$!!;
$self->{parts} = [split '/', $path, -1];
}
return $self->{$name} unless @_;
$self->{$name} = shift;
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojo::Path - Path
=head1 SYNOPSIS
use Mojo::Path;
# Parse
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html');
say $path->[0];
# Build
my $path = Mojo::Path->new('/i/♥');
push @$path, 'mojolicious';
say "$path";
=head1 DESCRIPTION
L<Mojo::Path> is a container for paths used by L<Mojo::URL> and based on
L<RFC 3986|http://tools.ietf.org/html/rfc3986>.
=head1 ATTRIBUTES
L<Mojo::Path> implements the following attributes.
=head2 charset
my $charset = $path->charset;
$path = $path->charset('UTF-8');
Charset used for encoding and decoding, defaults to C<UTF-8>.
# Disable encoding and decoding
$path->charset(undef);
=head1 METHODS
L<Mojo::Path> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 canonicalize
$path = $path->canonicalize;
Canonicalize path.
# "/foo/baz"
Mojo::Path->new('/foo/./bar/../baz')->canonicalize;
# "/../baz"
Mojo::Path->new('/foo/../bar/../../baz')->canonicalize;
=head2 clone
my $clone = $path->clone;
Clone path.
=head2 contains
my $bool = $path->contains('/i/♥/mojolicious');
Check if path contains given prefix.
# True
Mojo::Path->new('/foo/bar')->contains('/');
Mojo::Path->new('/foo/bar')->contains('/foo');
Mojo::Path->new('/foo/bar')->contains('/foo/bar');
# False
Mojo::Path->new('/foo/bar')->contains('/f');
Mojo::Path->new('/foo/bar')->contains('/bar');
Mojo::Path->new('/foo/bar')->contains('/whatever');
=head2 leading_slash
my $bool = $path->leading_slash;
$path = $path->leading_slash($bool);
Path has a leading slash. Note that this method will normalize the path and
that C<%2F> will be treated as C</> for security reasons.
=head2 merge
$path = $path->merge('/foo/bar');
$path = $path->merge('foo/bar');
$path = $path->merge(Mojo::Path->new('foo/bar'));
Merge paths. Note that this method will normalize both paths if necessary and
that C<%2F> will be treated as C</> for security reasons.
# "/baz/yada"
Mojo::Path->new('/foo/bar')->merge('/baz/yada');
# "/foo/baz/yada"
Mojo::Path->new('/foo/bar')->merge('baz/yada');
# "/foo/bar/baz/yada"
Mojo::Path->new('/foo/bar/')->merge('baz/yada');
=head2 new
my $path = Mojo::Path->new;
my $path = Mojo::Path->new('/foo%2Fbar%3B/baz.html');
Construct a new L<Mojo::Path> object and L</"parse"> path if necessary.
=head2 parse
$path = $path->parse('/foo%2Fbar%3B/baz.html');
Parse path.
=head2 to_abs_string
my $str = $path->to_abs_string;
Turn path into an absolute string.
# "/i/%E2%99%A5/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_abs_string;
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_abs_string;
=head2 parts
my $parts = $path->parts;
$path = $path->parts([qw(foo bar baz)]);
The path parts. Note that this method will normalize the path and that C<%2F>
will be treated as C</> for security reasons.
# Part with slash
push @{$path->parts}, 'foo/bar';
=head2 to_dir
my $dir = $route->to_dir;
Clone path and remove everything after the right-most slash.
# "/i/%E2%99%A5/"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_dir->to_abs_string;
# "i/%E2%99%A5/"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_dir->to_abs_string;
=head2 to_route
my $route = $path->to_route;
Turn path into a route.
# "/i/♥/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_route;
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_route;
=head2 to_string
my $str = $path->to_string;
Turn path into a string.
# "/i/%E2%99%A5/mojolicious"
Mojo::Path->new('/i/%E2%99%A5/mojolicious')->to_string;
# "i/%E2%99%A5/mojolicious"
Mojo::Path->new('i/%E2%99%A5/mojolicious')->to_string;
=head2 trailing_slash
my $bool = $path->trailing_slash;
$path = $path->trailing_slash($bool);
Path has a trailing slash. Note that this method will normalize the path and
that C<%2F> will be treated as C</> for security reasons.
=head1 OPERATORS
L<Mojo::Path> overloads the following operators.
=head2 array
my @parts = @$path;
Alias for L</"parts">. Note that this will normalize the path and that C<%2F>
will be treated as C</> for security reasons.
say $path->[0];
say for @$path;
=head2 bool
my $bool = !!$path;
Always true.
=head2 stringify
my $str = "$path";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_PATH
$fatpacked{"Mojo/Reactor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_REACTOR';
package Mojo::Reactor;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use IO::Poll qw(POLLIN POLLPRI);
use Mojo::Loader;
sub again { croak 'Method "again" not implemented by subclass' }
sub detect {
my $try = $ENV{MOJO_REACTOR} || 'Mojo::Reactor::EV';
return Mojo::Loader->new->load($try) ? 'Mojo::Reactor::Poll' : $try;
}
sub io { croak 'Method "io" not implemented by subclass' }
# This may break at some point in the future, but is worth it for performance
sub is_readable {
!(IO::Poll::_poll(0, fileno(pop), my $dummy = POLLIN | POLLPRI) == 0);
}
sub is_running { croak 'Method "is_running" not implemented by subclass' }
sub next_tick { shift->timer(0 => @_) and return undef }
sub one_tick { croak 'Method "one_tick" not implemented by subclass' }
sub recurring { croak 'Method "recurring" not implemented by subclass' }
sub remove { croak 'Method "remove" not implemented by subclass' }
sub reset { croak 'Method "reset" not implemented by subclass' }
sub start { croak 'Method "start" not implemented by subclass' }
sub stop { croak 'Method "stop" not implemented by subclass' }
sub timer { croak 'Method "timer" not implemented by subclass' }
sub watch { croak 'Method "watch" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojo::Reactor - Low-level event reactor base class
=head1 SYNOPSIS
package Mojo::Reactor::MyEventLoop;
use Mojo::Base 'Mojo::Reactor';
$ENV{MOJO_REACTOR} ||= 'Mojo::Reactor::MyEventLoop';
sub again {...}
sub io {...}
sub is_running {...}
sub one_tick {...}
sub recurring {...}
sub remove {...}
sub reset {...}
sub start {...}
sub stop {...}
sub timer {...}
sub watch {...}
=head1 DESCRIPTION
L<Mojo::Reactor> is an abstract base class for low-level event reactors.
=head1 EVENTS
L<Mojo::Reactor> inherits all events from L<Mojo::EventEmitter> and can emit
the following new ones.
=head2 error
$reactor->on(error => sub {
my ($reactor, $err) = @_;
...
});
Emitted for exceptions caught in callbacks, fatal if unhandled. Note that if
this event is unhandled or fails it might kill your program, so you need to be
careful.
$reactor->on(error => sub {
my ($reactor, $err) = @_;
say "Something very bad happened: $err";
});
=head1 METHODS
L<Mojo::Reactor> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 again
$reactor->again($id);
Restart active timer. Meant to be overloaded in a subclass.
=head2 detect
my $class = Mojo::Reactor->detect;
Detect and load the best reactor implementation available, will try the value
of the C<MOJO_REACTOR> environment variable, L<Mojo::Reactor::EV> or
L<Mojo::Reactor::Poll>.
# Instantiate best reactor implementation available
my $reactor = Mojo::Reactor->detect->new;
=head2 io
$reactor = $reactor->io($handle => sub {...});
Watch handle for I/O events, invoking the callback whenever handle becomes
readable or writable. Meant to be overloaded in a subclass.
# Callback will be invoked twice if handle becomes readable and writable
$reactor->io($handle => sub {
my ($reactor, $writable) = @_;
say $writable ? 'Handle is writable' : 'Handle is readable';
});
=head2 is_readable
my $bool = $reactor->is_readable($handle);
Quick non-blocking check if a handle is readable, useful for identifying
tainted sockets.
=head2 is_running
my $bool = $reactor->is_running;
Check if reactor is running. Meant to be overloaded in a subclass.
=head2 next_tick
my $undef = $reactor->next_tick(sub {...});
Invoke callback as soon as possible, but not before returning, always returns
C<undef>.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs. Note that this method can recurse back into
the reactor, so you need to be careful. Meant to be overloaded in a subclass.
# Don't block longer than 0.5 seconds
my $id = $reactor->timer(0.5 => sub {});
$reactor->one_tick;
$reactor->remove($id);
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given
amount of time in seconds. Meant to be overloaded in a subclass.
=head2 remove
my $bool = $reactor->remove($handle);
my $bool = $reactor->remove($id);
Remove handle or timer. Meant to be overloaded in a subclass.
=head2 reset
$reactor->reset;
Remove all handles and timers. Meant to be overloaded in a subclass.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is
called. Note that some reactors stop automatically if there are no events
being watched anymore. Meant to be overloaded in a subclass.
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events. Meant to be overloaded in a subclass.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in
seconds. Meant to be overloaded in a subclass.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Meant to be
overloaded in a subclass. Note that this method requires an active I/O
watcher.
# Watch only for readable events
$reactor->watch($handle, 1, 0);
# Watch only for writable events
$reactor->watch($handle, 0, 1);
# Watch for readable and writable events
$reactor->watch($handle, 1, 1);
# Pause watching for events
$reactor->watch($handle, 0, 0);
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_REACTOR
$fatpacked{"Mojo/Reactor/EV.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_REACTOR_EV';
package Mojo::Reactor::EV;
use Mojo::Base 'Mojo::Reactor::Poll';
use EV 4.0;
use Scalar::Util 'weaken';
my $EV;
sub CLONE { die "EV does not work with ithreads.\n" }
sub DESTROY { undef $EV }
sub again { shift->{timers}{shift()}{watcher}->again }
sub is_running { !!EV::depth }
# We have to fall back to Mojo::Reactor::Poll, since EV is unique
sub new { $EV++ ? Mojo::Reactor::Poll->new : shift->SUPER::new }
sub one_tick { EV::run(EV::RUN_ONCE) }
sub recurring { shift->_timer(1, @_) }
sub start {EV::run}
sub stop { EV::break(EV::BREAK_ALL) }
sub timer { shift->_timer(0, @_) }
sub watch {
my ($self, $handle, $read, $write) = @_;
my $mode = 0;
$mode |= EV::READ if $read;
$mode |= EV::WRITE if $write;
my $fd = fileno $handle;
my $io = $self->{io}{$fd};
if ($mode == 0) { delete $io->{watcher} }
elsif (my $w = $io->{watcher}) { $w->set($fd, $mode) }
else {
weaken $self;
$io->{watcher} = EV::io($fd, $mode, sub { $self->_io($fd, @_) });
}
return $self;
}
sub _io {
my ($self, $fd, $w, $revents) = @_;
my $io = $self->{io}{$fd};
$self->_sandbox('Read', $io->{cb}, 0) if EV::READ & $revents;
$self->_sandbox('Write', $io->{cb}, 1)
if EV::WRITE & $revents && $self->{io}{$fd};
}
sub _timer {
my ($self, $recurring, $after, $cb) = @_;
$after ||= 0.0001 if $recurring;
my $id = $self->SUPER::_timer(0, 0, $cb);
weaken $self;
$self->{timers}{$id}{watcher} = EV::timer(
$after => $after => sub {
my $timer = $self->{timers}{$id};
delete delete($self->{timers}{$id})->{watcher} unless $recurring;
$self->_sandbox("Timer $id", $timer->{cb});
}
);
return $id;
}
1;
=encoding utf8
=head1 NAME
Mojo::Reactor::EV - Low-level event reactor with libev support
=head1 SYNOPSIS
use Mojo::Reactor::EV;
# Watch if handle becomes readable or writable
my $reactor = Mojo::Reactor::EV->new;
$reactor->io($handle => sub {
my ($reactor, $writable) = @_;
say $writable ? 'Handle is writable' : 'Handle is readable';
});
# Change to watching only if handle becomes writable
$reactor->watch($handle, 0, 1);
# Add a timer
$reactor->timer(15 => sub {
my $reactor = shift;
$reactor->remove($handle);
say 'Timeout!';
});
# Start reactor if necessary
$reactor->start unless $reactor->is_running;
=head1 DESCRIPTION
L<Mojo::Reactor::EV> is a low-level event reactor based on L<EV> (4.0+).
=head1 EVENTS
L<Mojo::Reactor::EV> inherits all events from L<Mojo::Reactor::Poll>.
=head1 METHODS
L<Mojo::Reactor::EV> inherits all methods from L<Mojo::Reactor::Poll> and
implements the following new ones.
=head2 again
$reactor->again($id);
Restart active timer.
=head2 is_running
my $bool = $reactor->is_running;
Check if reactor is running.
=head2 new
my $reactor = Mojo::Reactor::EV->new;
Construct a new L<Mojo::Reactor::EV> object.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs or no events are being watched anymore. Note
that this method can recurse back into the reactor, so you need to be careful.
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given
amount of time in seconds.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is
called or no events are being watched anymore.
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in
seconds.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Note that
this method requires an active I/O watcher.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_REACTOR_EV
$fatpacked{"Mojo/Reactor/Poll.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_REACTOR_POLL';
package Mojo::Reactor::Poll;
use Mojo::Base 'Mojo::Reactor';
use IO::Poll qw(POLLERR POLLHUP POLLIN POLLOUT POLLPRI);
use List::Util 'min';
use Mojo::Util qw(md5_sum steady_time);
use Time::HiRes 'usleep';
sub again {
my $timer = shift->{timers}{shift()};
$timer->{time} = steady_time + $timer->{after};
}
sub io {
my ($self, $handle, $cb) = @_;
$self->{io}{fileno $handle} = {cb => $cb};
return $self->watch($handle, 1, 1);
}
sub is_running { !!shift->{running} }
sub one_tick {
my $self = shift;
# Remember state for later
my $running = $self->{running};
$self->{running} = 1;
# Wait for one event
my $i;
my $poll = $self->_poll;
until ($i) {
# Stop automatically if there is nothing to watch
return $self->stop unless keys %{$self->{timers}} || keys %{$self->{io}};
# Calculate ideal timeout based on timers
my $min = min map { $_->{time} } values %{$self->{timers}};
my $timeout = defined $min ? ($min - steady_time) : 0.5;
$timeout = 0 if $timeout < 0;
# I/O
if (keys %{$self->{io}}) {
$poll->poll($timeout);
for my $handle ($poll->handles(POLLIN | POLLPRI | POLLHUP | POLLERR)) {
next unless my $io = $self->{io}{fileno $handle};
++$i and $self->_sandbox('Read', $io->{cb}, 0);
}
for my $handle ($poll->handles(POLLOUT)) {
next unless my $io = $self->{io}{fileno $handle};
++$i and $self->_sandbox('Write', $io->{cb}, 1);
}
}
# Wait for timeout if poll can't be used
elsif ($timeout) { usleep $timeout * 1000000 }
# Timers (time should not change in between timers)
my $now = steady_time;
for my $id (keys %{$self->{timers}}) {
next unless my $t = $self->{timers}{$id};
next unless $t->{time} <= $now;
# Recurring timer
if (exists $t->{recurring}) { $t->{time} = $now + $t->{recurring} }
# Normal timer
else { $self->remove($id) }
++$i and $self->_sandbox("Timer $id", $t->{cb}) if $t->{cb};
}
}
# Restore state if necessary
$self->{running} = $running if $self->{running};
}
sub recurring { shift->_timer(1, @_) }
sub remove {
my ($self, $remove) = @_;
return !!delete $self->{timers}{$remove} unless ref $remove;
$self->_poll->remove($remove);
return !!delete $self->{io}{fileno $remove};
}
sub reset { delete @{shift()}{qw(io poll timers)} }
sub start {
my $self = shift;
$self->{running}++;
$self->one_tick while $self->{running};
}
sub stop { delete shift->{running} }
sub timer { shift->_timer(0, @_) }
sub watch {
my ($self, $handle, $read, $write) = @_;
my $mode = 0;
$mode |= POLLIN | POLLPRI if $read;
$mode |= POLLOUT if $write;
my $poll = $self->_poll;
$poll->remove($handle);
$poll->mask($handle, $mode) if $mode != 0;
return $self;
}
sub _poll { shift->{poll} ||= IO::Poll->new }
sub _sandbox {
my ($self, $event, $cb) = (shift, shift, shift);
eval { $self->$cb(@_); 1 } or $self->emit(error => "$event failed: $@");
}
sub _timer {
my ($self, $recurring, $after, $cb) = @_;
my $timers = $self->{timers} //= {};
my $id;
do { $id = md5_sum('t' . steady_time . rand 999) } while $timers->{$id};
my $timer = $timers->{$id}
= {cb => $cb, after => $after, time => steady_time + $after};
$timer->{recurring} = $after if $recurring;
return $id;
}
1;
=encoding utf8
=head1 NAME
Mojo::Reactor::Poll - Low-level event reactor with poll support
=head1 SYNOPSIS
use Mojo::Reactor::Poll;
# Watch if handle becomes readable or writable
my $reactor = Mojo::Reactor::Poll->new;
$reactor->io($handle => sub {
my ($reactor, $writable) = @_;
say $writable ? 'Handle is writable' : 'Handle is readable';
});
# Change to watching only if handle becomes writable
$reactor->watch($handle, 0, 1);
# Add a timer
$reactor->timer(15 => sub {
my $reactor = shift;
$reactor->remove($handle);
say 'Timeout!';
});
# Start reactor if necessary
$reactor->start unless $reactor->is_running;
=head1 DESCRIPTION
L<Mojo::Reactor::Poll> is a low-level event reactor based on L<IO::Poll>.
=head1 EVENTS
L<Mojo::Reactor::Poll> inherits all events from L<Mojo::Reactor>.
=head1 METHODS
L<Mojo::Reactor::Poll> inherits all methods from L<Mojo::Reactor> and
implements the following new ones.
=head2 again
$reactor->again($id);
Restart active timer.
=head2 io
$reactor = $reactor->io($handle => sub {...});
Watch handle for I/O events, invoking the callback whenever handle becomes
readable or writable.
=head2 is_running
my $bool = $reactor->is_running;
Check if reactor is running.
=head2 one_tick
$reactor->one_tick;
Run reactor until an event occurs or no events are being watched anymore. Note
that this method can recurse back into the reactor, so you need to be careful.
=head2 recurring
my $id = $reactor->recurring(0.25 => sub {...});
Create a new recurring timer, invoking the callback repeatedly after a given
amount of time in seconds.
=head2 remove
my $bool = $reactor->remove($handle);
my $bool = $reactor->remove($id);
Remove handle or timer.
=head2 reset
$reactor->reset;
Remove all handles and timers.
=head2 start
$reactor->start;
Start watching for I/O and timer events, this will block until L</"stop"> is
called or no events are being watched anymore.
=head2 stop
$reactor->stop;
Stop watching for I/O and timer events.
=head2 timer
my $id = $reactor->timer(0.5 => sub {...});
Create a new timer, invoking the callback after a given amount of time in
seconds.
=head2 watch
$reactor = $reactor->watch($handle, $readable, $writable);
Change I/O events to watch handle for with true and false values. Note that
this method requires an active I/O watcher.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_REACTOR_POLL
$fatpacked{"Mojo/Server.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER';
package Mojo::Server;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Cwd 'abs_path';
use Mojo::Loader;
use Mojo::Util 'md5_sum';
use POSIX;
use Scalar::Util 'blessed';
has app => sub { shift->build_app('Mojo::HelloWorld') };
has [qw(group user)];
has reverse_proxy => sub { $ENV{MOJO_REVERSE_PROXY} };
sub build_app {
my ($self, $app) = @_;
local $ENV{MOJO_EXE};
return $app->new unless my $e = Mojo::Loader->new->load($app);
die ref $e ? $e : qq{Can't find application class "$app" in \@INC. (@INC)\n};
}
sub build_tx {
my $self = shift;
my $tx = $self->app->build_tx;
$tx->req->reverse_proxy(1) if $self->reverse_proxy;
return $tx;
}
sub daemonize {
# Fork and kill parent
die "Can't fork: $!" unless defined(my $pid = fork);
exit 0 if $pid;
POSIX::setsid or die "Can't start a new session: $!";
# Close filehandles
open STDIN, '</dev/null';
open STDOUT, '>/dev/null';
open STDERR, '>&STDOUT';
}
sub load_app {
my ($self, $path) = @_;
# Clean environment (reset FindBin defensively)
{
local $0 = $path = abs_path $path;
require FindBin;
FindBin->again;
local $ENV{MOJO_APP_LOADER} = 1;
local $ENV{MOJO_EXE};
# Try to load application from script into sandbox
my $app = eval "package Mojo::Server::Sandbox::@{[md5_sum $path]};"
. 'return do($path) || die($@ || $!);';
die qq{Can't load application from file "$path": $@} if !$app && $@;
die qq{File "$path" did not return an application object.\n}
unless blessed $app && $app->isa('Mojo');
$self->app($app);
};
FindBin->again;
return $self->app;
}
sub new {
my $self = shift->SUPER::new(@_);
$self->on(request => sub { shift->app->handler(shift) });
return $self;
}
sub run { croak 'Method "run" not implemented by subclass' }
sub setuidgid {
my $self = shift;
# Group (make sure secondary groups are reassigned too)
if (my $group = $self->group) {
return $self->_log(qq{Group "$group" does not exist.})
unless defined(my $gid = getgrnam $group);
return $self->_log(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.})
unless defined(my $uid = getpwnam $user);
return $self->_log(qq{Can't switch to user "$user": $!})
unless POSIX::setuid($uid);
return $self;
}
sub _log { $_[0]->app->log->error($_[1]) and return $_[0] }
1;
=encoding utf8
=head1 NAME
Mojo::Server - HTTP server base class
=head1 SYNOPSIS
package Mojo::Server::MyServer;
use Mojo::Base 'Mojo::Server';
sub run {
my $self = shift;
# Get a transaction
my $tx = $self->build_tx;
# Emit "request" event
$self->emit(request => $tx);
}
=head1 DESCRIPTION
L<Mojo::Server> is an abstract HTTP server base class.
=head1 EVENTS
L<Mojo::Server> inherits all events from L<Mojo::EventEmitter> and can emit
the following new ones.
=head2 request
$server->on(request => sub {
my ($server, $tx) = @_;
...
});
Emitted when a request is ready and needs to be handled.
$server->unsubscribe('request');
$server->on(request => sub {
my ($server, $tx) = @_;
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body('Hello World!');
$tx->resume;
});
=head1 ATTRIBUTES
L<Mojo::Server> implements the following attributes.
=head2 app
my $app = $server->app;
$server = $server->app(MojoSubclass->new);
Application this server handles, defaults to a L<Mojo::HelloWorld> object.
=head2 group
my $group = $server->group;
$server = $server->group('users');
Group for server process.
=head2 reverse_proxy
my $bool = $server->reverse_proxy;
$server = $server->reverse_proxy($bool);
This server operates behind a reverse proxy, defaults to the value of the
C<MOJO_REVERSE_PROXY> environment variable.
=head2 user
my $user = $server->user;
$server = $server->user('web');
User for the server process.
=head1 METHODS
L<Mojo::Server> inherits all methods from L<Mojo::EventEmitter> and implements
the following new ones.
=head2 build_app
my $app = $server->build_app('Mojo::HelloWorld');
Build application from class.
=head2 build_tx
my $tx = $server->build_tx;
Let application build a transaction.
=head2 daemonize
$server->daemonize;
Daemonize server process.
=head2 load_app
my $app = $server->load_app('/home/sri/myapp.pl');
Load application from script.
say Mojo::Server->new->load_app('./myapp.pl')->home;
=head2 new
my $server = Mojo::Server->new;
Construct a new L<Mojo::Server> object and subscribe to L</"request"> event
with default request handling.
=head2 run
$server->run;
Run server. Meant to be overloaded in a subclass.
=head2 setuidgid
$server = $server->setuidgid;
Set L</"user"> and L</"group"> for process.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER
$fatpacked{"Mojo/Server/CGI.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_CGI';
package Mojo::Server::CGI;
use Mojo::Base 'Mojo::Server';
has 'nph';
sub run {
my $self = shift;
my $tx = $self->build_tx;
my $req = $tx->req->parse(\%ENV);
$tx->local_port($ENV{SERVER_PORT})->remote_address($ENV{REMOTE_ADDR});
# Request body (may block if we try to read too much)
binmode STDIN;
my $len = $req->headers->content_length;
until ($req->is_finished) {
my $chunk = ($len && $len < 131072) ? $len : 131072;
last unless my $read = STDIN->read(my $buffer, $chunk, 0);
$req->parse($buffer);
last if ($len -= $read) <= 0;
}
# Handle request
$self->emit(request => $tx);
# Response start line
STDOUT->autoflush(1);
binmode STDOUT;
my $res = $tx->res->fix_headers;
return undef if $self->nph && !_write($res, 'get_start_line_chunk');
# Response headers
my $code = $res->code || 404;
my $msg = $res->message || $res->default_message;
$res->headers->status("$code $msg") unless $self->nph;
return undef unless _write($res, 'get_header_chunk');
# Response body
return undef unless $tx->is_empty || _write($res, 'get_body_chunk');
# Finish transaction
$tx->server_close;
return $res->code;
}
sub _write {
my ($res, $method) = @_;
my $offset = 0;
while (1) {
# No chunk yet, try again
sleep 1 and next unless defined(my $chunk = $res->$method($offset));
# End of part
last unless my $len = length $chunk;
# Make sure we can still write
$offset += $len;
return undef unless STDOUT->opened;
print STDOUT $chunk;
}
return 1;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::CGI - CGI server
=head1 SYNOPSIS
use Mojo::Server::CGI;
my $cgi = Mojo::Server::CGI->new;
$cgi->unsubscribe('request');
$cgi->on(request => sub {
my ($cgi, $tx) = @_;
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$cgi->run;
=head1 DESCRIPTION
L<Mojo::Server::CGI> is a simple and portable implementation of
L<RFC 3875|http://tools.ietf.org/html/rfc3875>.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 EVENTS
L<Mojo::Server::CGI> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::CGI> inherits all attributes from L<Mojo::Server> and
implements the following new ones.
=head2 nph
my $bool = $cgi->nph;
$cgi = $cgi->nph($bool);
Activate non-parsed header mode.
=head1 METHODS
L<Mojo::Server::CGI> inherits all methods from L<Mojo::Server> and implements
the following new ones.
=head2 run
my $status = $cgi->run;
Run CGI.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_CGI
$fatpacked{"Mojo/Server/Daemon.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_DAEMON';
package Mojo::Server::Daemon;
use Mojo::Base 'Mojo::Server';
use Mojo::IOLoop;
use Mojo::URL;
use Scalar::Util 'weaken';
use constant DEBUG => $ENV{MOJO_DAEMON_DEBUG} || 0;
has acceptors => sub { [] };
has [qw(backlog max_clients silent)];
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 15 };
has ioloop => sub { Mojo::IOLoop->singleton };
has listen => sub { [split ',', $ENV{MOJO_LISTEN} || 'http://*:3000'] };
has max_requests => 25;
sub DESTROY {
my $self = shift;
return unless my $loop = $self->ioloop;
$self->_remove($_) for keys %{$self->{connections} || {}};
$loop->remove($_) for @{$self->acceptors};
}
sub run {
my $self = shift;
local $SIG{INT} = local $SIG{TERM} = sub { $self->ioloop->stop };
$self->start->setuidgid->ioloop->start;
}
sub start {
my $self = shift;
# Resume accepting connections
my $loop = $self->ioloop;
if (my $servers = $self->{servers}) {
push @{$self->acceptors}, $loop->acceptor(delete $servers->{$_})
for keys %$servers;
}
# Start listening
else { $self->_listen($_) for @{$self->listen} }
if (my $max = $self->max_clients) { $loop->max_connections($max) }
return $self;
}
sub stop {
my $self = shift;
# Suspend accepting connections but keep listen sockets open
my $loop = $self->ioloop;
while (my $id = shift @{$self->acceptors}) {
my $server = $self->{servers}{$id} = $loop->acceptor($id);
$loop->remove($id);
$server->stop;
}
return $self;
}
sub _build_tx {
my ($self, $id, $c) = @_;
my $tx = $self->build_tx->connection($id);
$tx->res->headers->server('Mojolicious (Perl)');
my $handle = $self->ioloop->stream($id)->handle;
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
$tx->req->url->base->scheme('https') if $c->{tls};
# Handle upgrades and requests
weaken $self;
$tx->on(
upgrade => sub {
my ($tx, $ws) = @_;
$ws->server_handshake;
$self->{connections}{$id}{ws} = $ws;
}
);
$tx->on(
request => sub {
my $tx = shift;
$self->emit(request => $self->{connections}{$id}{ws} || $tx);
$tx->on(resume => sub { $self->_write($id) });
}
);
# Kept alive if we have more than one request on the connection
return ++$c->{requests} > 1 ? $tx->kept_alive(1) : $tx;
}
sub _close {
my ($self, $id) = @_;
# Finish gracefully
if (my $tx = $self->{connections}{$id}{tx}) { $tx->server_close }
delete $self->{connections}{$id};
}
sub _finish {
my ($self, $id, $tx) = @_;
# Always remove connection for WebSockets
return $self->_remove($id) if $tx->is_websocket;
# Finish transaction
$tx->server_close;
# Upgrade connection to WebSocket
my $c = $self->{connections}{$id};
if (my $ws = $c->{tx} = delete $c->{ws}) {
# Successful upgrade
if ($ws->res->code == 101) {
weaken $self;
$ws->on(resume => sub { $self->_write($id) });
}
# Failed upgrade
else {
delete $c->{tx};
$ws->server_close;
}
}
# Close connection if necessary
my $req = $tx->req;
return $self->_remove($id) if $req->error || !$tx->keep_alive;
# Build new transaction for leftovers
return unless length(my $leftovers = $req->content->leftovers);
$tx = $c->{tx} = $self->_build_tx($id, $c);
$tx->server_read($leftovers);
}
sub _listen {
my ($self, $listen) = @_;
my $url = Mojo::URL->new($listen);
my $query = $url->query;
my $options = {
address => $url->host,
backlog => $self->backlog,
reuse => scalar $query->param('reuse'),
};
if (my $port = $url->port) { $options->{port} = $port }
$options->{"tls_$_"} = scalar $query->param($_) for qw(ca cert ciphers key);
my $verify = $query->param('verify');
$options->{tls_verify} = hex $verify if defined $verify;
delete $options->{address} if $options->{address} eq '*';
my $tls = $options->{tls} = $url->protocol eq 'https';
weaken $self;
push @{$self->acceptors}, $self->ioloop->server(
$options => sub {
my ($loop, $stream, $id) = @_;
my $c = $self->{connections}{$id} = {tls => $tls};
warn "-- Accept (@{[$stream->handle->peerhost]})\n" if DEBUG;
$stream->timeout($self->inactivity_timeout);
$stream->on(close => sub { $self && $self->_close($id) });
$stream->on(error =>
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} });
}
);
return if $self->silent;
$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.";
}
sub _read {
my ($self, $id, $chunk) = @_;
# 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;
$tx->server_read($chunk);
# Last keep-alive request or corrupted connection
$tx->res->headers->connection('close')
if (($c->{requests} || 0) >= $self->max_requests) || $tx->req->error;
# Finish or start writing
if ($tx->is_finished) { $self->_finish($id, $tx) }
elsif ($tx->is_writing) { $self->_write($id) }
}
sub _remove {
my ($self, $id) = @_;
$self->ioloop->remove($id);
$self->_close($id);
}
sub _write {
my ($self, $id) = @_;
# Get chunk and write
return unless my $c = $self->{connections}{$id};
return unless my $tx = $c->{tx};
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;
my $stream = $self->ioloop->stream($id)->write($chunk);
# Finish or continue writing
weaken $self;
my $cb = sub { $self->_write($id) };
if ($tx->is_finished) {
if ($tx->has_subscribers('finish')) {
$cb = sub { $self->_finish($id, $tx) }
}
else {
$self->_finish($id, $tx);
return unless $c->{tx};
}
}
$stream->write('' => $cb);
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Daemon - Non-blocking I/O HTTP and WebSocket server
=head1 SYNOPSIS
use Mojo::Server::Daemon;
my $daemon = Mojo::Server::Daemon->new(listen => ['http://*:8080']);
$daemon->unsubscribe('request');
$daemon->on(request => sub {
my ($daemon, $tx) = @_;
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$daemon->run;
=head1 DESCRIPTION
L<Mojo::Server::Daemon> is a full featured, highly portable non-blocking I/O
HTTP and WebSocket server, with IPv6, TLS, Comet (long polling), keep-alive
and multiple event loop support.
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 EVENTS
L<Mojo::Server::Daemon> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::Daemon> inherits all attributes from L<Mojo::Server> and
implements the following new ones.
=head2 acceptors
my $acceptors = $daemon->acceptors;
$daemon = $daemon->acceptors([]);
Active acceptors.
=head2 backlog
my $backlog = $daemon->backlog;
$daemon = $daemon->backlog(128);
Listen backlog size, defaults to C<SOMAXCONN>.
=head2 inactivity_timeout
my $timeout = $daemon->inactivity_timeout;
$daemon = $daemon->inactivity_timeout(5);
Maximum amount of time in seconds a connection can be inactive before getting
closed, defaults to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment
variable or C<15>. Setting the value to C<0> will allow connections to be
inactive indefinitely.
=head2 ioloop
my $loop = $daemon->ioloop;
$daemon = $daemon->ioloop(Mojo::IOLoop->new);
Event loop object to use for I/O operations, defaults to the global
L<Mojo::IOLoop> singleton.
=head2 listen
my $listen = $daemon->listen;
$daemon = $daemon->listen(['https://localhost:3000']);
List of one or more locations to listen on, defaults to the value of the
C<MOJO_LISTEN> environment variable or C<http://*:3000>.
# Listen on all IPv4 interfaces
$daemon->listen(['http://*:3000']);
# Listen on all IPv4 and IPv6 interfaces
$daemon->listen(['http://[::]:3000']);
# Listen on IPv6 interface
$daemon->listen(['http://[::1]:4000']);
# Listen on IPv4 and IPv6 interfaces
$daemon->listen(['http://127.0.0.1:3000', 'http://[::1]:3000']);
# Allow multiple servers to use the same port (SO_REUSEPORT)
$daemon->listen(['http://*:8080?reuse=1']);
# Listen on two ports with HTTP and HTTPS at the same time
$daemon->listen([qw(http://*:3000 https://*:4000)]);
# Use a custom certificate and key
$daemon->listen(['https://*:3000?cert=/x/server.crt&key=/y/server.key']);
# Or even a custom certificate authority
$daemon->listen(
['https://*:3000?cert=/x/server.crt&key=/y/server.key&ca=/z/ca.crt']);
These parameters are currently available:
=over 2
=item ca
ca=/etc/tls/ca.crt
Path to TLS certificate authority file.
=item cert
cert=/etc/tls/server.crt
Path to the TLS cert file, defaults to a built-in test certificate.
=item ciphers
ciphers=AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH
Cipher specification string.
=item key
key=/etc/tls/server.key
Path to the TLS key file, defaults to a built-in test key.
=item reuse
reuse=1
Allow multiple servers to use the same port with the C<SO_REUSEPORT> socket
option.
=item verify
verify=0x00
TLS verification mode, defaults to C<0x03>.
=back
=head2 max_clients
my $max = $daemon->max_clients;
$daemon = $daemon->max_clients(1000);
Maximum number of concurrent client connections, passed along to
L<Mojo::IOLoop/"max_connections">.
=head2 max_requests
my $max = $daemon->max_requests;
$daemon = $daemon->max_requests(100);
Maximum number of keep-alive requests per connection, defaults to C<25>.
=head2 silent
my $bool = $daemon->silent;
$daemon = $daemon->silent($bool);
Disable console messages.
=head1 METHODS
L<Mojo::Server::Daemon> inherits all methods from L<Mojo::Server> and
implements the following new ones.
=head2 run
$daemon->run;
Run server.
=head2 start
$daemon = $daemon->start;
Start accepting connections.
# Listen on random port
my $id = $daemon->listen(['http://127.0.0.1'])->start->acceptors->[0];
my $port = $daemon->ioloop->acceptor($id)->handle->sockport;
=head2 stop
$daemon = $daemon->stop;
Stop accepting connections.
=head1 DEBUGGING
You can set the C<MOJO_DAEMON_DEBUG> environment variable to get some advanced
diagnostics information printed to C<STDERR>.
MOJO_DAEMON_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_DAEMON
$fatpacked{"Mojo/Server/Hypnotoad.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_HYPNOTOAD';
package Mojo::Server::Hypnotoad;
use Mojo::Base -base;
# "Bender: I was God once.
# God: Yes, I saw. You were doing well, until everyone died."
use Cwd 'abs_path';
use File::Basename 'dirname';
use File::Spec::Functions 'catfile';
use Mojo::Server::Prefork;
use Mojo::Util 'steady_time';
use Scalar::Util 'weaken';
has prefork => sub { Mojo::Server::Prefork->new };
has upgrade_timeout => 60;
sub configure {
my ($self, $name) = @_;
# Hypnotoad settings
my $prefork = $self->prefork;
my $c = $prefork->app->config($name) || {};
$c->{listen} ||= ['http://*:8080'];
$self->upgrade_timeout($c->{upgrade_timeout}) if $c->{upgrade_timeout};
# Prefork settings
$prefork->reverse_proxy($c->{proxy}) if defined $c->{proxy};
$prefork->max_clients($c->{clients}) if $c->{clients};
$prefork->max_requests($c->{keep_alive_requests})
if $c->{keep_alive_requests};
defined $c->{$_} and $prefork->$_($c->{$_})
for qw(accept_interval accepts backlog graceful_timeout group),
qw(heartbeat_interval heartbeat_timeout inactivity_timeout listen),
qw(lock_file lock_timeout multi_accept pid_file user workers);
}
sub run {
my ($self, $app) = @_;
# No Windows support
_exit('Hypnotoad not available for Windows.') if $^O eq 'MSWin32';
# Remember executable and application for later
$ENV{HYPNOTOAD_EXE} ||= $0;
$0 = $ENV{HYPNOTOAD_APP} ||= abs_path $app;
# This is a production server
$ENV{MOJO_MODE} ||= 'production';
# Clean start (to make sure everything works)
die "Can't exec: $!" if !$ENV{HYPNOTOAD_REV}++ && !exec $ENV{HYPNOTOAD_EXE};
# Preload application and configure server
my $prefork = $self->prefork->cleanup(0);
$prefork->load_app($app)->config->{hypnotoad}{pid_file}
//= catfile dirname($ENV{HYPNOTOAD_APP}), 'hypnotoad.pid';
$self->configure('hypnotoad');
weaken $self;
$prefork->on(wait => sub { $self->_manage });
$prefork->on(reap => sub { $self->_reap(pop) });
$prefork->on(finish => sub { $self->{finished} = 1 });
# Testing
_exit('Everything looks good!') if $ENV{HYPNOTOAD_TEST};
# Stop running server
$self->_stop if $ENV{HYPNOTOAD_STOP};
# Initiate hot deployment
$self->_hot_deploy unless $ENV{HYPNOTOAD_PID};
# Daemonize as early as possible (but not for restarts)
$prefork->daemonize
if !$ENV{HYPNOTOAD_FOREGROUND} && $ENV{HYPNOTOAD_REV} < 3;
# Start accepting connections
local $SIG{USR2} = sub { $self->{upgrade} ||= steady_time };
$prefork->cleanup(1)->run;
}
sub _exit { say shift and exit 0 }
sub _hot_deploy {
# Make sure server is running
return unless my $pid = shift->prefork->check_pid;
# Start hot deployment
kill 'USR2', $pid;
_exit("Starting hot deployment for Hypnotoad server $pid.");
}
sub _manage {
my $self = shift;
# Upgraded
my $log = $self->prefork->app->log;
if ($ENV{HYPNOTOAD_PID} && $ENV{HYPNOTOAD_PID} ne $$) {
$log->info("Upgrade successful, stopping $ENV{HYPNOTOAD_PID}.");
kill 'QUIT', $ENV{HYPNOTOAD_PID};
}
$ENV{HYPNOTOAD_PID} = $$ unless ($ENV{HYPNOTOAD_PID} // '') eq $$;
# Upgrade
if ($self->{upgrade} && !$self->{finished}) {
# Fresh start
unless ($self->{new}) {
$log->info('Starting zero downtime software upgrade.');
die "Can't fork: $!" unless defined(my $pid = $self->{new} = fork);
exec($ENV{HYPNOTOAD_EXE}) or die("Can't exec: $!") unless $pid;
}
# Timeout
kill 'KILL', $self->{new}
if $self->{upgrade} + $self->upgrade_timeout <= steady_time;
}
}
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;
kill 'QUIT', $pid;
_exit("Stopping Hypnotoad server $pid gracefully.");
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Hypnotoad - ALL GLORY TO THE HYPNOTOAD!
=head1 SYNOPSIS
use Mojo::Server::Hypnotoad;
my $hypnotoad = Mojo::Server::Hypnotoad->new;
$hypnotoad->run('/home/sri/myapp.pl');
=head1 DESCRIPTION
L<Mojo::Server::Hypnotoad> is a full featured, UNIX optimized, preforking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Prefork>, with IPv6, TLS, Comet (long polling),
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.
$ hypnotoad ./myapp.pl
Server available at http://127.0.0.1:8080.
You can run the same command again for automatic hot deployment.
$ hypnotoad ./myapp.pl
Starting hot deployment for Hypnotoad server 31841.
This second invocation will load the application again, detect the process id
file with it, and send a L</"USR2"> signal to the already running server.
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 MANAGER SIGNALS
The L<Mojo::Server::Hypnotoad> manager process can be controlled at runtime
with the following signals.
=head2 INT, TERM
Shutdown server immediately.
=head2 QUIT
Shutdown server gracefully.
=head2 TTIN
Increase worker pool by one.
=head2 TTOU
Decrease worker pool by one.
=head2 USR2
Attempt zero downtime software upgrade (hot deployment) without losing any
incoming connections.
Manager (old)
|- Worker [1]
|- Worker [2]
|- Worker [3]
|- Worker [4]
+- Manager (new)
|- Worker [1]
|- Worker [2]
|- Worker [3]
+- Worker [4]
The new manager will automatically send a L</"QUIT"> signal to the old manager
and take over serving requests after starting up successfully.
=head1 WORKER SIGNALS
L<Mojo::Server::Hypnotoad> worker processes can be controlled at runtime with
the following signals.
=head2 INT, TERM
Stop worker immediately.
=head2 QUIT
Stop worker gracefully.
=head1 SETTINGS
L<Mojo::Server::Hypnotoad> can be configured with the following settings, see
L<Mojolicious::Guides::Cookbook/"Hypnotoad"> for examples.
=head2 accept_interval
accept_interval => 0.5
Interval in seconds for trying to reacquire the accept mutex, defaults to the
value of L<Mojo::IOLoop/"accept_interval">. Note that changing this value can
affect performance and idle CPU usage.
=head2 accepts
accepts => 100
Maximum number of connections a worker is allowed to accept before stopping
gracefully, defaults to the value of L<Mojo::Server::Prefork/"accepts">.
Setting the value to C<0> will allow workers to accept new connections
indefinitely. Note that up to half of this value can be subtracted randomly to
improve load balancing.
=head2 backlog
backlog => 128
Listen backlog size, defaults to the value of
L<Mojo::Server::Daemon/"backlog">.
=head2 clients
clients => 100
Maximum number of concurrent client connections per worker process, defaults
to the value of L<Mojo::IOLoop/"max_connections">. Note that high concurrency
works best with applications that perform mostly non-blocking operations, to
optimize for blocking operations you can decrease this value and increase
L</"workers"> instead for better performance.
=head2 graceful_timeout
graceful_timeout => 15
Maximum amount of time in seconds stopping a worker gracefully may take before
being forced, defaults to the value of
L<Mojo::Server::Prefork/"graceful_timeout">.
=head2 group
group => 'staff'
Group name for worker processes, defaults to the value of
L<Mojo::Server/"group">.
=head2 heartbeat_interval
heartbeat_interval => 3
Heartbeat interval in seconds, defaults to the value of
L<Mojo::Server::Prefork/"heartbeat_interval">.
=head2 heartbeat_timeout
heartbeat_timeout => 2
Maximum amount of time in seconds before a worker without a heartbeat will be
stopped gracefully, defaults to the value of
L<Mojo::Server::Prefork/"heartbeat_timeout">.
=head2 inactivity_timeout
inactivity_timeout => 10
Maximum amount of time in seconds a connection can be inactive before getting
closed, defaults to the value of L<Mojo::Server::Daemon/"inactivity_timeout">.
Setting the value to C<0> will allow connections to be inactive indefinitely.
=head2 keep_alive_requests
keep_alive_requests => 50
Number of keep-alive requests per connection, defaults to the value of
L<Mojo::Server::Daemon/"max_requests">.
=head2 listen
listen => ['http://*:80']
List of one or more locations to listen on, defaults to C<http://*:8080>. See
also L<Mojo::Server::Daemon/"listen"> for more examples.
=head2 lock_file
lock_file => '/tmp/hypnotoad.lock'
Full path of accept mutex lock file prefix, to which the process id will be
appended, defaults to the value of L<Mojo::Server::Prefork/"lock_file">.
=head2 lock_timeout
lock_timeout => 0.5
Maximum amount of time in seconds a worker may block when waiting for the
accept mutex, defaults to the value of
L<Mojo::Server::Prefork/"lock_timeout">. Note that changing this value can
affect performance and idle CPU usage.
=head2 multi_accept
multi_accept => 100
Number of connections to accept at once, defaults to the value of
L<Mojo::IOLoop/"multi_accept">.
=head2 pid_file
pid_file => '/var/run/hypnotoad.pid'
Full path to process id file, defaults to C<hypnotoad.pid> in the same
directory as the application. Note that this value can only be changed after
the server has been stopped.
=head2 proxy
proxy => 1
Activate reverse proxy support, which allows for the C<X-Forwarded-For> and
C<X-Forwarded-Proto> headers to be picked up automatically, defaults to the
value of L<Mojo::Server/"reverse_proxy">.
=head2 upgrade_timeout
upgrade_timeout => 45
Maximum amount of time in seconds a zero downtime software upgrade may take
before getting canceled, defaults to the value of L</"upgrade_timeout">.
=head2 user
user => 'sri'
Username for worker processes, defaults to the value of
L<Mojo::Server/"user">.
=head2 workers
workers => 10
Number of worker processes, defaults to the value of
L<Mojo::Server::Prefork/"workers">. A good rule of thumb is two worker
processes per CPU core for applications that perform mostly non-blocking
operations, blocking operations often require more and benefit from decreasing
the number of concurrent L</"clients"> (often as low as C<1>).
=head1 ATTRIBUTES
L<Mojo::Server::Hypnotoad> implements the following attributes.
=head2 prefork
my $prefork = $hypnotoad->prefork;
$hypnotoad = $hypnotoad->prefork(Mojo::Server::Prefork->new);
L<Mojo::Server::Prefork> object this server manages.
=head2 upgrade_timeout
my $timeout = $hypnotoad->upgrade_timeout;
$hypnotoad = $hypnotoad->upgrade_timeout(15);
Maximum amount of time in seconds a zero downtime software upgrade may take
before getting canceled, defaults to C<60>.
=head1 METHODS
L<Mojo::Server::Hypnotoad> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 configure
$hypnotoad->configure('hypnotoad');
Configure server from application settings.
=head2 run
$hypnotoad->run('script/myapp');
Run server for application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_HYPNOTOAD
$fatpacked{"Mojo/Server/Morbo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_MORBO';
package Mojo::Server::Morbo;
use Mojo::Base -base;
# "Linda: With Haley's Comet out of ice, Earth is experiencing the devastating
# effects of sudden, intense global warming.
# Morbo: Morbo is pleased but sticky."
use Mojo::Home;
use Mojo::Server::Daemon;
use POSIX 'WNOHANG';
has watch => sub { [qw(lib templates)] };
sub check {
my $self = shift;
# Discover files
my @files;
for my $watch (@{$self->watch}) {
if (-d $watch) {
my $home = Mojo::Home->new->parse($watch);
push @files, $home->rel_file($_) for @{$home->list_files};
}
elsif (-r $watch) { push @files, $watch }
}
# Check files
$self->_check($_) and return $_ for @files;
return undef;
}
sub run {
my ($self, $app) = @_;
# Clean manager environment
local $SIG{CHLD} = sub { $self->_reap };
local $SIG{INT} = local $SIG{TERM} = local $SIG{QUIT} = sub {
$self->{finished} = 1;
kill 'TERM', $self->{running} if $self->{running};
};
unshift @{$self->watch}, $app;
$self->{modified} = 1;
# 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};
exit 0;
}
sub _check {
my ($self, $file) = @_;
# Check if modify time and/or size have changed
my ($size, $mtime) = (stat $file)[7, 9];
return undef unless defined $mtime;
my $cache = $self->{cache} ||= {};
my $stats = $cache->{$file} ||= [$^T, $size];
return undef if $mtime <= $stats->[0] && $size == $stats->[1];
return !!($cache->{$file} = [$mtime, $size]);
}
sub _manage {
my $self = shift;
if (defined(my $file = $self->check)) {
say qq{File "$file" changed, restarting.} if $ENV{MORBO_VERBOSE};
kill 'TERM', $self->{running} if $self->{running};
$self->{modified} = 1;
}
$self->_reap;
delete $self->{running} if $self->{running} && !kill 0, $self->{running};
$self->_spawn if !$self->{running} && delete $self->{modified};
sleep 1;
}
sub _reap { delete $_[0]{running} while (waitpid -1, WNOHANG) > 0 }
sub _spawn {
my $self = shift;
# Fork
my $manager = $$;
$ENV{MORBO_REV}++;
die "Can't fork: $!" unless defined(my $pid = fork);
# Manager
return $self->{running} = $pid if $pid;
# Worker
$SIG{CHLD} = 'DEFAULT';
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub { $self->{finished} = 1 };
my $daemon = Mojo::Server::Daemon->new;
$daemon->load_app($self->watch->[0]);
$daemon->silent(1) if $ENV{MORBO_REV} > 1;
$daemon->start;
my $loop = $daemon->ioloop;
$loop->recurring(
1 => sub { shift->stop if !kill(0, $manager) || $self->{finished} });
$loop->start;
exit 0;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Morbo - DOOOOOOOOOOOOOOOOOOM!
=head1 SYNOPSIS
use Mojo::Server::Morbo;
my $morbo = Mojo::Server::Morbo->new;
$morbo->run('/home/sri/myapp.pl');
=head1 DESCRIPTION
L<Mojo::Server::Morbo> is a full featured, self-restart capable non-blocking
I/O HTTP and WebSocket server, built around the very well tested and reliable
L<Mojo::Server::Daemon>, with IPv6, TLS, Comet (long polling), keep-alive and
multiple event loop support. 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<morbo> script.
$ morbo ./myapp.pl
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 ATTRIBUTES
L<Mojo::Server::Morbo> implements the following attributes.
=head2 watch
my $watch = $morbo->watch;
$morbo = $morbo->watch(['/home/sri/myapp']);
Files and directories to watch for changes, defaults to the application script
as well as the C<lib> and C<templates> directories in the current working
directory.
=head1 METHODS
L<Mojo::Server::Morbo> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 check
my $file = $morbo->check;
Check if file from L</"watch"> has been modified since last check and return
its name or C<undef> if there have been no changes.
=head2 run
$morbo->run('script/myapp');
Run server for application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_MORBO
$fatpacked{"Mojo/Server/PSGI.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_PSGI';
package Mojo::Server::PSGI;
use Mojo::Base 'Mojo::Server';
sub run {
my ($self, $env) = @_;
my $tx = $self->build_tx;
my $req = $tx->req->parse($env);
$tx->local_port($env->{SERVER_PORT})->remote_address($env->{REMOTE_ADDR});
# Request body (may block if we try to read too much)
my $len = $env->{CONTENT_LENGTH};
until ($req->is_finished) {
my $chunk = ($len && $len < 131072) ? $len : 131072;
last unless my $read = $env->{'psgi.input'}->read(my $buffer, $chunk, 0);
$req->parse($buffer);
last if ($len -= $read) <= 0;
}
# Handle request
$self->emit(request => $tx);
# Response headers
my $res = $tx->res->fix_headers;
my $hash = $res->headers->to_hash(1);
my @headers;
for my $name (keys %$hash) {
push @headers, map { $name => $_ } @{$hash->{$name}};
}
# PSGI response
my $io = Mojo::Server::PSGI::_IO->new(tx => $tx, empty => $tx->is_empty);
return [$res->code // 404, \@headers, $io];
}
sub to_psgi_app {
my $self = shift;
# Preload application and wrap it
$self->app;
return sub { $self->run(@_) }
}
package Mojo::Server::PSGI::_IO;
use Mojo::Base -base;
# Finish transaction
sub close { shift->{tx}->server_close }
sub getline {
my $self = shift;
# Empty
return undef if $self->{empty};
# No content yet, try again later
my $chunk = $self->{tx}->res->get_body_chunk($self->{offset} //= 0);
return '' unless defined $chunk;
# End of content
return undef unless length $chunk;
$self->{offset} += length $chunk;
return $chunk;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::PSGI - PSGI server
=head1 SYNOPSIS
use Mojo::Server::PSGI;
my $psgi = Mojo::Server::PSGI->new;
$psgi->unsubscribe('request');
$psgi->on(request => sub {
my ($psgi, $tx) = @_;
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
my $app = $psgi->to_psgi_app;
=head1 DESCRIPTION
L<Mojo::Server::PSGI> allows L<Mojolicious> applications to run on all L<PSGI>
compatible servers.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 EVENTS
L<Mojo::Server::PSGI> inherits all events from L<Mojo::Server>.
=head1 ATTRIBUTES
L<Mojo::Server::PSGI> inherits all attributes from L<Mojo::Server>.
=head1 METHODS
L<Mojo::Server::PSGI> inherits all methods from L<Mojo::Server> and implements
the following new ones.
=head2 run
my $res = $psgi->run($env);
Run L<PSGI>.
=head2 to_psgi_app
my $app = $psgi->to_psgi_app;
Turn L<Mojo> application into L<PSGI> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_PSGI
$fatpacked{"Mojo/Server/Prefork.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_SERVER_PREFORK';
package Mojo::Server::Prefork;
use Mojo::Base 'Mojo::Server::Daemon';
use Fcntl ':flock';
use File::Spec::Functions qw(catfile tmpdir);
use IO::Poll qw(POLLIN POLLPRI);
use List::Util 'shuffle';
use Mojo::Util 'steady_time';
use POSIX 'WNOHANG';
use Scalar::Util 'weaken';
use Time::HiRes ();
has accepts => 1000;
has [qw(accept_interval multi_accept)];
has [qw(cleanup lock_timeout)] => 1;
has [qw(graceful_timeout heartbeat_timeout)] => 20;
has heartbeat_interval => 5;
has lock_file => sub { catfile tmpdir, 'prefork.lock' };
has pid_file => sub { catfile tmpdir, 'prefork.pid' };
has workers => 4;
sub DESTROY {
my $self = shift;
# Worker
return unless $self->cleanup;
# Manager
if (my $file = $self->{lock_file}) { unlink $file if -w $file }
if (my $file = $self->pid_file) { unlink $file if -w $file }
}
sub check_pid {
my $file = shift->pid_file;
return undef unless open my $handle, '<', $file;
my $pid = <$handle>;
chomp $pid;
# Running
return $pid if $pid && kill 0, $pid;
# Not running
unlink $file if -w $file;
return undef;
}
sub ensure_pid_file {
my $self = shift;
# Check if PID file already exists
return if -e (my $file = $self->pid_file);
# Create PID 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 run {
my $self = shift;
# No Windows support
say 'Preforking not available for Windows.' and exit 0 if $^O eq 'MSWin32';
# Prepare lock file and event loop
$self->{lock_file} = $self->lock_file . ".$$";
my $loop = $self->ioloop->max_accepts($self->accepts);
$loop->$_($self->$_ // $loop->$_) for qw(accept_interval multi_accept);
# Pipe for worker communication
pipe($self->{reader}, $self->{writer}) or die "Can't create pipe: $!";
$self->{poll} = IO::Poll->new;
$self->{poll}->mask($self->{reader}, POLLIN | POLLPRI);
# Clean manager environment
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};
}
};
local $SIG{QUIT} = sub { $self->_term(1) };
local $SIG{TTIN} = sub { $self->workers($self->workers + 1) };
local $SIG{TTOU} = sub {
$self->workers($self->workers - 1) if $self->workers > 0;
return unless $self->workers;
$self->{pool}{shuffle keys %{$self->{pool}}}{graceful} ||= steady_time;
};
# Preload application before starting workers
$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 _manage {
my $self = shift;
# Spawn more workers and check PID file
if (!$self->{finished}) {
$self->_spawn while keys %{$self->{pool}} < $self->workers;
$self->ensure_pid_file;
}
# Shutdown
elsif (!keys %{$self->{pool}}) { return delete $self->{running} }
# Wait for heartbeats
$self->emit('wait')->_heartbeat;
my $interval = $self->heartbeat_interval;
my $ht = $self->heartbeat_timeout;
my $gt = $self->graceful_timeout;
my $time = steady_time;
my $log = $self->app->log;
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.")
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
if $graceful && !$w->{quit}++;
$w->{force} = 1 if $graceful && $graceful + $gt <= $time;
# Normal stop
$log->debug("Stopping worker $pid.") and kill 'KILL', $pid
if $w->{force} || ($self->{finished} && !$graceful);
}
}
sub _spawn {
my $self = shift;
# Manager
die "Can't fork: $!" unless defined(my $pid = fork);
return $self->emit(spawn => $pid)->{pool}{$pid} = {time => steady_time}
if $pid;
# Prepare lock file
my $file = $self->{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);
# Accept mutex
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 $lock;
}
);
$loop->unlock(sub { flock $handle, LOCK_UN });
# Heartbeat messages (stop sending during graceful stop)
weaken $self;
$loop->recurring(
$self->heartbeat_interval => sub {
my $graceful = shift->max_connections ? 0 : 1;
$self->{writer}->syswrite("$$:$graceful\n") or exit 0;
}
);
# 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.");
$loop->start;
exit 0;
}
sub _term {
my ($self, $graceful) = @_;
$self->emit(finish => $graceful)->{finished} = 1;
$self->{graceful} = 1 if $graceful;
}
1;
=encoding utf8
=head1 NAME
Mojo::Server::Prefork - Preforking non-blocking I/O HTTP and WebSocket server
=head1 SYNOPSIS
use Mojo::Server::Prefork;
my $prefork = Mojo::Server::Prefork->new(listen => ['http://*:8080']);
$prefork->unsubscribe('request');
$prefork->on(request => sub {
my ($prefork, $tx) = @_;
# Request
my $method = $tx->req->method;
my $path = $tx->req->url->path;
# Response
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body("$method request for $path!");
# Resume transaction
$tx->resume;
});
$prefork->run;
=head1 DESCRIPTION
L<Mojo::Server::Prefork> is a full featured, UNIX optimized, preforking
non-blocking I/O HTTP and WebSocket server, built around the very well tested
and reliable L<Mojo::Server::Daemon>, with IPv6, TLS, Comet (long polling),
keep-alive and multiple event loop support. Note that the server uses signals
for process management, so you should avoid modifying signal handlers in your
applications.
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"DEPLOYMENT"> for more.
=head1 MANAGER SIGNALS
The L<Mojo::Server::Prefork> manager process can be controlled at runtime with
the following signals.
=head2 INT, TERM
Shutdown server immediately.
=head2 QUIT
Shutdown server gracefully.
=head2 TTIN
Increase worker pool by one.
=head2 TTOU
Decrease worker pool by one.
=head1 WORKER SIGNALS
L<Mojo::Server::Prefork> worker processes can be controlled at runtime with
the following signals.
=head2 INT, TERM
Stop worker immediately.
=head2 QUIT
Stop worker gracefully.
=head1 EVENTS
L<Mojo::Server::Prefork> inherits all events from L<Mojo::Server::Daemon> and
can emit the following new ones.
=head2 finish
$prefork->on(finish => sub {
my ($prefork, $graceful) = @_;
...
});
Emitted when the server shuts down.
$prefork->on(finish => sub {
my ($prefork, $graceful) = @_;
say $graceful ? 'Graceful server shutdown' : 'Server shutdown';
});
=head2 heartbeat
$prefork->on(heartbeat => sub {
my ($prefork, $pid) = @_;
...
});
Emitted when a heartbeat message has been received from a worker.
$prefork->on(heartbeat => sub {
my ($prefork, $pid) = @_;
say "Worker $pid has a heartbeat";
});
=head2 reap
$prefork->on(reap => sub {
my ($prefork, $pid) = @_;
...
});
Emitted when a child process dies.
$prefork->on(reap => sub {
my ($prefork, $pid) = @_;
say "Worker $pid stopped";
});
=head2 spawn
$prefork->on(spawn => sub {
my ($prefork, $pid) = @_;
...
});
Emitted when a worker process is spawned.
$prefork->on(spawn => sub {
my ($prefork, $pid) = @_;
say "Worker $pid started";
});
=head2 wait
$prefork->on(wait => sub {
my $prefork = shift;
...
});
Emitted when the manager starts waiting for new heartbeat messages.
$prefork->on(wait => sub {
my $prefork = shift;
my $workers = $prefork->workers;
say "Waiting for heartbeat messages from $workers workers";
});
=head1 ATTRIBUTES
L<Mojo::Server::Prefork> inherits all attributes from L<Mojo::Server::Daemon>
and implements the following new ones.
=head2 accept_interval
my $interval = $prefork->accept_interval;
$prefork = $prefork->accept_interval(0.5);
Interval in seconds for trying to reacquire the accept mutex, passed along to
L<Mojo::IOLoop/"accept_interval">. Note that changing this value can affect
performance and idle CPU usage.
=head2 accepts
my $accepts = $prefork->accepts;
$prefork = $prefork->accepts(100);
Maximum number of connections a worker is allowed to accept before stopping
gracefully, passed along to L<Mojo::IOLoop/"max_accepts">, defaults to
C<1000>. Setting the value to C<0> will allow workers to accept new
connections indefinitely. Note that up to half of this value can be subtracted
randomly to improve load balancing, and that worker processes will stop
sending heartbeat messages once the limit has been reached.
=head2 cleanup
my $bool = $prefork->cleanup;
$prefork = $prefork->cleanup($bool);
Delete L</"lock_file"> and L</"pid_file"> automatically once they are not
needed anymore, defaults to a true value.
=head2 graceful_timeout
my $timeout = $prefork->graceful_timeout;
$prefork = $prefork->graceful_timeout(15);
Maximum amount of time in seconds stopping a worker gracefully may take before
being forced, defaults to C<20>.
=head2 heartbeat_interval
my $interval = $prefork->heartbeat_intrval;
$prefork = $prefork->heartbeat_interval(3);
Heartbeat interval in seconds, defaults to C<5>.
=head2 heartbeat_timeout
my $timeout = $prefork->heartbeat_timeout;
$prefork = $prefork->heartbeat_timeout(2);
Maximum amount of time in seconds before a worker without a heartbeat will be
stopped gracefully, defaults to C<20>.
=head2 lock_file
my $file = $prefork->lock_file;
$prefork = $prefork->lock_file('/tmp/prefork.lock');
Full path of accept mutex lock file prefix, to which the process id will be
appended, defaults to a random temporary path.
=head2 lock_timeout
my $timeout = $prefork->lock_timeout;
$prefork = $prefork->lock_timeout(0.5);
Maximum amount of time in seconds a worker may block when waiting for the
accept mutex, defaults to C<1>. Note that changing this value can affect
performance and idle CPU usage.
=head2 multi_accept
my $multi = $prefork->multi_accept;
$prefork = $prefork->multi_accept(100);
Number of connections to accept at once, passed along to
L<Mojo::IOLoop/"multi_accept">.
=head2 pid_file
my $file = $prefork->pid_file;
$prefork = $prefork->pid_file('/tmp/prefork.pid');
Full path of process id file, defaults to a random temporary path.
=head2 workers
my $workers = $prefork->workers;
$prefork = $prefork->workers(10);
Number of worker processes, defaults to C<4>. A good rule of thumb is two
worker processes per CPU core for applications that perform mostly
non-blocking operations, blocking operations often require more and benefit
from decreasing the number of concurrent L<Mojo::Server::Daemon/"clients">
(often as low as C<1>).
=head1 METHODS
L<Mojo::Server::Prefork> inherits all methods from L<Mojo::Server::Daemon> and
implements the following new ones.
=head2 check_pid
my $pid = $prefork->check_pid;
Get process id for running server from L</"pid_file"> or delete it if server
is not running.
say 'Server is not running' unless $prefork->check_pid;
=head2 ensure_pid_file
$prefork->ensure_pid_file;
Ensure L</"pid_file"> exists.
=head2 run
$prefork->run;
Run server.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_SERVER_PREFORK
$fatpacked{"Mojo/Template.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_TEMPLATE';
package Mojo::Template;
use Mojo::Base -base;
use Carp 'croak';
use Mojo::ByteStream;
use Mojo::Exception;
use Mojo::Util qw(decode encode monkey_patch slurp);
use constant DEBUG => $ENV{MOJO_TEMPLATE_DEBUG} || 0;
has [qw(append code prepend template)] => '';
has [qw(auto_escape compiled)];
has capture_end => 'end';
has capture_start => 'begin';
has comment_mark => '#';
has encoding => 'UTF-8';
has escape => sub { \&Mojo::Util::xss_escape };
has [qw(escape_mark expression_mark trim_mark)] => '=';
has [qw(line_start replace_mark)] => '%';
has name => 'template';
has namespace => 'Mojo::Template::SandBox';
has tag_start => '<%';
has tag_end => '%>';
has tree => sub { [] };
sub build {
my $self = shift;
my $tree = $self->tree;
my $escape = $self->auto_escape;
my @blocks = ('');
my ($i, $capture, $multi);
while (++$i <= @$tree && (my $next = $tree->[$i])) {
my ($op, $value) = @{$tree->[$i - 1]};
push @blocks, '' and next if $op eq 'line';
my $newline = chomp($value //= '');
# Text (quote and fix line ending)
if ($op eq 'text') {
$value = join "\n", map { quotemeta $_ } split("\n", $value, -1);
$value .= '\n' if $newline;
$blocks[-1] .= "\$_M .= \"" . $value . "\";" if length $value;
}
# Code or multiline expression
elsif ($op eq 'code' || $multi) { $blocks[-1] .= $value }
# Capture end
elsif ($op eq 'cpen') {
$blocks[-1] .= 'return Mojo::ByteStream->new($_M) }';
# No following code
$blocks[-1] .= ';' if ($next->[1] // '') =~ /^\s*$/;
}
# Expression
if ($op eq 'expr' || $op eq 'escp') {
# Escaped
if (!$multi && ($op eq 'escp' && !$escape || $op eq 'expr' && $escape)) {
$blocks[-1] .= "\$_M .= _escape scalar $value";
}
# Raw
elsif (!$multi) { $blocks[-1] .= "\$_M .= scalar $value" }
# Multiline
$multi = !$next || $next->[0] ne 'text';
# Append semicolon
$blocks[-1] .= ';' unless $multi || $capture;
}
# Capture start
if ($op eq 'cpst') { $capture = 1 }
elsif ($capture) {
$blocks[-1] .= " sub { my \$_M = ''; ";
$capture = 0;
}
}
return $self->code(join "\n", @blocks)->tree([]);
}
sub compile {
my $self = shift;
# Compile with line directive
return undef unless my $code = $self->code;
my $compiled = eval $self->_wrap($code);
$self->compiled($compiled) and return undef unless $@;
# Use local stacktrace for compile exceptions
return Mojo::Exception->new($@, [$self->template, $code])->trace->verbose(1);
}
sub interpret {
my $self = shift;
# Stacktrace
local $SIG{__DIE__} = sub {
CORE::die($_[0]) if ref $_[0];
Mojo::Exception->throw(shift, [$self->template, $self->code]);
};
return undef unless my $compiled = $self->compiled;
my $output;
return $output if eval { $output = $compiled->(@_); 1 };
# Exception with template context
return Mojo::Exception->new($@, [$self->template])->verbose(1);
}
sub parse {
my ($self, $template) = @_;
# Clean start
$self->template($template)->tree(\my @tree);
my $tag = $self->tag_start;
my $replace = $self->replace_mark;
my $expr = $self->expression_mark;
my $escp = $self->escape_mark;
my $cpen = $self->capture_end;
my $cmnt = $self->comment_mark;
my $cpst = $self->capture_start;
my $trim = $self->trim_mark;
my $end = $self->tag_end;
my $start = $self->line_start;
my $token_re = qr/
(
\Q$tag\E(?:\Q$replace\E|\Q$cmnt\E) # Replace
|
\Q$tag$expr\E(?:\Q$escp\E)?(?:\s*\Q$cpen\E(?!\w))? # Expression
|
\Q$tag\E(?:\s*\Q$cpen\E(?!\w))? # Code
|
(?:(?<!\w)\Q$cpst\E\s*)?(?:\Q$trim\E)?\Q$end\E # End
)
/x;
my $cpen_re = qr/^(\Q$tag\E)(?:\Q$expr\E)?(?:\Q$escp\E)?\s*\Q$cpen\E/;
my $end_re = qr/^(?:(\Q$cpst\E)\s*)?(\Q$trim\E)?\Q$end\E$/;
# Split lines
my $op = 'text';
my ($trimming, $capture);
for my $line (split "\n", $template) {
# Turn Perl line into mixed line
if ($op eq 'text' && $line !~ s/^(\s*)\Q$start$replace\E/$1$start/) {
if ($line =~ s/^(\s*)\Q$start\E(?:(\Q$cmnt\E)|(\Q$expr\E))?//) {
# Comment
if ($2) { $line = "$tag$2 $trim$end" }
# Expression or code
else { $line = $3 ? "$1$tag$3$line $end" : "$tag$line $trim$end" }
}
}
# Escaped line ending
$line .= "\n" if $line !~ s/\\\\$/\\\n/ && $line !~ s/\\$//;
# Mixed line
for my $token (split $token_re, $line) {
# Capture end
$capture = 1 if $token =~ s/$cpen_re/$1/;
# End
if ($op ne 'text' && $token =~ $end_re) {
$op = 'text';
# Capture start
splice @tree, -1, 0, ['cpst'] if $1;
# Trim left side
_trim(\@tree) if ($trimming = $2) && @tree > 1;
# Hint at end
push @tree, ['text', ''];
}
# Code
elsif ($token =~ /^\Q$tag\E$/) { $op = 'code' }
# Expression
elsif ($token =~ /^\Q$tag$expr\E$/) { $op = 'expr' }
# Expression that needs to be escaped
elsif ($token =~ /^\Q$tag$expr$escp\E$/) { $op = 'escp' }
# Comment
elsif ($token =~ /^\Q$tag$cmnt\E$/) { $op = 'cmnt' }
# Text (comments are just ignored)
elsif ($op ne 'cmnt') {
# Replace
$token = $tag if $token eq "$tag$replace";
# Trim right side (convert whitespace to line noise)
if ($trimming && $token =~ s/^(\s+)//) {
push @tree, ['code', $1];
$trimming = 0;
}
# Token (with optional capture end)
push @tree, $capture ? ['cpen'] : (), [$op, $token];
$capture = 0;
}
}
# Optimize successive text lines separated by a newline
push @tree, ['line'] and next
if $tree[-4] && $tree[-4][0] ne 'line'
|| (!$tree[-3] || $tree[-3][0] ne 'text' || $tree[-3][1] !~ /\n$/)
|| ($tree[-2][0] ne 'line' || $tree[-1][0] ne 'text');
$tree[-3][1] .= pop(@tree)->[1];
}
return $self;
}
sub render {
my $self = shift;
return $self->parse(shift)->build->compile || $self->interpret(@_);
}
sub render_file {
my ($self, $path) = (shift, shift);
$self->name($path) unless defined $self->{name};
my $template = slurp $path;
my $encoding = $self->encoding;
croak qq{Template "$path" has invalid encoding}
if $encoding && !defined($template = decode $encoding, $template);
return $self->render($template, @_);
}
sub _line {
my $name = shift->name;
$name =~ y/"//d;
return qq{#line @{[shift]} "$name"};
}
sub _trim {
my $tree = shift;
# Skip captures
my $i = $tree->[-2][0] eq 'cpst' || $tree->[-2][0] eq 'cpen' ? -3 : -2;
# Only trim text
return unless $tree->[$i][0] eq 'text';
# Convert whitespace text to line noise
splice @$tree, $i, 0, ['code', $1] if $tree->[$i][1] =~ s/(\s+)$//;
}
sub _wrap {
my ($self, $code) = @_;
# Escape function
monkey_patch $self->namespace, '_escape', $self->escape;
# Wrap lines
my $num = () = $code =~ /\n/g;
my $head = $self->_line(1);
$head .= "\npackage @{[$self->namespace]}; use Mojo::Base -strict;";
$code = "$head sub { my \$_M = ''; @{[$self->prepend]}; { $code\n";
$code .= $self->_line($num + 1) . "\n@{[$self->append]}; } \$_M };";
warn "-- Code for @{[$self->name]}\n@{[encode 'UTF-8', $code]}\n\n" if DEBUG;
return $code;
}
1;
=encoding utf8
=head1 NAME
Mojo::Template - Perl-ish templates!
=head1 SYNOPSIS
use Mojo::Template;
# Simple
my $mt = Mojo::Template->new;
my $output = $mt->render(<<'EOF');
% use Time::Piece;
<!DOCTYPE html>
<html>
<head><title>Simple</title></head>
% my $now = localtime;
<body>Time: <%= $now->hms %></body>
</html>
EOF
say $output;
# More advanced
my $output = $mt->render(<<'EOF', 23, 'foo bar');
% my ($num, $text) = @_;
%= 5 * 5
<!DOCTYPE html>
<html>
<head><title>More advanced</title></head>
<body>
test 123
foo <% my $i = $num + 2; %>
% for (1 .. 23) {
* some text <%= $i++ %>
% }
</body>
</html>
EOF
say $output;
=head1 DESCRIPTION
L<Mojo::Template> is a minimalistic and very Perl-ish template engine,
designed specifically for all those small tasks that come up during big
projects. Like preprocessing a configuration file, generating text from
heredocs and stuff like that.
See L<Mojolicious::Guides::Rendering> for information on how to generate
content with the L<Mojolicious> renderer.
=head1 SYNTAX
For all templates L<strict>, L<warnings>, L<utf8> and Perl 5.10 features are
automatically enabled.
<% Perl code %>
<%= Perl expression, replaced with result %>
<%== Perl expression, replaced with XML escaped result %>
<%# Comment, useful for debugging %>
<%% Replaced with "<%", useful for generating templates %>
% Perl code line, treated as "<% line =%>"
%= Perl expression line, treated as "<%= line %>"
%== Perl expression line, treated as "<%== line %>"
%# Comment line, useful for debugging
%% Replaced with "%", useful for generating templates
Escaping behavior can be reversed with the L</"auto_escape"> attribute, this
is the default in L<Mojolicious> C<.ep> templates for example.
<%= Perl expression, replaced with XML escaped result %>
<%== Perl expression, replaced with result %>
L<Mojo::ByteStream> objects are always excluded from automatic escaping.
% use Mojo::ByteStream 'b';
<%= b('<div>excluded!</div>') %>
Whitespace characters around tags can be trimmed by adding an additional equal
sign to the end of a tag.
<%= All whitespace characters around this expression will be trimmed =%>
Newline characters can be escaped with a backslash.
This is <%= 1 + 1 %> a\
single line
And a backslash in front of a newline character can be escaped with another
backslash.
This will <%= 1 + 1 %> result\\
in multiple\\
lines
You can capture whole template blocks for reuse later with the C<begin> and
C<end> keywords.
<% my $block = begin %>
<% my $name = shift; =%>
Hello <%= $name %>.
<% end %>
<%= $block->('Baerbel') %>
<%= $block->('Wolfgang') %>
Perl lines can also be indented freely.
% my $block = begin
% my $name = shift;
Hello <%= $name %>.
% end
%= $block->('Baerbel')
%= $block->('Wolfgang')
L<Mojo::Template> templates get compiled to a Perl subroutine, that means you
can access arguments simply via C<@_>.
% my ($foo, $bar) = @_;
% my $x = shift;
test 123 <%= $foo %>
The compilation of templates to Perl code can make debugging a bit tricky, but
L<Mojo::Template> will return L<Mojo::Exception> objects that stringify to
error messages with context.
Bareword "xx" not allowed while "strict subs" in use at template line 4.
2: </head>
3: <body>
4: % my $i = 2; xx
5: %= $i * 2
6: </body>
=head1 ATTRIBUTES
L<Mojo::Template> implements the following attributes.
=head2 auto_escape
my $bool = $mt->auto_escape;
$mt = $mt->auto_escape($bool);
Activate automatic escaping.
=head2 append
my $code = $mt->append;
$mt = $mt->append('warn "Processed template"');
Append Perl code to compiled template. Note that this code should not contain
newline characters, or line numbers in error messages might end up being
wrong.
=head2 capture_end
my $end = $mt->capture_end;
$mt = $mt->capture_end('end');
Keyword indicating the end of a capture block, defaults to C<end>.
<% my $block = begin %>
Some data!
<% end %>
=head2 capture_start
my $start = $mt->capture_start;
$mt = $mt->capture_start('begin');
Keyword indicating the start of a capture block, defaults to C<begin>.
<% my $block = begin %>
Some data!
<% end %>
=head2 code
my $code = $mt->code;
$mt = $mt->code($code);
Perl code for template.
=head2 comment_mark
my $mark = $mt->comment_mark;
$mt = $mt->comment_mark('#');
Character indicating the start of a comment, defaults to C<#>.
<%# This is a comment %>
=head2 compiled
my $compiled = $mt->compiled;
$mt = $mt->compiled($compiled);
Compiled template code.
=head2 encoding
my $encoding = $mt->encoding;
$mt = $mt->encoding('UTF-8');
Encoding used for template files.
=head2 escape
my $cb = $mt->escape;
$mt = $mt->escape(sub {...});
A callback used to escape the results of escaped expressions, defaults to
L<Mojo::Util/"xss_escape">.
$mt->escape(sub {
my $str = shift;
return reverse $str;
});
=head2 escape_mark
my $mark = $mt->escape_mark;
$mt = $mt->escape_mark('=');
Character indicating the start of an escaped expression, defaults to C<=>.
<%== $foo %>
=head2 expression_mark
my $mark = $mt->expression_mark;
$mt = $mt->expression_mark('=');
Character indicating the start of an expression, defaults to C<=>.
<%= $foo %>
=head2 line_start
my $start = $mt->line_start;
$mt = $mt->line_start('%');
Character indicating the start of a code line, defaults to C<%>.
% $foo = 23;
=head2 name
my $name = $mt->name;
$mt = $mt->name('foo.mt');
Name of template currently being processed, defaults to C<template>. Note that
this value should not contain quotes or newline characters, or error messages
might end up being wrong.
=head2 namespace
my $namespace = $mt->namespace;
$mt = $mt->namespace('main');
Namespace used to compile templates, defaults to C<Mojo::Template::SandBox>.
Note that namespaces should only be shared very carefully between templates,
since functions and global variables will not be cleared automatically.
=head2 prepend
my $code = $mt->prepend;
$mt = $mt->prepend('my $self = shift;');
Prepend Perl code to compiled template. Note that this code should not contain
newline characters, or line numbers in error messages might end up being
wrong.
=head2 replace_mark
my $mark = $mt->replace_mark;
$mt = $mt->replace_mark('%');
Character used for escaping the start of a tag or line, defaults to C<%>.
<%% my $foo = 23; %>
=head2 tag_start
my $start = $mt->tag_start;
$mt = $mt->tag_start('<%');
Characters indicating the start of a tag, defaults to C<E<lt>%>.
<% $foo = 23; %>
=head2 tag_end
my $end = $mt->tag_end;
$mt = $mt->tag_end('%>');
Characters indicating the end of a tag, defaults to C<%E<gt>>.
<%= $foo %>
=head2 template
my $template = $mt->template;
$mt = $mt->template($template);
Raw unparsed template.
=head2 tree
my $tree = $mt->tree;
$mt = $mt->tree([['text', 'foo'], ['line']]);
Template in parsed form. Note that this structure should only be used very
carefully since it is very dynamic.
=head2 trim_mark
my $mark = $mt->trim_mark;
$mt = $mt->trim_mark('-');
Character activating automatic whitespace trimming, defaults to C<=>.
<%= $foo =%>
=head1 METHODS
L<Mojo::Template> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 build
$mt = $mt->build;
Build Perl L</"code"> from L</"tree">.
=head2 compile
my $exception = $mt->compile;
Compile Perl L</"code"> for template.
=head2 interpret
my $output = $mt->interpret;
my $output = $mt->interpret(@args);
Interpret L</"compiled"> template code.
# Reuse template
say $mt->render('Hello <%= $_[0] %>!', 'Bender');
say $mt->interpret('Fry');
say $mt->interpret('Leela');
=head2 parse
$mt = $mt->parse($template);
Parse template into L</"tree">.
=head2 render
my $output = $mt->render($template);
my $output = $mt->render($template, @args);
Render template.
say $mt->render('Hello <%= $_[0] %>!', 'Bender');
=head2 render_file
my $output = $mt->render_file('/tmp/foo.mt');
my $output = $mt->render_file('/tmp/foo.mt', @args);
Render template file.
=head1 DEBUGGING
You can set the C<MOJO_TEMPLATE_DEBUG> environment variable to get some
advanced diagnostics information printed to C<STDERR>.
MOJO_TEMPLATE_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_TEMPLATE
$fatpacked{"Mojo/Transaction.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_TRANSACTION';
package Mojo::Transaction;
use Mojo::Base 'Mojo::EventEmitter';
use Carp 'croak';
use Mojo::Message::Request;
use Mojo::Message::Response;
has [
qw(kept_alive local_address local_port original_remote_address remote_port)];
has req => sub { Mojo::Message::Request->new };
has res => sub { Mojo::Message::Response->new };
sub client_close {
my ($self, $close) = @_;
# Premature connection close
my $res = $self->res->finish;
if ($close && !$res->code && !$res->error) {
$res->error({message => 'Premature connection close'});
}
# 400/500
elsif ($res->is_status_class(400) || $res->is_status_class(500)) {
$res->error({message => $res->message, code => $res->code});
}
return $self->server_close;
}
sub client_read { croak 'Method "client_read" not implemented by subclass' }
sub client_write { croak 'Method "client_write" not implemented by subclass' }
sub connection {
my $self = shift;
return $self->emit(connection => $self->{connection} = shift) if @_;
return $self->{connection};
}
sub error { $_[0]->req->error || $_[0]->res->error }
sub is_finished { (shift->{state} // '') eq 'finished' }
sub is_websocket {undef}
sub is_writing { (shift->{state} // 'write') eq 'write' }
sub remote_address {
my $self = shift;
return $self->original_remote_address(@_) if @_;
return $self->original_remote_address unless $self->req->reverse_proxy;
# Reverse proxy
return ($self->req->headers->header('X-Forwarded-For') // '')
=~ /([^,\s]+)$/ ? $1 : $self->original_remote_address;
}
sub resume { shift->_state(qw(write resume)) }
sub server_close { shift->_state(qw(finished finish)) }
sub server_read { croak 'Method "server_read" not implemented by subclass' }
sub server_write { croak 'Method "server_write" not implemented by subclass' }
sub success { $_[0]->error ? undef : $_[0]->res }
sub _state {
my ($self, $state, $event) = @_;
$self->{state} = $state;
return $self->emit($event);
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction - Transaction base class
=head1 SYNOPSIS
package Mojo::Transaction::MyTransaction;
use Mojo::Base 'Mojo::Transaction';
sub client_read {...}
sub client_write {...}
sub server_read {...}
sub server_write {...}
=head1 DESCRIPTION
L<Mojo::Transaction> is an abstract base class for transactions.
=head1 EVENTS
L<Mojo::Transaction> inherits all events from L<Mojo::EventEmitter> and can
emit the following new ones.
=head2 connection
$tx->on(connection => sub {
my ($tx, $connection) = @_;
...
});
Emitted when a connection has been assigned to transaction.
=head2 finish
$tx->on(finish => sub {
my $tx = shift;
...
});
Emitted when transaction is finished.
=head2 resume
$tx->on(resume => sub {
my $tx = shift;
...
});
Emitted when transaction is resumed.
=head1 ATTRIBUTES
L<Mojo::Transaction> implements the following attributes.
=head2 kept_alive
my $kept_alive = $tx->kept_alive;
$tx = $tx->kept_alive(1);
Connection has been kept alive.
=head2 local_address
my $address = $tx->local_address;
$tx = $tx->local_address('127.0.0.1');
Local interface address.
=head2 local_port
my $port = $tx->local_port;
$tx = $tx->local_port(8080);
Local interface port.
=head2 original_remote_address
my $address = $tx->original_remote_address;
$tx = $tx->original_remote_address('127.0.0.1');
Remote interface address.
=head2 remote_port
my $port = $tx->remote_port;
$tx = $tx->remote_port(8081);
Remote interface port.
=head2 req
my $req = $tx->req;
$tx = $tx->req(Mojo::Message::Request->new);
HTTP request, defaults to a L<Mojo::Message::Request> object.
=head2 res
my $res = $tx->res;
$tx = $tx->res(Mojo::Message::Response->new);
HTTP response, defaults to a L<Mojo::Message::Response> object.
=head1 METHODS
L<Mojo::Transaction> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 client_close
$tx->client_close;
$tx->client_close(1);
Transaction closed client-side, no actual connection close is assumed by
default, used to implement user agents.
=head2 client_read
$tx->client_read($bytes);
Read data client-side, used to implement user agents. Meant to be overloaded
in a subclass.
=head2 client_write
my $bytes = $tx->client_write;
Write data client-side, used to implement user agents. Meant to be overloaded
in a subclass.
=head2 connection
my $connection = $tx->connection;
$tx = $tx->connection($connection);
Connection identifier or socket.
=head2 error
my $err = $tx->error;
Return transaction error or C<undef> if there is no error, commonly used
together with L</"success">.
=head2 is_finished
my $bool = $tx->is_finished;
Check if transaction is finished.
=head2 is_websocket
my $false = $tx->is_websocket;
False.
=head2 is_writing
my $bool = $tx->is_writing;
Check if transaction is writing.
=head2 resume
$tx = $tx->resume;
Resume transaction.
=head2 remote_address
my $address = $tx->remote_address;
$tx = $tx->remote_address('127.0.0.1');
Same as L</"original_remote_address"> or the last value of the
C<X-Forwarded-For> header if L</"req"> has been performed through a reverse
proxy.
=head2 server_close
$tx->server_close;
Transaction closed server-side, used to implement web servers.
=head2 server_read
$tx->server_read($bytes);
Read data server-side, used to implement web servers. Meant to be overloaded
in a subclass.
=head2 server_write
my $bytes = $tx->server_write;
Write data server-side, used to implement web servers. Meant to be overloaded
in a subclass.
=head2 success
my $res = $tx->success;
Returns the L<Mojo::Message::Response> object from L</"res"> if transaction
was successful or C<undef> otherwise. Connection and parser errors have only a
message in L</"error">, 400 and 500 responses also a code.
# Sensible exception handling
if (my $res = $tx->success) { say $res->body }
else {
my $err = $tx->error;
die "$err->{code} response: $err->{message}" if $err->{code};
die "Connection error: $err->{message}";
}
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_TRANSACTION
$fatpacked{"Mojo/Transaction/HTTP.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_TRANSACTION_HTTP';
package Mojo::Transaction::HTTP;
use Mojo::Base 'Mojo::Transaction';
use Mojo::Transaction::WebSocket;
has 'previous';
sub client_read {
my ($self, $chunk) = @_;
# Skip body for HEAD request
my $res = $self->res;
$res->content->skip_body(1) if uc $self->req->method eq 'HEAD';
return unless $res->parse($chunk)->is_finished;
# Unexpected 1xx response
return $self->{state} = 'finished'
if !$res->is_status_class(100) || $res->headers->upgrade;
$self->res($res->new)->emit(unexpected => $res);
return unless length(my $leftovers = $res->content->leftovers);
$self->client_read($leftovers);
}
sub client_write { shift->_write(0) }
sub is_empty { !!(uc $_[0]->req->method eq 'HEAD' || $_[0]->res->is_empty) }
sub keep_alive {
my $self = shift;
# Close
my $req = $self->req;
my $res = $self->res;
my $req_conn = lc($req->headers->connection // '');
my $res_conn = lc($res->headers->connection // '');
return undef if $req_conn eq 'close' || $res_conn eq 'close';
# Keep-alive is optional for 1.0
return $res_conn eq 'keep-alive' if $res->version eq '1.0';
return $req_conn eq 'keep-alive' if $req->version eq '1.0';
# Keep-alive is the default for 1.1
return 1;
}
sub redirects {
my $previous = shift;
my @redirects;
unshift @redirects, $previous while $previous = $previous->previous;
return \@redirects;
}
sub server_read {
my ($self, $chunk) = @_;
# Parse request
my $req = $self->req;
$req->parse($chunk) unless $req->error;
$self->{state} ||= 'read';
# Generate response
return unless $req->is_finished && !$self->{handled}++;
$self->emit(upgrade => Mojo::Transaction::WebSocket->new(handshake => $self))
if $req->is_handshake;
$self->emit('request');
}
sub server_write { shift->_write(1) }
sub _body {
my ($self, $msg, $finish) = @_;
# Prepare body chunk
my $buffer = $msg->get_body_chunk($self->{offset});
my $written = defined $buffer ? length $buffer : 0;
$self->{write} = $msg->content->is_dynamic ? 1 : ($self->{write} - $written);
$self->{offset} += $written;
if (defined $buffer) { delete $self->{delay} }
# Delayed
else {
if (delete $self->{delay}) { $self->{state} = 'paused' }
else { $self->{delay} = 1 }
}
# Finished
$self->{state} = $finish ? 'finished' : 'read'
if $self->{write} <= 0 || defined $buffer && !length $buffer;
return defined $buffer ? $buffer : '';
}
sub _headers {
my ($self, $msg, $head) = @_;
# Prepare header chunk
my $buffer = $msg->get_header_chunk($self->{offset});
my $written = defined $buffer ? length $buffer : 0;
$self->{write} -= $written;
$self->{offset} += $written;
# Switch to body
if ($self->{write} <= 0) {
$self->{offset} = 0;
# Response without body
if ($head && $self->is_empty) { $self->{state} = 'finished' }
# Body
else {
$self->{http_state} = 'body';
$self->{write} = $msg->content->is_dynamic ? 1 : $msg->body_size;
}
}
return $buffer;
}
sub _start_line {
my ($self, $msg) = @_;
# Prepare start line chunk
my $buffer = $msg->get_start_line_chunk($self->{offset});
my $written = defined $buffer ? length $buffer : 0;
$self->{write} -= $written;
$self->{offset} += $written;
# Switch to headers
@$self{qw(http_state write offset)} = ('headers', $msg->header_size, 0)
if $self->{write} <= 0;
return $buffer;
}
sub _write {
my ($self, $server) = @_;
# Client starts writing right away
$self->{state} ||= 'write' unless $server;
return '' unless $self->{state} eq 'write';
# Nothing written yet
$self->{$_} ||= 0 for qw(offset write);
my $msg = $server ? $self->res : $self->req;
unless ($self->{http_state}) {
# Connection header
my $headers = $msg->headers;
$headers->connection($self->keep_alive ? 'keep-alive' : 'close')
unless $headers->connection;
# Switch to start line
@$self{qw(http_state write)} = ('start_line', $msg->start_line_size);
}
# Start line
my $chunk = '';
$chunk .= $self->_start_line($msg) if $self->{http_state} eq 'start_line';
# Headers
$chunk .= $self->_headers($msg, $server) if $self->{http_state} eq 'headers';
# Body
$chunk .= $self->_body($msg, $server) if $self->{http_state} eq 'body';
return $chunk;
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction::HTTP - HTTP transaction
=head1 SYNOPSIS
use Mojo::Transaction::HTTP;
# Client
my $tx = Mojo::Transaction::HTTP->new;
$tx->req->method('GET');
$tx->req->url->parse('http://example.com');
$tx->req->headers->accept('application/json');
say $tx->res->code;
say $tx->res->headers->content_type;
say $tx->res->body;
say $tx->remote_address;
# Server
my $tx = Mojo::Transaction::HTTP->new;
say $tx->req->method;
say $tx->req->url->to_abs;
say $tx->req->headers->accept;
say $tx->remote_address;
$tx->res->code(200);
$tx->res->headers->content_type('text/plain');
$tx->res->body('Hello World!');
=head1 DESCRIPTION
L<Mojo::Transaction::HTTP> is a container for HTTP transactions based on
L<RFC 7230|http://tools.ietf.org/html/rfc7230> and
L<RFC 7231|http://tools.ietf.org/html/rfc7231>.
=head1 EVENTS
L<Mojo::Transaction::HTTP> inherits all events from L<Mojo::Transaction> and
can emit the following new ones.
=head2 request
$tx->on(request => sub {
my $tx = shift;
...
});
Emitted when a request is ready and needs to be handled.
$tx->on(request => sub {
my $tx = shift;
$tx->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
});
=head2 unexpected
$tx->on(unexpected => sub {
my ($tx, $res) = @_;
...
});
Emitted for unexpected C<1xx> responses that will be ignored.
$tx->on(unexpected => sub {
my $tx = shift;
$tx->res->on(finish => sub { say 'Followup response is finished.' });
});
=head2 upgrade
$tx->on(upgrade => sub {
my ($tx, $ws) = @_;
...
});
Emitted when transaction gets upgraded to a L<Mojo::Transaction::WebSocket>
object.
$tx->on(upgrade => sub {
my ($tx, $ws) = @_;
$ws->res->headers->header('X-Bender' => 'Bite my shiny metal ass!');
});
=head1 ATTRIBUTES
L<Mojo::Transaction::HTTP> inherits all attributes from L<Mojo::Transaction>
and implements the following new ones.
=head2 previous
my $previous = $tx->previous;
$tx = $tx->previous(Mojo::Transaction::HTTP->new);
Previous transaction that triggered this followup transaction, usually a
L<Mojo::Transaction::HTTP> object.
# Paths of previous requests
say $tx->previous->previous->req->url->path;
say $tx->previous->req->url->path;
=head1 METHODS
L<Mojo::Transaction::HTTP> inherits all methods from L<Mojo::Transaction> and
implements the following new ones.
=head2 client_read
$tx->client_read($bytes);
Read data client-side, used to implement user agents.
=head2 client_write
my $bytes = $tx->client_write;
Write data client-side, used to implement user agents.
=head2 is_empty
my $bool = $tx->is_empty;
Check transaction for C<HEAD> request and C<1xx>, C<204> or C<304> response.
=head2 keep_alive
my $bool = $tx->keep_alive;
Check if connection can be kept alive.
=head2 redirects
my $redirects = $tx->redirects;
Return a list of all previous transactions that preceded this followup
transaction.
# Paths of all previous requests
say $_->req->url->path for @{$tx->redirects};
=head2 server_read
$tx->server_read($bytes);
Read data server-side, used to implement web servers.
=head2 server_write
my $bytes = $tx->server_write;
Write data server-side, used to implement web servers.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_TRANSACTION_HTTP
$fatpacked{"Mojo/Transaction/WebSocket.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_TRANSACTION_WEBSOCKET';
package Mojo::Transaction::WebSocket;
use Mojo::Base 'Mojo::Transaction';
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 constant DEBUG => $ENV{MOJO_WEBSOCKET_DEBUG} || 0;
# Perl with support for quads
use constant MODERN =>
(($Config{use64bitint} // '') eq 'define' || $Config{longsize} >= 8);
# Unique value from RFC 6455
use constant GUID => '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
# Opcodes
use constant {
CONTINUATION => 0x0,
TEXT => 0x1,
BINARY => 0x2,
CLOSE => 0x8,
PING => 0x9,
PONG => 0xa
};
has [qw(compressed masked)];
has handshake => sub { Mojo::Transaction::HTTP->new };
has max_websocket_size => sub { $ENV{MOJO_MAX_WEBSOCKET_SIZE} || 262144 };
sub build_frame {
my ($self, $fin, $rsv1, $rsv2, $rsv3, $op, $payload) = @_;
warn "-- Building frame ($fin, $rsv1, $rsv2, $rsv3, $op)\n" if DEBUG;
# Head
my $head = $op + ($fin ? 128 : 0);
$head |= 0b01000000 if $rsv1;
$head |= 0b00100000 if $rsv2;
$head |= 0b00010000 if $rsv3;
my $frame = pack 'C', $head;
# Small payload
my $len = length $payload;
my $masked = $self->masked;
if ($len < 126) {
warn "-- Small payload ($len)\n$payload\n" 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;
$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;
$frame .= pack 'C', $masked ? (127 | 128) : 127;
$frame .= MODERN ? pack('Q>', $len) : pack('NN', 0, $len & 0xffffffff);
}
# Mask payload
if ($masked) {
my $mask = pack 'N', int(rand 9 x 7);
$payload = $mask . xor_encode($payload, $mask x 128);
}
return $frame . $payload;
}
sub build_message {
my ($self, $frame) = @_;
# Text
$frame = {text => encode('UTF-8', $frame)} if ref $frame ne 'HASH';
# JSON
$frame->{text} = encode_json($frame->{json}) if exists $frame->{json};
# Raw text or binary
if (exists $frame->{text}) { $frame = [1, 0, 0, 0, TEXT, $frame->{text}] }
else { $frame = [1, 0, 0, 0, BINARY, $frame->{binary}] }
# "permessage-deflate" extension
return $self->build_frame(@$frame) unless $self->compressed;
my $deflate = $self->{deflate} ||= Compress::Raw::Zlib::Deflate->new(
AppendOutput => 1,
MemLevel => 8,
WindowBits => -15
);
$deflate->deflate($frame->[5], my $out);
$deflate->flush($out, Z_SYNC_FLUSH);
@$frame[1, 5] = (1, substr($out, 0, length($out) - 4));
return $self->build_frame(@$frame);
}
sub client_challenge {
my $self = shift;
# "permessage-deflate" extension
my $headers = $self->res->headers;
$self->compressed(1)
if ($headers->sec_websocket_extensions // '') =~ /permessage-deflate/;
return _challenge($self->req->headers->sec_websocket_key) eq
$headers->sec_websocket_accept;
}
sub client_handshake {
my $self = shift;
my $headers = $self->req->headers;
$headers->upgrade('websocket') unless $headers->upgrade;
$headers->connection('Upgrade') unless $headers->connection;
$headers->sec_websocket_version(13) unless $headers->sec_websocket_version;
# Generate 16 byte WebSocket challenge
my $challenge = b64_encode sprintf('%16u', int(rand 9 x 16)), '';
$headers->sec_websocket_key($challenge) unless $headers->sec_websocket_key;
}
sub client_read { shift->server_read(@_) }
sub client_write { shift->server_write(@_) }
sub connection { shift->handshake->connection }
sub finish {
my $self = shift;
my $close = $self->{close} = [@_];
my $payload = $close->[0] ? pack('n', $close->[0]) : '';
$payload .= encode 'UTF-8', $close->[1] if defined $close->[1];
$close->[0] //= 1005;
$self->send([1, 0, 0, 0, CLOSE, $payload])->{finished} = 1;
return $self;
}
sub is_websocket {1}
sub kept_alive { shift->handshake->kept_alive }
sub local_address { shift->handshake->local_address }
sub local_port { shift->handshake->local_port }
sub new {
my $self = shift->SUPER::new(@_);
$self->on(frame => sub { shift->_message(@_) });
return $self;
}
sub parse_frame {
my ($self, $buffer) = @_;
# Head
return undef unless length $$buffer >= 2;
my ($first, $second) = unpack 'C*', substr($$buffer, 0, 2);
# FIN
my $fin = ($first & 0b10000000) == 0b10000000 ? 1 : 0;
# RSV1-3
my $rsv1 = ($first & 0b01000000) == 0b01000000 ? 1 : 0;
my $rsv2 = ($first & 0b00100000) == 0b00100000 ? 1 : 0;
my $rsv3 = ($first & 0b00010000) == 0b00010000 ? 1 : 0;
# Opcode
my $op = $first & 0b00001111;
warn "-- Parsing frame ($fin, $rsv1, $rsv2, $rsv3, $op)\n" if DEBUG;
# Small payload
my ($hlen, $len) = (2, $second & 0b01111111);
if ($len < 126) { warn "-- Small payload ($len)\n" if DEBUG }
# Extended payload (16-bit)
elsif ($len == 126) {
return undef unless length $$buffer > 4;
$hlen = 4;
$len = unpack 'n', substr($$buffer, 2, 2);
warn "-- Extended 16-bit payload ($len)\n" if DEBUG;
}
# Extended payload (64-bit with 32-bit fallback)
elsif ($len == 127) {
return undef unless length $$buffer > 10;
$hlen = 10;
my $ext = substr $$buffer, 2, 8;
$len = MODERN ? unpack('Q>', $ext) : unpack('N', substr($ext, 4, 4));
warn "-- Extended 64-bit payload ($len)\n" if DEBUG;
}
# Check message size
$self->finish(1009) and return undef if $len > $self->max_websocket_size;
# Check if whole packet has arrived
$len += 4 if my $masked = $second & 0b10000000;
return undef if length $$buffer < ($hlen + $len);
substr $$buffer, 0, $hlen, '';
# 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;
return [$fin, $rsv1, $rsv2, $rsv3, $op, $payload];
}
sub remote_address { shift->handshake->remote_address }
sub remote_port { shift->handshake->remote_port }
sub req { shift->handshake->req }
sub res { shift->handshake->res }
sub resume {
my $self = shift;
$self->handshake->resume;
return $self;
}
sub send {
my ($self, $msg, $cb) = @_;
$self->once(drain => $cb) if $cb;
if (ref $msg eq 'ARRAY') { $self->{write} .= $self->build_frame(@$msg) }
else { $self->{write} .= $self->build_message($msg) }
$self->{state} = 'write';
return $self->emit('resume');
}
sub server_close {
my $self = shift;
$self->{state} = 'finished';
return $self->emit(finish => $self->{close} ? (@{$self->{close}}) : 1006);
}
sub server_handshake {
my $self = shift;
my $res_headers = $self->res->code(101)->headers;
$res_headers->upgrade('websocket')->connection('Upgrade');
my $req_headers = $self->req->headers;
($req_headers->sec_websocket_protocol // '') =~ /^\s*([^,]+)/
and $res_headers->sec_websocket_protocol($1);
$res_headers->sec_websocket_accept(
_challenge($req_headers->sec_websocket_key));
}
sub server_read {
my ($self, $chunk) = @_;
$self->{read} .= $chunk // '';
while (my $frame = $self->parse_frame(\$self->{read})) {
$self->emit(frame => $frame);
}
$self->emit('resume');
}
sub server_write {
my $self = shift;
unless (length($self->{write} // '')) {
$self->{state} = $self->{finished} ? 'finished' : 'read';
$self->emit('drain');
}
return delete $self->{write} // '';
}
sub with_compression {
my $self = shift;
# "permessage-deflate" extension
$self->compressed(1)
and $self->res->headers->sec_websocket_extensions('permessage-deflate')
if ($self->req->headers->sec_websocket_extensions // '')
=~ /permessage-deflate/;
}
sub _challenge { b64_encode(sha1_bytes(($_[0] || '') . GUID), '') }
sub _message {
my ($self, $frame) = @_;
# Assume continuation
my $op = $frame->[4] || CONTINUATION;
# Ping/Pong
return $self->send([1, 0, 0, 0, PONG, $frame->[5]]) if $op == PING;
return if $op == PONG;
# Close
if ($op == CLOSE) {
return $self->finish unless length $frame->[5] >= 2;
return $self->finish(unpack('n', substr($frame->[5], 0, 2, '')),
decode('UTF-8', $frame->[5]));
}
# Append chunk and check message size
$self->{op} = $op unless exists $self->{op};
$self->{message} .= $frame->[5];
my $max = $self->max_websocket_size;
return $self->finish(1009) if length $self->{message} > $max;
# No FIN bit (Continuation)
return unless $frame->[0];
# "permessage-deflate" extension (handshake and RSV1)
my $msg = delete $self->{message};
if ($self->compressed && $frame->[1]) {
my $inflate = $self->{inflate} ||= Compress::Raw::Zlib::Inflate->new(
Bufsize => $max,
LimitOutput => 1,
WindowBits => -15
);
$inflate->inflate(($msg .= "\x00\x00\xff\xff"), my $out);
return $self->finish(1009) if length $msg;
$msg = $out;
}
$self->emit(json => j($msg)) if $self->has_subscribers('json');
$op = delete $self->{op};
$self->emit($op == TEXT ? 'text' : 'binary' => $msg);
$self->emit(message => $op == TEXT ? decode('UTF-8', $msg) : $msg)
if $self->has_subscribers('message');
}
1;
=encoding utf8
=head1 NAME
Mojo::Transaction::WebSocket - WebSocket transaction
=head1 SYNOPSIS
use Mojo::Transaction::WebSocket;
# Send and receive WebSocket messages
my $ws = Mojo::Transaction::WebSocket->new;
$ws->send('Hello World!');
$ws->on(message => sub {
my ($ws, $msg) = @_;
say "Message: $msg";
});
$ws->on(finish => sub {
my ($ws, $code, $reason) = @_;
say "WebSocket closed with status $code.";
});
=head1 DESCRIPTION
L<Mojo::Transaction::WebSocket> is a container for WebSocket transactions
based on L<RFC 6455|http://tools.ietf.org/html/rfc6455>. Note that 64-bit
frames require a Perl with support for quads or they are limited to 32-bit.
=head1 EVENTS
L<Mojo::Transaction::WebSocket> inherits all events from L<Mojo::Transaction>
and can emit the following new ones.
=head2 binary
$ws->on(binary => sub {
my ($ws, $bytes) = @_;
...
});
Emitted when a complete WebSocket binary message has been received.
$ws->on(binary => sub {
my ($ws, $bytes) = @_;
say "Binary: $bytes";
});
=head2 drain
$ws->on(drain => sub {
my $ws = shift;
...
});
Emitted once all data has been sent.
$ws->on(drain => sub {
my $ws = shift;
$ws->send(time);
});
=head2 finish
$ws->on(finish => sub {
my ($ws, $code, $reason) = @_;
...
});
Emitted when transaction is finished.
=head2 frame
$ws->on(frame => sub {
my ($ws, $frame) = @_;
...
});
Emitted when a WebSocket frame has been received.
$ws->unsubscribe('frame');
$ws->on(frame => sub {
my ($ws, $frame) = @_;
say "FIN: $frame->[0]";
say "RSV1: $frame->[1]";
say "RSV2: $frame->[2]";
say "RSV3: $frame->[3]";
say "Opcode: $frame->[4]";
say "Payload: $frame->[5]";
});
=head2 json
$ws->on(json => sub {
my ($ws, $json) = @_;
...
});
Emitted when a complete WebSocket message has been received, all text and
binary messages will be automatically JSON decoded. Note that this event only
gets emitted when it has at least one subscriber.
$ws->on(json => sub {
my ($ws, $hash) = @_;
say "Message: $hash->{msg}";
});
=head2 message
$ws->on(message => sub {
my ($ws, $msg) = @_;
...
});
Emitted when a complete WebSocket message has been received, text messages
will be automatically decoded. Note that this event only gets emitted when it
has at least one subscriber.
$ws->on(message => sub {
my ($ws, $msg) = @_;
say "Message: $msg";
});
=head2 text
$ws->on(text => sub {
my ($ws, $bytes) = @_;
...
});
Emitted when a complete WebSocket text message has been received.
$ws->on(text => sub {
my ($ws, $bytes) = @_;
say "Text: $bytes";
});
=head1 ATTRIBUTES
L<Mojo::Transaction::WebSocket> inherits all attributes from
L<Mojo::Transaction> and implements the following new ones.
=head2 compressed
my $bool = $ws->compressed;
$ws = $ws->compressed($bool);
Compress messages with C<permessage-deflate> extension.
=head2 handshake
my $handshake = $ws->handshake;
$ws = $ws->handshake(Mojo::Transaction::HTTP->new);
The original handshake transaction, defaults to a L<Mojo::Transaction::HTTP>
object.
=head2 masked
my $bool = $ws->masked;
$ws = $ws->masked($bool);
Mask outgoing frames with XOR cipher and a random 32-bit key.
=head2 max_websocket_size
my $size = $ws->max_websocket_size;
$ws = $ws->max_websocket_size(1024);
Maximum WebSocket message size in bytes, defaults to the value of the
C<MOJO_MAX_WEBSOCKET_SIZE> environment variable or C<262144> (256KB).
=head1 METHODS
L<Mojo::Transaction::WebSocket> inherits all methods from
L<Mojo::Transaction> and implements the following new ones.
=head2 build_frame
my $bytes = $ws->build_frame($fin, $rsv1, $rsv2, $rsv3, $op, $payload);
Build WebSocket frame.
# Binary frame with FIN bit and payload
say $ws->build_frame(1, 0, 0, 0, 2, 'Hello World!');
# Text frame with payload but without FIN bit
say $ws->build_frame(0, 0, 0, 0, 1, 'Hello ');
# Continuation frame with FIN bit and payload
say $ws->build_frame(1, 0, 0, 0, 0, 'World!');
# Close frame with FIN bit and without payload
say $ws->build_frame(1, 0, 0, 0, 8, '');
# Ping frame with FIN bit and payload
say $ws->build_frame(1, 0, 0, 0, 9, 'Test 123');
# Pong frame with FIN bit and payload
say $ws->build_frame(1, 0, 0, 0, 10, 'Test 123');
=head2 build_message
my $bytes = $ws->build_message({binary => $bytes});
my $bytes = $ws->build_message({text => $bytes});
my $bytes = $ws->build_message({json => {test => [1, 2, 3]}});
my $bytes = $ws->build_message($chars);
Build WebSocket message.
=head2 client_challenge
my $bool = $ws->client_challenge;
Check WebSocket handshake challenge client-side, used to implement user
agents.
=head2 client_handshake
$ws->client_handshake;
Perform WebSocket handshake client-side, used to implement user agents.
=head2 client_read
$ws->client_read($data);
Read data client-side, used to implement user agents.
=head2 client_write
my $bytes = $ws->client_write;
Write data client-side, used to implement user agents.
=head2 connection
my $connection = $ws->connection;
Connection identifier or socket.
=head2 finish
$ws = $ws->finish;
$ws = $ws->finish(1000);
$ws = $ws->finish(1003 => 'Cannot accept data!');
Close WebSocket connection gracefully.
=head2 is_websocket
my $true = $ws->is_websocket;
True.
=head2 kept_alive
my $kept_alive = $ws->kept_alive;
Connection has been kept alive.
=head2 local_address
my $address = $ws->local_address;
Local interface address.
=head2 local_port
my $port = $ws->local_port;
Local interface port.
=head2 new
my $ws = Mojo::Transaction::WebSocket->new;
Construct a new L<Mojo::Transaction::WebSocket> object and subscribe to
L</"frame"> event with default message parser, which also handles C<PING> and
C<CLOSE> frames automatically.
=head2 parse_frame
my $frame = $ws->parse_frame(\$bytes);
Parse WebSocket frame.
# Parse single frame and remove it from buffer
my $frame = $ws->parse_frame(\$buffer);
say "FIN: $frame->[0]";
say "RSV1: $frame->[1]";
say "RSV2: $frame->[2]";
say "RSV3: $frame->[3]";
say "Opcode: $frame->[4]";
say "Payload: $frame->[5]";
=head2 remote_address
my $address = $ws->remote_address;
Remote interface address.
=head2 remote_port
my $port = $ws->remote_port;
Remote interface port.
=head2 req
my $req = $ws->req;
Handshake request, usually a L<Mojo::Message::Request> object.
=head2 res
my $res = $ws->res;
Handshake response, usually a L<Mojo::Message::Response> object.
=head2 resume
$ws = $ws->resume;
Resume L</"handshake"> transaction.
=head2 send
$ws = $ws->send({binary => $bytes});
$ws = $ws->send({text => $bytes});
$ws = $ws->send({json => {test => [1, 2, 3]}});
$ws = $ws->send([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
$ws = $ws->send($chars);
$ws = $ws->send($chars => sub {...});
Send message or frame non-blocking via WebSocket, the optional drain callback
will be invoked once all data has been written.
# Send "Ping" frame
$ws->send([1, 0, 0, 0, 9, 'Hello World!']);
=head2 server_close
$ws->server_close;
Transaction closed server-side, used to implement web servers.
=head2 server_handshake
$ws->server_handshake;
Perform WebSocket handshake server-side, used to implement web servers.
=head2 server_read
$ws->server_read($data);
Read data server-side, used to implement web servers.
=head2 server_write
my $bytes = $ws->server_write;
Write data server-side, used to implement web servers.
=head2 with_compression
$ws->with_compression;
Negotiate C<permessage-deflate> extension for this WebSocket connection.
=head1 DEBUGGING
You can set the C<MOJO_WEBSOCKET_DEBUG> environment variable to get some
advanced diagnostics information printed to C<STDERR>.
MOJO_WEBSOCKET_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_TRANSACTION_WEBSOCKET
$fatpacked{"Mojo/URL.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_URL';
package Mojo::URL;
use Mojo::Base -base;
use overload bool => sub {1}, '""' => sub { shift->to_string }, fallback => 1;
use Mojo::Parameters;
use Mojo::Path;
use Mojo::Util qw(punycode_decode punycode_encode url_escape url_unescape);
has base => sub { Mojo::URL->new };
has [qw(fragment host port scheme userinfo)];
sub authority {
my $self = shift;
# New authority
if (@_) {
return $self unless defined(my $authority = shift);
# Userinfo
$authority =~ s/^([^\@]+)\@// and $self->userinfo(url_unescape $1);
# Port
$authority =~ s/:(\d+)$// and $self->port($1);
# Host
my $host = url_unescape $authority;
return $host =~ /[^\x00-\x7f]/ ? $self->ihost($host) : $self->host($host);
}
# Build authority
return undef unless defined(my $authority = $self->host_port);
return $authority unless my $info = $self->userinfo;
return url_escape($info, '^A-Za-z0-9\-._~!$&\'()*+,;=:') . '@' . $authority;
}
sub host_port {
my $self = shift;
return undef unless defined(my $host = $self->ihost);
return $host unless my $port = $self->port;
return "$host:$port";
}
sub clone {
my $self = shift;
my $clone = $self->new;
@$clone{keys %$self} = values %$self;
$clone->{$_} && ($clone->{$_} = $clone->{$_}->clone) for qw(base path query);
return $clone;
}
sub ihost {
my $self = shift;
# Decode
return $self->host(join '.',
map { /^xn--(.+)$/ ? punycode_decode($_) : $_ } split /\./, shift)
if @_;
# Check if host needs to be encoded
return undef unless defined(my $host = $self->host);
return lc $host unless $host =~ /[^\x00-\x7f]/;
# Encode
return lc join '.',
map { /[^\x00-\x7f]/ ? ('xn--' . punycode_encode $_) : $_ } split /\./,
$host;
}
sub is_abs { !!shift->scheme }
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my ($self, $url) = @_;
# Official regex from RFC 3986
$url =~ m!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!;
return $self->scheme($2)->authority($4)->path($5)->query($7)->fragment($9);
}
sub path {
my $self = shift;
# Old path
$self->{path} ||= Mojo::Path->new;
return $self->{path} unless @_;
# New path
my $path = shift;
$self->{path} = ref $path ? $path : $self->{path}->merge($path);
return $self;
}
sub path_query {
my $self = shift;
my $query = $self->query->to_string;
return $self->path->to_string . (length $query ? "?$query" : '');
}
sub protocol { lc(shift->scheme // '') }
sub query {
my $self = shift;
# Old parameters
my $q = $self->{query} ||= Mojo::Parameters->new;
return $q unless @_;
# Replace with list
if (@_ > 1) { $q->params([])->parse(@_) }
# Merge with array
elsif (ref $_[0] eq 'ARRAY') {
while (my $name = shift @{$_[0]}) {
my $value = shift @{$_[0]};
defined $value ? $q->param($name => $value) : $q->remove($name);
}
}
# Append hash
elsif (ref $_[0] eq 'HASH') { $q->append(%{$_[0]}) }
# Replace with string
else { $q->parse($_[0]) }
return $self;
}
sub to_abs {
my $self = shift;
my $abs = $self->clone;
return $abs if $abs->is_abs;
# Scheme
my $base = shift || $abs->base;
$abs->base($base)->scheme($base->scheme);
# Authority
return $abs if $abs->authority;
$abs->authority($base->authority);
# Absolute path
my $path = $abs->path;
return $abs if $path->leading_slash;
# Inherit path
my $base_path = $base->path;
if (!@{$path->parts}) {
$path
= $abs->path($base_path->clone)->path->trailing_slash(0)->canonicalize;
# Query
return $abs if length $abs->query->to_string;
$abs->query($base->query->clone);
}
# Merge paths
else { $abs->path($base_path->clone->merge($path)->canonicalize) }
return $abs;
}
sub to_string {
my $self = shift;
# Scheme
my $url = '';
if (my $proto = $self->protocol) { $url .= "$proto:" }
# Authority
my $authority = $self->authority;
$url .= "//$authority" if defined $authority;
# Path and query
my $path = $self->path_query;
$url .= !$authority || $path eq '' || $path =~ m!^[/?]! ? $path : "/$path";
# Fragment
return $url unless defined(my $fragment = $self->fragment);
return $url . '#' . url_escape $fragment, '^A-Za-z0-9\-._~!$&\'()*+,;=%:@/?';
}
1;
=encoding utf8
=head1 NAME
Mojo::URL - Uniform Resource Locator
=head1 SYNOPSIS
use Mojo::URL;
# Parse
my $url
= Mojo::URL->new('http://sri:foobar@example.com:3000/foo/bar?foo=bar#23');
say $url->scheme;
say $url->userinfo;
say $url->host;
say $url->port;
say $url->path;
say $url->query;
say $url->fragment;
# Build
my $url = Mojo::URL->new;
$url->scheme('http');
$url->userinfo('sri:foobar');
$url->host('example.com');
$url->port(3000);
$url->path('/foo/bar');
$url->query->param(foo => 'bar');
$url->fragment(23);
say "$url";
=head1 DESCRIPTION
L<Mojo::URL> implements a subset of
L<RFC 3986|http://tools.ietf.org/html/rfc3986> and
L<RFC 3987|http://tools.ietf.org/html/rfc3987> for Uniform Resource Locators
with support for IDNA and IRIs.
=head1 ATTRIBUTES
L<Mojo::URL> implements the following attributes.
=head2 base
my $base = $url->base;
$url = $url->base(Mojo::URL->new);
Base of this URL, defaults to a L<Mojo::URL> object.
=head2 fragment
my $fragment = $url->fragment;
$url = $url->fragment('foo');
Fragment part of this URL.
=head2 host
my $host = $url->host;
$url = $url->host('127.0.0.1');
Host part of this URL.
=head2 port
my $port = $url->port;
$url = $url->port(8080);
Port part of this URL.
=head2 scheme
my $scheme = $url->scheme;
$url = $url->scheme('http');
Scheme part of this URL.
=head2 userinfo
my $info = $url->userinfo;
$url = $url->userinfo('root:pass%3Bw0rd');
Userinfo part of this URL.
=head1 METHODS
L<Mojo::URL> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 authority
my $authority = $url->authority;
$url = $url->authority('root:pass%3Bw0rd@localhost:8080');
Authority part of this URL.
=head2 clone
my $url2 = $url->clone;
Clone this URL.
=head2 host_port
my $host_port = $url->host_port;
Normalized version of L</"host"> and L</"port">.
# "xn--n3h.net:8080"
Mojo::URL->new('http://☃.net:8080/test')->host_port;
=head2 ihost
my $ihost = $url->ihost;
$url = $url->ihost('xn--bcher-kva.ch');
Host part of this URL in punycode format.
# "xn--n3h.net"
Mojo::URL->new('http://☃.net')->ihost;
=head2 is_abs
my $bool = $url->is_abs;
Check if URL is absolute.
=head2 new
my $url = Mojo::URL->new;
my $url = Mojo::URL->new('http://127.0.0.1:3000/foo?f=b&baz=2#foo');
Construct a new L<Mojo::URL> object and L</"parse"> URL if necessary.
=head2 parse
$url = $url->parse('http://127.0.0.1:3000/foo/bar?fo=o&baz=23#foo');
Parse relative or absolute URL.
# "/test/123"
$url->parse('/test/123?foo=bar')->path;
# "example.com"
$url->parse('http://example.com/test/123?foo=bar')->host;
# "sri@example.com"
$url->parse('mailto:sri@example.com')->path;
=head2 path
my $path = $url->path;
$url = $url->path('/foo/bar');
$url = $url->path('foo/bar');
$url = $url->path(Mojo::Path->new);
Path part of this URL, relative paths will be merged with the existing path,
defaults to a L<Mojo::Path> object.
# "http://example.com/DOM/HTML"
Mojo::URL->new('http://example.com/perldoc/Mojo')->path('/DOM/HTML');
# "http://example.com/perldoc/DOM/HTML"
Mojo::URL->new('http://example.com/perldoc/Mojo')->path('DOM/HTML');
# "http://example.com/perldoc/Mojo/DOM/HTML"
Mojo::URL->new('http://example.com/perldoc/Mojo/')->path('DOM/HTML');
=head2 path_query
my $path_query = $url->path_query;
Normalized version of L</"path"> and L</"query">.
=head2 protocol
my $proto = $url->protocol;
Normalized version of L</"scheme">.
# "http"
Mojo::URL->new('HtTp://example.com')->protocol;
=head2 query
my $query = $url->query;
$url = $url->query(replace => 'with');
$url = $url->query([merge => 'with']);
$url = $url->query({append => 'to'});
$url = $url->query(Mojo::Parameters->new);
Query part of this URL, pairs in an array will be merged and pairs in a hash
appended, defaults to a L<Mojo::Parameters> object.
# "2"
Mojo::URL->new('http://example.com?a=1&b=2')->query->param('b');
# "http://example.com?a=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => 2, c => 3);
# "http://example.com?a=2&a=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query(a => [2, 3]);
# "http://example.com?a=2&b=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query([a => 2, c => 3]);
# "http://example.com?b=2"
Mojo::URL->new('http://example.com?a=1&b=2')->query([a => undef]);
# "http://example.com?a=1&b=2&a=2&c=3"
Mojo::URL->new('http://example.com?a=1&b=2')->query({a => 2, c => 3});
=head2 to_abs
my $abs = $url->to_abs;
my $abs = $url->to_abs(Mojo::URL->new('http://example.com/foo'));
Clone relative URL and turn it into an absolute one using L</"base"> or
provided base URL.
# "http://example.com/foo/baz.xml?test=123"
Mojo::URL->new('baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
# "http://example.com/baz.xml?test=123"
Mojo::URL->new('/baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
# "http://example.com/foo/baz.xml?test=123"
Mojo::URL->new('//example.com/foo/baz.xml?test=123')
->to_abs(Mojo::URL->new('http://example.com/foo/bar.html'));
=head2 to_string
my $str = $url->to_string;
Turn URL into a string.
=head1 OPERATORS
L<Mojo::URL> overloads the following operators.
=head2 bool
my $bool = !!$url;
Always true.
=head2 stringify
my $str = "$url";
Alias for L</"to_string">.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_URL
$fatpacked{"Mojo/Upload.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_UPLOAD';
package Mojo::Upload;
use Mojo::Base -base;
use Mojo::Asset::File;
use Mojo::Headers;
has asset => sub { Mojo::Asset::File->new };
has [qw(filename name)];
has headers => sub { Mojo::Headers->new };
sub move_to {
my $self = shift;
$self->asset->move_to(@_);
return $self;
}
sub size { shift->asset->size }
sub slurp { shift->asset->slurp }
1;
=encoding utf8
=head1 NAME
Mojo::Upload - Upload
=head1 SYNOPSIS
use Mojo::Upload;
my $upload = Mojo::Upload->new;
say $upload->filename;
$upload->move_to('/home/sri/foo.txt');
=head1 DESCRIPTION
L<Mojo::Upload> is a container for uploaded files.
=head1 ATTRIBUTES
L<Mojo::Upload> implements the following attributes.
=head2 asset
my $asset = $upload->asset;
$upload = $upload->asset(Mojo::Asset::File->new);
Asset containing the uploaded data, usually a L<Mojo::Asset::File> or
L<Mojo::Asset::Memory> object.
=head2 filename
my $filename = $upload->filename;
$upload = $upload->filename('foo.txt');
Name of the uploaded file.
=head2 headers
my $headers = $upload->headers;
$upload = $upload->headers(Mojo::Headers->new);
Headers for upload, defaults to a L<Mojo::Headers> object.
=head2 name
my $name = $upload->name;
$upload = $upload->name('foo');
Name of the upload.
=head1 METHODS
L<Mojo::Upload> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 move_to
$upload = $upload->move_to('/home/sri/foo.txt');
Move uploaded data into a specific file.
=head2 size
my $size = $upload->size;
Size of uploaded data in bytes.
=head2 slurp
my $bytes = $upload->slurp;
Read all uploaded data at once.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_UPLOAD
$fatpacked{"Mojo/UserAgent.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_USERAGENT';
package Mojo::UserAgent;
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::UserAgent::CookieJar;
use Mojo::UserAgent::Proxy;
use Mojo::UserAgent::Server;
use Mojo::UserAgent::Transactor;
use Scalar::Util 'weaken';
use constant DEBUG => $ENV{MOJO_USERAGENT_DEBUG} || 0;
has ca => sub { $ENV{MOJO_CA_FILE} };
has cert => sub { $ENV{MOJO_CERT_FILE} };
has connect_timeout => sub { $ENV{MOJO_CONNECT_TIMEOUT} || 10 };
has cookie_jar => sub { Mojo::UserAgent::CookieJar->new };
has 'local_address';
has inactivity_timeout => sub { $ENV{MOJO_INACTIVITY_TIMEOUT} // 20 };
has ioloop => sub { Mojo::IOLoop->new };
has key => sub { $ENV{MOJO_KEY_FILE} };
has max_connections => 5;
has max_redirects => sub { $ENV{MOJO_MAX_REDIRECTS} || 0 };
has proxy => sub { Mojo::UserAgent::Proxy->new };
has request_timeout => sub { $ENV{MOJO_REQUEST_TIMEOUT} // 0 };
has server => sub { Mojo::UserAgent::Server->new(ioloop => shift->ioloop) };
has transactor => sub { Mojo::UserAgent::Transactor->new };
# Common HTTP methods
for my $name (qw(DELETE GET HEAD OPTIONS PATCH POST PUT)) {
monkey_patch __PACKAGE__, lc $name, sub {
my $self = shift;
my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
return $self->start($self->build_tx($name, @_), $cb);
};
}
sub DESTROY { shift->_cleanup }
sub build_tx { shift->transactor->tx(@_) }
sub build_websocket_tx { shift->transactor->websocket(@_) }
sub start {
my ($self, $tx, $cb) = @_;
# Fork safety
$self->_cleanup->server->restart unless ($self->{pid} //= $$) eq $$;
# Non-blocking
if ($cb) {
warn "-- Non-blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG;
return $self->_start(1, $tx, $cb);
}
# Blocking
warn "-- Blocking request (@{[$tx->req->url->to_abs]})\n" if DEBUG;
$self->_start(0, $tx => sub { shift->ioloop->stop; $tx = shift });
$self->ioloop->start;
return $tx;
}
sub websocket {
my $self = shift;
my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
$self->start($self->build_websocket_tx(@_), $cb);
}
sub _cleanup {
my $self = shift;
return unless my $loop = $self->_loop(0);
# Clean up active connections (by closing them)
delete $self->{pid};
$self->_finish($_, 1) for keys %{$self->{connections} || {}};
# Clean up keep-alive connections
$loop->remove($_->[1]) for @{delete $self->{queue} || []};
$loop = $self->_loop(1);
$loop->remove($_->[1]) for @{delete $self->{nb_queue} || []};
return $self;
}
sub _connect {
my ($self, $nb, $peer, $tx, $handle, $cb) = @_;
my $t = $self->transactor;
my ($proto, $host, $port) = $peer ? $t->peer($tx) : $t->endpoint($tx);
my %options
= (address => $host, port => $port, timeout => $self->connect_timeout);
if (my $local = $self->local_address) { $options{local_address} = $local }
$options{handle} = $handle if $handle;
# SOCKS
if ($proto eq 'socks') {
@options{qw(socks_address socks_port)} = @options{qw(address port)};
($proto, @options{qw(address port)}) = $t->endpoint($tx);
my $req = $tx->req;
my $userinfo = $req->proxy->userinfo;
$req->proxy(0);
@options{qw(socks_user socks_pass)} = split ':', $userinfo if $userinfo;
}
# TLS
map { $options{"tls_$_"} = $self->$_ } qw(ca cert key)
if ($options{tls} = $proto eq 'https');
weaken $self;
my $id;
return $id = $self->_loop($nb)->client(
%options => sub {
my ($loop, $err, $stream) = @_;
# Connection error
return unless $self;
return $self->_error($id, $err) if $err;
# Connection established
$stream->on(timeout => sub { $self->_error($id, 'Inactivity timeout') });
$stream->on(close => sub { $self && $self->_finish($id, 1) });
$stream->on(error => sub { $self && $self->_error($id, pop) });
$stream->on(read => sub { $self->_read($id, pop) });
$self->$cb($id);
}
);
}
sub _connect_proxy {
my ($self, $nb, $old, $cb) = @_;
# Start CONNECT request
return undef unless my $new = $self->transactor->proxy_connect($old);
return $self->_start(
($nb, $new) => sub {
my ($self, $tx) = @_;
# CONNECT failed (connection needs to be kept alive)
$old->res->error({message => 'Proxy connection failed'})
and return $self->$cb($old)
if $tx->error || !$tx->res->is_status_class(200) || !$tx->keep_alive;
# Start real transaction
$old->req->proxy(0);
my $id = $tx->connection;
return $self->_start($nb, $old->connection($id), $cb)
unless $tx->req->url->protocol eq 'https';
# TLS upgrade
my $loop = $self->_loop($nb);
my $handle = $loop->stream($id)->steal_handle;
$loop->remove($id);
$id = $self->_connect($nb, 0, $old, $handle,
sub { shift->_start($nb, $old->connection($id), $cb) });
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $old};
}
);
}
sub _connected {
my ($self, $id) = @_;
# Inactivity timeout
my $c = $self->{connections}{$id};
my $stream
= $self->_loop($c->{nb})->stream($id)->timeout($self->inactivity_timeout);
# Store connection information in transaction
my $tx = $c->{tx}->connection($id);
my $handle = $stream->handle;
$tx->local_address($handle->sockhost)->local_port($handle->sockport);
$tx->remote_address($handle->peerhost)->remote_port($handle->peerport);
# Start writing
weaken $self;
$tx->on(resume => sub { $self->_write($id) });
$self->_write($id);
}
sub _connection {
my ($self, $nb, $tx, $cb) = @_;
# Reuse connection
my $id = $tx->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;
$self->{connections}{$id} = {cb => $cb, nb => $nb, tx => $tx};
$tx->kept_alive(1) unless $tx->connection;
$self->_connected($id);
return $id;
}
# CONNECT request to proxy required
if (my $id = $self->_connect_proxy($nb, $tx, $cb)) { return $id }
# Connect
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};
return $id;
}
sub _dequeue {
my ($self, $nb, $name, $test) = @_;
my $loop = $self->_loop($nb);
my $old = $self->{$nb ? 'nb_queue' : 'queue'} ||= [];
my ($found, @new);
for my $queued (@$old) {
push @new, $queued and next if $found || !grep { $_ eq $name } @$queued;
# Search for id/name and sort out corrupted connections if necessary
next unless my $stream = $loop->stream($queued->[1]);
$test && $stream->is_readable ? $stream->close : ($found = $queued->[1]);
}
@$old = @new;
return $found;
}
sub _enqueue {
my ($self, $nb, $name, $id) = @_;
# Enforce connection limit
my $queue = $self->{$nb ? 'nb_queue' : 'queue'} ||= [];
my $max = $self->max_connections;
$self->_remove(shift(@$queue)->[1]) while @$queue && @$queue >= $max;
$max ? push @$queue, [$name, $id] : $self->_loop($nb)->stream($id)->close;
}
sub _error {
my ($self, $id, $err) = @_;
my $tx = $self->{connections}{$id}{tx};
$tx->res->error({message => $err}) if $tx;
$self->_finish($id, 1);
}
sub _finish {
my ($self, $id, $close) = @_;
# Remove request timeout
return unless my $c = $self->{connections}{$id};
return unless my $loop = $self->_loop($c->{nb});
$loop->remove($c->{timeout}) if $c->{timeout};
return $self->_remove($id, $close) unless my $old = $c->{tx};
$old->client_close($close);
# Finish WebSocket
return $self->_remove($id, 1) if $old->is_websocket;
if (my $jar = $self->cookie_jar) { $jar->extract($old) }
# Upgrade connection to WebSocket
if (my $new = $self->transactor->upgrade($old)) {
weaken $self;
$new->on(resume => sub { $self->_write($id) });
$c->{cb}->($self, $c->{tx} = $new);
return $new->client_read($old->res->content->leftovers);
}
# Finish normal connection and handle redirects
$self->_remove($id, $close);
$c->{cb}->($self, $old) unless $self->_redirect($c, $old);
}
sub _loop { $_[1] ? Mojo::IOLoop->singleton : $_[0]->ioloop }
sub _read {
my ($self, $id, $chunk) = @_;
# Corrupted connection
return unless my $c = $self->{connections}{$id};
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;
$tx->client_read($chunk);
if ($tx->is_finished) { $self->_finish($id) }
elsif ($tx->is_writing) { $self->_write($id) }
}
sub _redirect {
my ($self, $c, $old) = @_;
return undef unless my $new = $self->transactor->redirect($old);
return undef unless @{$old->redirects} < $self->max_redirects;
return $self->_start($c->{nb}, $new, delete $c->{cb});
}
sub _remove {
my ($self, $id, $close) = @_;
# Close connection
my $c = delete $self->{connections}{$id} || {};
my $tx = $c->{tx};
return map { $self->_dequeue($_, $id); $self->_loop($_)->remove($id) } 1, 0
if $close || !$tx || !$tx->keep_alive || $tx->error;
# Keep connection alive (CONNECT requests get upgraded)
$self->_enqueue($c->{nb}, join(':', $self->transactor->endpoint($tx)), $id)
unless uc $tx->req->method eq 'CONNECT';
}
sub _start {
my ($self, $nb, $tx, $cb) = @_;
# Application server
my $url = $tx->req->url;
unless ($url->is_abs) {
my $base = $nb ? $self->server->nb_url : $self->server->url;
$url->scheme($base->scheme)->authority($base->authority);
}
$_ && $_->inject($tx) for $self->proxy, $self->cookie_jar;
# Connect and add request timeout if necessary
my $id = $self->emit(start => $tx)->_connection($nb, $tx, $cb);
if (my $timeout = $self->request_timeout) {
weaken $self;
$self->{connections}{$id}{timeout} = $self->_loop($nb)
->timer($timeout => sub { $self->_error($id, 'Request timeout') });
}
return $id;
}
sub _write {
my ($self, $id) = @_;
# Get and write chunk
return unless my $c = $self->{connections}{$id};
return unless my $tx = $c->{tx};
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;
my $stream = $self->_loop($c->{nb})->stream($id)->write($chunk);
$self->_finish($id) if $tx->is_finished;
# Continue writing
return unless $tx->is_writing;
weaken $self;
$stream->write('' => sub { $self->_write($id) });
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent - Non-blocking I/O HTTP and WebSocket user agent
=head1 SYNOPSIS
use Mojo::UserAgent;
# Say hello to the Unicode snowman with "Do Not Track" header
my $ua = Mojo::UserAgent->new;
say $ua->get('www.☃.net?hello=there' => {DNT => 1})->res->body;
# Form POST with exception handling
my $tx = $ua->post('https://metacpan.org/search' => form => {q => 'mojo'});
if (my $res = $tx->success) { say $res->body }
else {
my $err = $tx->error;
die "$err->{code} response: $err->{message}" if $err->{code};
die "Connection error: $err->{message}";
}
# Quick JSON API request with Basic authentication
say $ua->get('https://sri:s3cret@example.com/search.json?q=perl')
->res->json('/results/0/title');
# Extract data from HTML and XML resources
say $ua->get('www.perl.org')->res->dom->at('title')->text;
# Scrape the latest headlines from a news site with CSS selectors
say $ua->get('blogs.perl.org')
->res->dom->find('h2 > a')->map('text')->join("\n");
# Search DuckDuckGo anonymously through Tor
$ua->proxy->http('socks://127.0.0.1:9050');
say $ua->get('api.3g2upl4pq6kufc4m.onion/?q=mojolicious&format=json')
->res->json('/Abstract');
# IPv6 PUT request with content
my $tx
= $ua->put('[::1]:3000' => {'Content-Type' => 'text/plain'} => 'Hello!');
# Follow redirects to grab the latest Mojolicious release :)
$ua->max_redirects(5)
->get('https://www.github.com/kraih/mojo/tarball/master')
->res->content->asset->move_to('/Users/sri/mojo.tar.gz');
# TLS certificate authentication and JSON POST
my $tx = $ua->cert('tls.crt')->key('tls.key')
->post('https://example.com' => json => {top => 'secret'});
# Non-blocking concurrent requests
Mojo::IOLoop->delay(
sub {
my $delay = shift;
$ua->get('mojolicio.us' => $delay->begin);
$ua->get('cpan.org' => $delay->begin);
},
sub {
my ($delay, $mojo, $cpan) = @_;
say $mojo->res->dom->at('title')->text;
say $cpan->res->dom->at('title')->text;
}
)->wait;
# Non-blocking WebSocket connection sending and receiving JSON messages
$ua->websocket('ws://example.com/echo.json' => sub {
my ($ua, $tx) = @_;
say 'WebSocket handshake failed!' and return unless $tx->is_websocket;
$tx->on(json => sub {
my ($tx, $hash) = @_;
say "WebSocket message via JSON: $hash->{msg}";
$tx->finish;
});
$tx->send({json => {msg => 'Hello World!'}});
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head1 DESCRIPTION
L<Mojo::UserAgent> is a full featured non-blocking I/O HTTP and WebSocket user
agent, with IPv6, TLS, SNI, IDNA, HTTP/SOCKS5 proxy, Comet (long polling),
keep-alive, connection pooling, timeout, cookie, multipart, gzip compression
and multiple event loop support.
All connections will be reset automatically if a new process has been forked,
this allows multiple processes to share the same L<Mojo::UserAgent> object
safely.
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+),
L<Net::DNS::Native> (0.12+), L<IO::Socket::Socks> (0.64+) and
L<IO::Socket::SSL> (1.84+) will be used automatically if they are installed.
Individual features can also be disabled with the C<MOJO_NO_NDN>,
C<MOJO_NO_SOCKS> and C<MOJO_NO_TLS> environment variables.
See L<Mojolicious::Guides::Cookbook/"USER AGENT"> for more.
=head1 EVENTS
L<Mojo::UserAgent> inherits all events from L<Mojo::EventEmitter> and can emit
the following new ones.
=head2 start
$ua->on(start => sub {
my ($ua, $tx) = @_;
...
});
Emitted whenever a new transaction is about to start, this includes
automatically prepared proxy C<CONNECT> requests and followed redirects.
$ua->on(start => sub {
my ($ua, $tx) = @_;
$tx->req->headers->header('X-Bender' => 'Bite my shiny metal ass!');
});
=head1 ATTRIBUTES
L<Mojo::UserAgent> implements the following attributes.
=head2 ca
my $ca = $ua->ca;
$ua = $ua->ca('/etc/tls/ca.crt');
Path to TLS certificate authority file, defaults to the value of the
C<MOJO_CA_FILE> environment variable. Also activates hostname verification.
# Show certificate authorities for debugging
IO::Socket::SSL::set_defaults(
SSL_verify_callback => sub { say "Authority: $_[2]" and return $_[0] });
=head2 cert
my $cert = $ua->cert;
$ua = $ua->cert('/etc/tls/client.crt');
Path to TLS certificate file, defaults to the value of the C<MOJO_CERT_FILE>
environment variable.
=head2 connect_timeout
my $timeout = $ua->connect_timeout;
$ua = $ua->connect_timeout(5);
Maximum amount of time in seconds establishing a connection may take before
getting canceled, defaults to the value of the C<MOJO_CONNECT_TIMEOUT>
environment variable or C<10>.
=head2 cookie_jar
my $cookie_jar = $ua->cookie_jar;
$ua = $ua->cookie_jar(Mojo::UserAgent::CookieJar->new);
Cookie jar to use for requests performed by this user agent, defaults to a
L<Mojo::UserAgent::CookieJar> object.
# Disable extraction of cookies from responses
$ua->cookie_jar->extracting(0);
=head2 inactivity_timeout
my $timeout = $ua->inactivity_timeout;
$ua = $ua->inactivity_timeout(15);
Maximum amount of time in seconds a connection can be inactive before getting
closed, defaults to the value of the C<MOJO_INACTIVITY_TIMEOUT> environment
variable or C<20>. Setting the value to C<0> will allow connections to be
inactive indefinitely.
=head2 ioloop
my $loop = $ua->ioloop;
$ua = $ua->ioloop(Mojo::IOLoop->new);
Event loop object to use for blocking I/O operations, defaults to a
L<Mojo::IOLoop> object.
=head2 key
my $key = $ua->key;
$ua = $ua->key('/etc/tls/client.crt');
Path to TLS key file, defaults to the value of the C<MOJO_KEY_FILE>
environment variable.
=head2 local_address
my $address = $ua->local_address;
$ua = $ua->local_address('127.0.0.1');
Local address to bind to.
=head2 max_connections
my $max = $ua->max_connections;
$ua = $ua->max_connections(5);
Maximum number of keep-alive connections that the user agent will retain
before it starts closing the oldest ones, defaults to C<5>. Setting the value
to C<0> will prevent any connections from being kept alive.
=head2 max_redirects
my $max = $ua->max_redirects;
$ua = $ua->max_redirects(3);
Maximum number of redirects the user agent will follow before it fails,
defaults to the value of the C<MOJO_MAX_REDIRECTS> environment variable or
C<0>.
=head2 proxy
my $proxy = $ua->proxy;
$ua = $ua->proxy(Mojo::UserAgent::Proxy->new);
Proxy manager, defaults to a L<Mojo::UserAgent::Proxy> object.
# Detect proxy servers from environment
$ua->proxy->detect;
# Manually configure HTTP proxy (using CONNECT for HTTPS)
$ua->proxy->http('http://127.0.0.1:8080')->https('http://127.0.0.1:8080');
# Manually configure Tor (SOCKS5)
$ua->proxy->http('socks://127.0.0.1:9050')->https('socks://127.0.0.1:9050');
=head2 request_timeout
my $timeout = $ua->request_timeout;
$ua = $ua->request_timeout(5);
Maximum amount of time in seconds establishing a connection, sending the
request and receiving a whole response may take before getting canceled,
defaults to the value of the C<MOJO_REQUEST_TIMEOUT> environment variable or
C<0>. Setting the value to C<0> will allow the user agent to wait
indefinitely. The timeout will reset for every followed redirect.
# Total limit of 5 seconds, of which 3 seconds may be spent connecting
$ua->max_redirects(0)->connect_timeout(3)->request_timeout(5);
=head2 server
my $server = $ua->server;
$ua = $ua->server(Mojo::UserAgent::Server->new);
Application server relative URLs will be processed with, defaults to a
L<Mojo::UserAgent::Server> object.
# Introspect
say for @{$ua->server->app->secrets};
# Change log level
$ua->server->app->log->level('fatal');
# Port currently used for processing relative URLs blocking
say $ua->server->url->port;
# Port currently used for processing relative URLs non-blocking
say $ua->server->nb_url->port;
=head2 transactor
my $t = $ua->transactor;
$ua = $ua->transactor(Mojo::UserAgent::Transactor->new);
Transaction builder, defaults to a L<Mojo::UserAgent::Transactor> object.
# Change name of user agent
$ua->transactor->name('MyUA 1.0');
=head1 METHODS
L<Mojo::UserAgent> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 build_tx
my $tx = $ua->build_tx(GET => 'example.com');
my $tx = $ua->build_tx(
PUT => 'http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->build_tx(
PUT => 'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->build_tx(
PUT => 'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Generate L<Mojo::Transaction::HTTP> object with
L<Mojo::UserAgent::Transactor/"tx">.
# Request with custom cookie
my $tx = $ua->build_tx(GET => 'example.com');
$tx->req->cookies({name => 'foo', value => 'bar'});
$tx = $ua->start($tx);
# Deactivate gzip compression
my $tx = $ua->build_tx(GET => 'example.com');
$tx->req->headers->remove('Accept-Encoding');
$tx = $ua->start($tx);
# Interrupt response by raising an error
my $tx = $ua->build_tx(GET => 'example.com');
$tx->res->on(progress => sub {
my $res = shift;
return unless my $server = $res->headers->server;
$res->error({message => 'Oh noes, it is IIS!'}) if $server =~ /IIS/;
});
$tx = $ua->start($tx);
=head2 build_websocket_tx
my $tx = $ua->build_websocket_tx('ws://example.com');
my $tx = $ua->build_websocket_tx(
'ws://example.com' => {DNT => 1} => ['v1.proto']);
Generate L<Mojo::Transaction::HTTP> object with
L<Mojo::UserAgent::Transactor/"websocket">.
=head2 delete
my $tx = $ua->delete('example.com');
my $tx = $ua->delete('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->delete(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->delete(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<DELETE> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<DELETE> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->delete('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 get
my $tx = $ua->get('example.com');
my $tx = $ua->get('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->get(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->get(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<GET> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<GET> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->get('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 head
my $tx = $ua->head('example.com');
my $tx = $ua->head('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->head(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->head(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<HEAD> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<HEAD> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->head('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 options
my $tx = $ua->options('example.com');
my $tx = $ua->options('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->options(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->options(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<OPTIONS> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<OPTIONS> method, which
is implied). You can also append a callback to perform requests non-blocking.
$ua->options('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 patch
my $tx = $ua->patch('example.com');
my $tx = $ua->patch('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->patch(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->patch(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<PATCH> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<PATCH> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->patch('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 post
my $tx = $ua->post('example.com');
my $tx = $ua->post('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->post(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->post(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<POST> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<POST> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->post('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 put
my $tx = $ua->put('example.com');
my $tx = $ua->put('http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $ua->put(
'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $ua->put(
'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Perform blocking C<PUT> request and return resulting
L<Mojo::Transaction::HTTP> object, takes the same arguments as
L<Mojo::UserAgent::Transactor/"tx"> (except for the C<PUT> method, which is
implied). You can also append a callback to perform requests non-blocking.
$ua->put('http://example.com' => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 start
my $tx = $ua->start(Mojo::Transaction::HTTP->new);
Perform blocking request for a custom L<Mojo::Transaction::HTTP> object, which
can be prepared manually or with L</"build_tx">. You can also append a
callback to perform requests non-blocking.
my $tx = $ua->build_tx(GET => 'http://example.com');
$ua->start($tx => sub {
my ($ua, $tx) = @_;
say $tx->res->body;
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
=head2 websocket
$ua->websocket('ws://example.com' => sub {...});
$ua->websocket(
'ws://example.com' => {DNT => 1} => ['v1.proto'] => sub {...});
Open a non-blocking WebSocket connection with transparent handshake, takes the
same arguments as L<Mojo::UserAgent::Transactor/"websocket">. The callback
will receive either a L<Mojo::Transaction::WebSocket> or
L<Mojo::Transaction::HTTP> object, depending on if the handshake was
successful.
$ua->websocket('ws://example.com/echo' => sub {
my ($ua, $tx) = @_;
say 'WebSocket handshake failed!' and return unless $tx->is_websocket;
$tx->on(finish => sub {
my ($tx, $code, $reason) = @_;
say "WebSocket closed with status $code.";
});
$tx->on(message => sub {
my ($tx, $msg) = @_;
say "WebSocket message: $msg";
$tx->finish;
});
$tx->send('Hi!');
});
Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
You can activate C<permessage-deflate> compression by setting the
C<Sec-WebSocket-Extensions> header, this can result in much better
performance, but also increases memory usage by up to 300KB per connection.
my $headers = {'Sec-WebSocket-Extensions' => 'permessage-deflate'};
$ua->websocket('ws://example.com/foo' => $headers => sub {...});
=head1 DEBUGGING
You can set the C<MOJO_USERAGENT_DEBUG> environment variable to get some
advanced diagnostics information printed to C<STDERR>.
MOJO_USERAGENT_DEBUG=1
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_USERAGENT
$fatpacked{"Mojo/UserAgent/CookieJar.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_USERAGENT_COOKIEJAR';
package Mojo::UserAgent::CookieJar;
use Mojo::Base -base;
use Mojo::Cookie::Request;
use Mojo::Path;
has extracting => 1;
has max_cookie_size => 4096;
sub add {
my ($self, @cookies) = @_;
my $size = $self->max_cookie_size;
for my $cookie (@cookies) {
# Convert max age to expires
if (my $age = $cookie->max_age) { $cookie->expires($age + time) }
# Check cookie size
next if length($cookie->value // '') > $size;
# Replace cookie
my $origin = $cookie->origin // '';
next unless my $domain = lc($cookie->domain // $origin);
$domain =~ s/^\.//;
next unless my $path = $cookie->path;
next unless length(my $name = $cookie->name // '');
my $jar = $self->{jar}{$domain} ||= [];
@$jar = (grep({ _compare($_, $path, $name, $origin) } @$jar), $cookie);
}
return $self;
}
sub all {
my $jar = shift->{jar};
return map { @{$jar->{$_}} } sort keys %$jar;
}
sub empty { delete shift->{jar} }
sub extract {
my ($self, $tx) = @_;
return unless $self->extracting;
my $url = $tx->req->url;
for my $cookie (@{$tx->res->cookies}) {
# Validate domain
my $host = $url->ihost;
my $domain = lc($cookie->domain // $cookie->origin($host)->origin);
$domain =~ s/^\.//;
next
if $host ne $domain && ($host !~ /\Q.$domain\E$/ || $host =~ /\.\d+$/);
# Validate path
my $path = $cookie->path // $url->path->to_dir->to_abs_string;
$path = Mojo::Path->new($path)->trailing_slash(0)->to_abs_string;
next unless _path($path, $url->path->to_abs_string);
$self->add($cookie->path($path));
}
}
sub find {
my ($self, $url) = @_;
return unless my $domain = my $host = $url->ihost;
my $path = $url->path->to_abs_string;
my @found;
while ($domain =~ /[^.]+\.[^.]+|localhost$/) {
next unless my $old = $self->{jar}{$domain};
# Grab cookies
my $new = $self->{jar}{$domain} = [];
for my $cookie (@$old) {
next unless $cookie->domain || $host eq $cookie->origin;
# Check if cookie has expired
my $expires = $cookie->expires;
next if $expires && time > ($expires->epoch || 0);
push @$new, $cookie;
# Taste cookie
next if $cookie->secure && $url->protocol ne 'https';
next unless _path($cookie->path, $path);
my $name = $cookie->name;
my $value = $cookie->value;
push @found, Mojo::Cookie::Request->new(name => $name, value => $value);
}
}
# Remove another part
continue { $domain =~ s/^[^.]+\.?// }
return @found;
}
sub inject {
my ($self, $tx) = @_;
return unless keys %{$self->{jar}};
my $req = $tx->req;
$req->cookies($self->find($req->url));
}
sub _compare {
my ($cookie, $path, $name, $origin) = @_;
return 1 if $cookie->path ne $path || $cookie->name ne $name;
return ($cookie->origin // '') ne $origin;
}
sub _path { $_[0] eq '/' || $_[0] eq $_[1] || $_[1] =~ m!^\Q$_[0]/! }
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::CookieJar - Cookie jar for HTTP user agents
=head1 SYNOPSIS
use Mojo::UserAgent::CookieJar;
# Add response cookies
my $jar = Mojo::UserAgent::CookieJar->new;
$jar->add(
Mojo::Cookie::Response->new(
name => 'foo',
value => 'bar',
domain => 'localhost',
path => '/test'
)
);
# Find request cookies
for my $cookie ($jar->find(Mojo::URL->new('http://localhost/test'))) {
say $cookie->name;
say $cookie->value;
}
=head1 DESCRIPTION
L<Mojo::UserAgent::CookieJar> is a minimalistic and relaxed cookie jar used by
L<Mojo::UserAgent> and based on
L<RFC 6265|http://tools.ietf.org/html/rfc6265>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::CookieJar> implements the following attributes.
=head2 extracting
my $bool = $jar->extracting;
$jar = $jar->extracting($bool);
Allow L</"extract"> to L</"add"> new cookies to the jar, defaults to a true
value.
=head2 max_cookie_size
my $size = $jar->max_cookie_size;
$jar = $jar->max_cookie_size(4096);
Maximum cookie size in bytes, defaults to C<4096> (4KB).
=head1 METHODS
L<Mojo::UserAgent::CookieJar> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 add
$jar = $jar->add(@cookies);
Add multiple L<Mojo::Cookie::Response> objects to the jar.
=head2 all
my @cookies = $jar->all;
Return all L<Mojo::Cookie::Response> objects that are currently stored in the
jar.
=head2 empty
$jar->empty;
Empty the jar.
=head2 extract
$jar->extract(Mojo::Transaction::HTTP->new);
Extract response cookies from transaction.
=head2 find
my @cookies = $jar->find(Mojo::URL->new);
Find L<Mojo::Cookie::Request> objects in the jar for L<Mojo::URL> object.
=head2 inject
$jar->inject(Mojo::Transaction::HTTP->new);
Inject request cookies into transaction.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_USERAGENT_COOKIEJAR
$fatpacked{"Mojo/UserAgent/Proxy.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_USERAGENT_PROXY';
package Mojo::UserAgent::Proxy;
use Mojo::Base -base;
has [qw(http https not)];
sub detect {
my $self = shift;
$self->http($ENV{HTTP_PROXY} || $ENV{http_proxy});
$self->https($ENV{HTTPS_PROXY} || $ENV{https_proxy});
return $self->not([split ',', $ENV{NO_PROXY} || $ENV{no_proxy} || '']);
}
sub inject {
my ($self, $tx) = @_;
$self->detect if $ENV{MOJO_PROXY};
my $req = $tx->req;
my $url = $req->url;
return if !$self->is_needed($url->host) || defined $req->proxy;
# HTTP proxy
my $proto = $url->protocol;
my $http = $self->http;
$req->proxy($http) if $http && $proto eq 'http';
# HTTPS proxy
my $https = $self->https;
$req->proxy($https) if $https && $proto eq 'https';
}
sub is_needed {
!grep { $_[1] =~ /\Q$_\E$/ } @{$_[0]->not || []};
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Proxy - User agent proxy manager
=head1 SYNOPSIS
use Mojo::UserAgent::Proxy;
my $proxy = Mojo::UserAgent::Proxy->new;
$proxy->detect;
say $proxy->http;
=head1 DESCRIPTION
L<Mojo::UserAgent::Proxy> manages proxy servers for L<Mojo::UserAgent>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Proxy> implements the following attributes.
=head2 http
my $http = $proxy->http;
$proxy = $proxy->http('socks://sri:secret@127.0.0.1:8080');
Proxy server to use for HTTP and WebSocket requests.
=head2 https
my $https = $proxy->https;
$proxy = $proxy->https('http://sri:secret@127.0.0.1:8080');
Proxy server to use for HTTPS and WebSocket requests.
=head2 not
my $not = $proxy->not;
$proxy = $proxy->not([qw(localhost intranet.mojolicio.us)]);
Domains that don't require a proxy server to be used.
=head1 METHODS
L<Mojo::UserAgent::Proxy> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 detect
$proxy = $proxy->detect;
Check environment variables C<HTTP_PROXY>, C<http_proxy>, C<HTTPS_PROXY>,
C<https_proxy>, C<NO_PROXY> and C<no_proxy> for proxy information. Automatic
proxy detection can be enabled with the C<MOJO_PROXY> environment variable.
=head2 inject
$proxy->inject(Mojo::Transaction::HTTP->new);
Inject proxy server information into transaction.
=head2 is_needed
my $bool = $proxy->is_needed('intranet.example.com');
Check if request for domain would use a proxy server.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_USERAGENT_PROXY
$fatpacked{"Mojo/UserAgent/Server.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_USERAGENT_SERVER';
package Mojo::UserAgent::Server;
use Mojo::Base -base;
use Mojo::IOLoop;
use Mojo::Server::Daemon;
use Scalar::Util 'weaken';
has ioloop => sub { Mojo::IOLoop->singleton };
sub app {
my ($self, $app) = @_;
# Singleton application
state $singleton;
return $singleton = $app ? $app : $singleton unless ref $self;
# Default to singleton application
return $self->{app} || $singleton unless $app;
$self->{app} = $app;
return $self;
}
sub nb_url { shift->_url(1, @_) }
sub restart { shift->_restart(1) }
sub url { shift->_url(0, @_) }
sub _restart {
my ($self, $full, $proto) = @_;
delete @{$self}{qw(nb_port port)} if $full;
$self->{proto} = $proto ||= 'http';
# Blocking
my $server = $self->{server}
= Mojo::Server::Daemon->new(ioloop => $self->ioloop, silent => 1);
weaken $server->app($self->app)->{app};
my $port = $self->{port} ? ":$self->{port}" : '';
$self->{port} = $server->listen(["$proto://127.0.0.1$port"])
->start->ioloop->acceptor($server->acceptors->[0])->handle->sockport;
# Non-blocking
$server = $self->{nb_server} = Mojo::Server::Daemon->new(silent => 1);
weaken $server->app($self->app)->{app};
$port = $self->{nb_port} ? ":$self->{nb_port}" : '';
$self->{nb_port} = $server->listen(["$proto://127.0.0.1$port"])
->start->ioloop->acceptor($server->acceptors->[0])->handle->sockport;
}
sub _url {
my ($self, $nb) = (shift, shift);
$self->_restart(0, @_) if !$self->{server} || @_;
my $port = $nb ? $self->{nb_port} : $self->{port};
return Mojo::URL->new("$self->{proto}://localhost:$port/");
}
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Server - Application server
=head1 SYNOPSIS
use Mojo::UserAgent::Server;
my $server = Mojo::UserAgent::Server->new;
say $server->url;
=head1 DESCRIPTION
L<Mojo::UserAgent::Server> is an embedded web server based on
L<Mojo::Server::Daemon> that processes requests for L<Mojo::UserAgent>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Server> implements the following attributes.
=head2 ioloop
my $loop = $server->ioloop;
$server = $server->ioloop(Mojo::IOLoop->new);
Event loop object to use for I/O operations, defaults to the global
L<Mojo::IOLoop> singleton.
=head1 METHODS
L<Mojo::UserAgent::Server> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 app
my $app = Mojo::UserAgent::Server->app;
Mojo::UserAgent::Server->app(MyApp->new);
my $app = $server->app;
$server = $server->app(MyApp->new);
Application this server handles, instance specific applications override the
global default.
# Change application behavior
$server->app->defaults(testing => 'oh yea!');
=head2 nb_url
my $url = $ua->nb_url;
my $url = $ua->nb_url('http');
my $url = $ua->nb_url('https');
Get absolute L<Mojo::URL> object for server processing non-blocking requests
with L</"app"> and switch protocol if necessary.
=head2 restart
$server->restart;
Restart server with new port.
=head2 url
my $url = $ua->url;
my $url = $ua->url('http');
my $url = $ua->url('https');
Get absolute L<Mojo::URL> object for server processing blocking requests with
L</"app"> and switch protocol if necessary.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_USERAGENT_SERVER
$fatpacked{"Mojo/UserAgent/Transactor.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_USERAGENT_TRANSACTOR';
package Mojo::UserAgent::Transactor;
use Mojo::Base -base;
use File::Basename 'basename';
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Content::MultiPart;
use Mojo::Content::Single;
use Mojo::JSON 'encode_json';
use Mojo::Parameters;
use Mojo::Transaction::HTTP;
use Mojo::Transaction::WebSocket;
use Mojo::URL;
use Mojo::Util qw(encode url_escape);
has generators => sub { {form => \&_form, json => \&_json} };
has name => 'Mojolicious (Perl)';
sub add_generator {
my ($self, $name, $cb) = @_;
$self->generators->{$name} = $cb;
return $self;
}
sub endpoint {
my ($self, $tx) = @_;
# Basic endpoint
my $req = $tx->req;
my $url = $req->url;
my $proto = $url->protocol || 'http';
my $host = $url->ihost;
my $port = $url->port || ($proto eq 'https' ? 443 : 80);
# Proxy for normal HTTP requests
my $socks;
if (my $proxy = $req->proxy) { $socks = $proxy->protocol eq 'socks' }
return $self->_proxy($tx, $proto, $host, $port)
if $proto eq 'http' && !$req->is_handshake && !$socks;
return $proto, $host, $port;
}
sub peer { $_[0]->_proxy($_[1], $_[0]->endpoint($_[1])) }
sub proxy_connect {
my ($self, $old) = @_;
# Already a CONNECT request
my $req = $old->req;
return undef if uc $req->method eq 'CONNECT';
# No proxy
return undef unless my $proxy = $req->proxy;
return undef if $proxy->protocol eq 'socks';
# WebSocket and/or HTTPS
my $url = $req->url;
return undef unless $req->is_handshake || $url->protocol eq 'https';
# CONNECT request (expect a bad response)
my $new = $self->tx(CONNECT => $url->clone->userinfo(undef));
$new->req->proxy($proxy);
$new->res->content->auto_relax(0)->headers->connection('keep-alive');
return $new;
}
sub redirect {
my ($self, $old) = @_;
# Commonly used codes
my $res = $old->res;
my $code = $res->code // 0;
return undef unless grep { $_ == $code } 301, 302, 303, 307, 308;
# Fix location without authority and/or scheme
return undef unless my $location = $res->headers->location;
$location = Mojo::URL->new($location);
$location = $location->base($old->req->url)->to_abs unless $location->is_abs;
my $proto = $location->protocol;
return undef unless $proto eq 'http' || $proto eq 'https';
# Clone request if necessary
my $new = Mojo::Transaction::HTTP->new;
my $req = $old->req;
if ($code == 307 || $code == 308) {
return undef unless my $clone = $req->clone;
$new->req($clone);
}
else {
my $method = uc $req->method;
my $headers = $new->req->method($method eq 'POST' ? 'GET' : $method)
->content->headers($req->headers->clone)->headers;
$headers->remove($_) for grep {/^content-/i} @{$headers->names};
}
my $headers = $new->req->url($location)->headers;
$headers->remove($_) for qw(Authorization Cookie Host Referer);
return $new->previous($old);
}
sub tx {
my $self = shift;
# Method and URL
my $tx = Mojo::Transaction::HTTP->new;
my $req = $tx->req->method(shift);
my $url = shift;
$url = "http://$url" unless $url =~ m!^/|://!;
ref $url ? $req->url($url) : $req->url->parse($url);
# Headers (we identify ourselves and accept gzip compression)
my $headers = $req->headers;
$headers->from_hash(shift) if ref $_[0] eq 'HASH';
$headers->user_agent($self->name) unless $headers->user_agent;
$headers->accept_encoding('gzip') unless $headers->accept_encoding;
# Generator
if (@_ > 1) {
return $tx unless my $generator = $self->generators->{shift()};
$self->$generator($tx, @_);
}
# Body
elsif (@_) { $req->body(shift) }
return $tx;
}
sub upgrade {
my ($self, $tx) = @_;
my $code = $tx->res->code // 0;
return undef unless $tx->req->is_handshake && $code == 101;
my $ws = Mojo::Transaction::WebSocket->new(handshake => $tx, masked => 1);
return $ws->client_challenge ? $ws : undef;
}
sub websocket {
my $self = shift;
# New WebSocket transaction
my $sub = ref $_[-1] eq 'ARRAY' ? pop : [];
my $tx = $self->tx(GET => @_);
my $req = $tx->req;
$req->headers->sec_websocket_protocol(join ', ', @$sub) if @$sub;
my $url = $req->url;
my $proto = $url->protocol;
$url->scheme($proto eq 'wss' ? 'https' : 'http') if $proto;
# Handshake
Mojo::Transaction::WebSocket->new(handshake => $tx)->client_handshake;
return $tx;
}
sub _form {
my ($self, $tx, $form, %options) = @_;
# Check for uploads and force multipart if necessary
my $req = $tx->req;
my $headers = $req->headers;
my $multipart = ($headers->content_type // '') =~ m!multipart/form-data!i;
for my $value (map { ref $_ eq 'ARRAY' ? @$_ : $_ } values %$form) {
++$multipart and last if ref $value eq 'HASH';
}
# Multipart
if ($multipart) {
my $parts = $self->_multipart($options{charset}, $form);
$req->content(
Mojo::Content::MultiPart->new(headers => $headers, parts => $parts));
_type($headers, 'multipart/form-data');
return $tx;
}
# Query parameters or urlencoded
my $p = Mojo::Parameters->new(map { $_ => $form->{$_} } sort keys %$form);
$p->charset($options{charset}) if defined $options{charset};
my $method = uc $req->method;
if ($method eq 'GET' || $method eq 'HEAD') { $req->url->query->merge($p) }
else {
$req->body($p->to_string);
_type($headers, 'application/x-www-form-urlencoded');
}
return $tx;
}
sub _json {
my ($self, $tx, $data) = @_;
_type($tx->req->body(encode_json $data)->headers, 'application/json');
return $tx;
}
sub _multipart {
my ($self, $charset, $form) = @_;
my @parts;
for my $name (sort keys %$form) {
my $values = $form->{$name};
for my $value (ref $values eq 'ARRAY' ? @$values : ($values)) {
push @parts, my $part = Mojo::Content::Single->new;
# Upload
my $filename;
my $headers = $part->headers;
if (ref $value eq 'HASH') {
# File
if (my $file = delete $value->{file}) {
$file = Mojo::Asset::File->new(path => $file) unless ref $file;
$part->asset($file);
$value->{filename} //= basename $file->path
if $file->isa('Mojo::Asset::File');
}
# Memory
elsif (defined(my $content = delete $value->{content})) {
$part->asset(Mojo::Asset::Memory->new->add_chunk($content));
}
# Filename and headers
$filename = url_escape delete $value->{filename} // $name, '"';
$filename = encode $charset, $filename if $charset;
$headers->from_hash($value);
}
# Field
else {
$value = encode $charset, $value if $charset;
$part->asset(Mojo::Asset::Memory->new->add_chunk($value));
}
# Content-Disposition
$name = url_escape $name, '"';
$name = encode $charset, $name if $charset;
my $disposition = qq{form-data; name="$name"};
$disposition .= qq{; filename="$filename"} if defined $filename;
$headers->content_disposition($disposition);
}
}
return \@parts;
}
sub _proxy {
my ($self, $tx, $proto, $host, $port) = @_;
# Update with proxy information
if (my $proxy = $tx->req->proxy) {
$proto = $proxy->protocol;
$host = $proxy->ihost;
$port = $proxy->port || ($proto eq 'https' ? 443 : 80);
}
return $proto, $host, $port;
}
sub _type { $_[0]->content_type($_[1]) unless $_[0]->content_type }
1;
=encoding utf8
=head1 NAME
Mojo::UserAgent::Transactor - User agent transactor
=head1 SYNOPSIS
use Mojo::UserAgent::Transactor;
# Simple GET request
my $t = Mojo::UserAgent::Transactor->new;
say $t->tx(GET => 'http://example.com')->req->to_string;
# PATCH request with "Do Not Track" header and content
say $t->tx(PATCH => 'example.com' => {DNT => 1} => 'Hi!')->req->to_string;
# POST request with form-data
say $t->tx(POST => 'example.com' => form => {a => 'b'})->req->to_string;
# PUT request with JSON data
say $t->tx(PUT => 'example.com' => json => {a => 'b'})->req->to_string;
=head1 DESCRIPTION
L<Mojo::UserAgent::Transactor> is the transaction building and manipulation
framework used by L<Mojo::UserAgent>.
=head1 GENERATORS
These content generators are available by default.
=head2 form
$t->tx(POST => 'http://example.com' => form => {a => 'b'});
Generate query string, C<application/x-www-form-urlencoded> or
C<multipart/form-data> content.
=head2 json
$t->tx(PATCH => 'http://example.com' => json => {a => 'b'});
Generate JSON content with L<Mojo::JSON>.
=head1 ATTRIBUTES
L<Mojo::UserAgent::Transactor> implements the following attributes.
=head2 generators
my $generators = $t->generators;
$t = $t->generators({foo => sub {...}});
Registered content generators, by default only C<form> and C<json> are already
defined.
=head2 name
my $name = $t->name;
$t = $t->name('Mojolicious');
Value for C<User-Agent> request header of generated transactions, defaults to
C<Mojolicious (Perl)>.
=head1 METHODS
L<Mojo::UserAgent::Transactor> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 add_generator
$t = $t->add_generator(foo => sub {...});
Register a new content generator.
=head2 endpoint
my ($proto, $host, $port) = $t->endpoint(Mojo::Transaction::HTTP->new);
Actual endpoint for transaction.
=head2 peer
my ($proto, $host, $port) = $t->peer(Mojo::Transaction::HTTP->new);
Actual peer for transaction.
=head2 proxy_connect
my $tx = $t->proxy_connect(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::HTTP> proxy C<CONNECT> request for transaction if
possible.
=head2 redirect
my $tx = $t->redirect(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::HTTP> followup request for C<301>, C<302>, C<303>,
C<307> or C<308> redirect response if possible.
=head2 tx
my $tx = $t->tx(GET => 'example.com');
my $tx = $t->tx(POST => 'http://example.com');
my $tx = $t->tx(GET => 'http://example.com' => {Accept => '*/*'});
my $tx = $t->tx(PUT => 'http://example.com' => 'Hi!');
my $tx = $t->tx(PUT => 'http://example.com' => form => {a => 'b'});
my $tx = $t->tx(PUT => 'http://example.com' => json => {a => 'b'});
my $tx = $t->tx(POST => 'http://example.com' => {Accept => '*/*'} => 'Hi!');
my $tx = $t->tx(
PUT => 'http://example.com' => {Accept => '*/*'} => form => {a => 'b'});
my $tx = $t->tx(
PUT => 'http://example.com' => {Accept => '*/*'} => json => {a => 'b'});
Versatile general purpose L<Mojo::Transaction::HTTP> transaction builder for
requests, with support for L</"GENERATORS">.
# Generate and inspect custom GET request with DNT header and content
say $t->tx(GET => 'example.com' => {DNT => 1} => 'Bye!')->req->to_string;
# Use a custom socket for processing this transaction
my $tx = $t->tx(GET => 'http://example.com');
$tx->connection($sock);
# Stream response content to STDOUT
my $tx = $t->tx(GET => 'http://example.com');
$tx->res->content->unsubscribe('read')->on(read => sub { say $_[1] });
# PUT request with content streamed from file
my $tx = $t->tx(PUT => 'http://example.com');
$tx->req->content->asset(Mojo::Asset::File->new(path => '/foo.txt'));
The C<json> content generator uses L<Mojo::JSON> for encoding and sets the
content type to C<application/json>.
# POST request with "application/json" content
my $tx = $t->tx(
POST => 'http://example.com' => json => {a => 'b', c => [1, 2, 3]});
The C<form> content generator will automatically use query parameters for
C<GET> and C<HEAD> requests.
# GET request with query parameters
my $tx = $t->tx(GET => 'http://example.com' => form => {a => 'b'});
For all other request methods the C<application/x-www-form-urlencoded> content
type is used.
# POST request with "application/x-www-form-urlencoded" content
my $tx = $t->tx(
POST => 'http://example.com' => form => {a => 'b', c => 'd'});
Parameters may be encoded with the C<charset> option.
# PUT request with Shift_JIS encoded form values
my $tx = $t->tx(
PUT => 'example.com' => form => {a => 'b'} => charset => 'Shift_JIS');
An array reference can be used for multiple form values sharing the same name.
# POST request with form values sharing the same name
my $tx = $t->tx(POST => 'http://example.com' => form => {a => [qw(b c d)]});
A hash reference with a C<content> or C<file> value can be used to switch to
the C<multipart/form-data> content type for file uploads.
# POST request with "multipart/form-data" content
my $tx = $t->tx(
POST => 'http://example.com' => form => {mytext => {content => 'lala'}});
# POST request with multiple files sharing the same name
my $tx = $t->tx(POST => 'http://example.com' =>
form => {mytext => [{content => 'first'}, {content => 'second'}]});
The C<file> value should contain the path to the file you want to upload or an
asset object, like L<Mojo::Asset::File> or L<Mojo::Asset::Memory>.
# POST request with upload streamed from file
my $tx = $t->tx(
POST => 'http://example.com' => form => {mytext => {file => '/foo.txt'}});
# POST request with upload streamed from asset
my $asset = Mojo::Asset::Memory->new->add_chunk('lalala');
my $tx = $t->tx(
POST => 'http://example.com' => form => {mytext => {file => $asset}});
A C<filename> value will be generated automatically, but can also be set
manually if necessary. All remainging values in the hash reference get merged
into the C<multipart/form-data> content as headers.
# POST request with form values and customized upload (filename and header)
my $tx = $t->tx(POST => 'http://example.com' => form => {
a => 'b',
c => 'd',
mytext => {
content => 'lalala',
filename => 'foo.txt',
'Content-Type' => 'text/plain'
}
});
The C<multipart/form-data> content type can also be enforced by setting the
C<Content-Type> header manually.
# Force "multipart/form-data"
my $headers = {'Content-Type' => 'multipart/form-data'};
my $tx = $t->tx(POST => 'example.com' => $headers => form => {a => 'b'});
=head2 upgrade
my $tx = $t->upgrade(Mojo::Transaction::HTTP->new);
Build L<Mojo::Transaction::WebSocket> followup transaction for WebSocket
handshake if possible.
=head2 websocket
my $tx = $t->websocket('ws://example.com');
my $tx = $t->websocket('ws://example.com' => {DNT => 1} => ['v1.proto']);
Versatile L<Mojo::Transaction::HTTP> transaction builder for WebSocket
handshake requests.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_USERAGENT_TRANSACTOR
$fatpacked{"Mojo/Util.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJO_UTIL';
package Mojo::Util;
use Mojo::Base -strict;
use Carp qw(carp croak);
use Data::Dumper ();
use Digest::MD5 qw(md5 md5_hex);
use Digest::SHA qw(hmac_sha1_hex sha1 sha1_hex);
use Encode 'find_encoding';
use Exporter 'import';
use List::Util 'min';
use MIME::Base64 qw(decode_base64 encode_base64);
use Symbol 'delete_package';
use Time::HiRes ();
# Check for monotonic clock support
use constant MONOTONIC => eval
'!!Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC())';
# Punycode bootstring parameters
use constant {
PC_BASE => 36,
PC_TMIN => 1,
PC_TMAX => 26,
PC_SKEW => 38,
PC_DAMP => 700,
PC_INITIAL_BIAS => 72,
PC_INITIAL_N => 128
};
# Will be shipping with Perl 5.22
my $NAME = eval 'use Sub::Util; 1' ? \&Sub::Util::set_subname : sub { $_[1] };
# To update HTML entities run this command
# perl -pi -e '$_=$seen++?"":"__DATA__\n".`$^X examples/entities.pl`."__END__\n"
# if $d=/^__DATA__/../^__END__/' lib/Mojo/Util.pm
my %ENTITIES;
while (my $line = <DATA>) {
next unless $line =~ /^(\S+)\s+U\+(\S+)(?:\s+U\+(\S+))?/;
$ENTITIES{$1} = defined $3 ? (chr(hex $2) . chr(hex $3)) : chr(hex $2);
}
# Characters that should be escaped in XML
my %XML = (
'&' => '&amp;',
'<' => '&lt;',
'>' => '&gt;',
'"' => '&quot;',
'\'' => '&#39;'
);
# Encoding cache
my %CACHE;
our @EXPORT_OK = (
qw(b64_decode b64_encode camelize class_to_file class_to_path decamelize),
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)
);
sub b64_decode { decode_base64($_[0]) }
sub b64_encode { encode_base64($_[0], $_[1]) }
sub camelize {
my $str = shift;
return $str if $str =~ /^[A-Z]/;
# CamelCase words
return join '::', map {
join('', map { ucfirst lc } split '_')
} split '-', $str;
}
sub class_to_file {
my $class = shift;
$class =~ s/::|'//g;
$class =~ s/([A-Z])([A-Z]*)/$1.lc($2)/ge;
return decamelize($class);
}
sub class_to_path { join '.', join('/', split /::|'/, shift), 'pm' }
sub decamelize {
my $str = shift;
return $str if $str !~ /^[A-Z]/;
# snake_case words
return join '-', map {
join('_', map {lc} grep {length} split /([A-Z]{1}[^A-Z]*)/)
} split '::', $str;
}
sub decode {
my ($encoding, $bytes) = @_;
return undef
unless eval { $bytes = _encoding($encoding)->decode("$bytes", 1); 1 };
return $bytes;
}
sub deprecated {
local $Carp::CarpLevel = 1;
$ENV{MOJO_FATAL_DEPRECATIONS} ? croak(@_) : carp(@_);
}
sub dumper {
Data::Dumper->new([@_])->Indent(1)->Sortkeys(1)->Terse(1)->Useqq(1)->Dump;
}
sub encode { _encoding($_[0])->encode("$_[1]") }
sub hmac_sha1_sum { hmac_sha1_hex(@_) }
sub html_unescape {
my $str = shift;
$str =~ s/&(?:\#((?:\d{1,7}|x[0-9a-fA-F]{1,6}));|(\w+;?))/_decode($1, $2)/ge;
return $str;
}
sub md5_bytes { md5(@_) }
sub md5_sum { md5_hex(@_) }
sub monkey_patch {
my ($class, %patch) = @_;
no strict 'refs';
no warnings 'redefine';
*{"${class}::$_"} = $NAME->("${class}::$_", $patch{$_}) for keys %patch;
}
# Direct translation of RFC 3492
sub punycode_decode {
my $input = shift;
use integer;
my $n = PC_INITIAL_N;
my $i = 0;
my $bias = PC_INITIAL_BIAS;
my @output;
# Consume all code points before the last delimiter
push @output, split '', $1 if $input =~ s/(.*)\x2d//s;
while (length $input) {
my $oldi = $i;
my $w = 1;
# Base to infinity in steps of base
for (my $k = PC_BASE; 1; $k += PC_BASE) {
my $digit = ord substr $input, 0, 1, '';
$digit = $digit < 0x40 ? $digit + (26 - 0x30) : ($digit & 0x1f) - 1;
$i += $digit * $w;
my $t = $k - $bias;
$t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
last if $digit < $t;
$w *= PC_BASE - $t;
}
$bias = _adapt($i - $oldi, @output + 1, $oldi == 0);
$n += $i / (@output + 1);
$i = $i % (@output + 1);
splice @output, $i++, 0, chr $n;
}
return join '', @output;
}
# Direct translation of RFC 3492
sub punycode_encode {
my $output = shift;
use integer;
my $n = PC_INITIAL_N;
my $delta = 0;
my $bias = PC_INITIAL_BIAS;
# Extract basic code points
my $len = length $output;
my @input = map {ord} split '', $output;
my @chars = sort grep { $_ >= PC_INITIAL_N } @input;
$output =~ s/[^\x00-\x7f]+//gs;
my $h = my $b = length $output;
$output .= "\x2d" if $b > 0;
for my $m (@chars) {
next if $m < $n;
$delta += ($m - $n) * ($h + 1);
$n = $m;
for (my $i = 0; $i < $len; $i++) {
my $c = $input[$i];
if ($c < $n) { $delta++ }
elsif ($c == $n) {
my $q = $delta;
# Base to infinity in steps of base
for (my $k = PC_BASE; 1; $k += PC_BASE) {
my $t = $k - $bias;
$t = $t < PC_TMIN ? PC_TMIN : $t > PC_TMAX ? PC_TMAX : $t;
last if $q < $t;
my $o = $t + (($q - $t) % (PC_BASE - $t));
$output .= chr $o + ($o < 26 ? 0x61 : 0x30 - 26);
$q = ($q - $t) / (PC_BASE - $t);
}
$output .= chr $q + ($q < 26 ? 0x61 : 0x30 - 26);
$bias = _adapt($delta, $h + 1, $h == $b);
$delta = 0;
$h++;
}
}
$delta++;
$n++;
}
return $output;
}
sub quote {
my $str = shift;
$str =~ s/(["\\])/\\$1/g;
return qq{"$str"};
}
sub secure_compare {
my ($a, $b) = @_;
return undef if length $a != length $b;
my $r = 0;
$r |= ord(substr $a, $_) ^ ord(substr $b, $_) for 0 .. length($a) - 1;
return $r == 0;
}
sub sha1_bytes { sha1(@_) }
sub sha1_sum { sha1_hex(@_) }
sub slurp {
my $path = shift;
croak qq{Can't open file "$path": $!} unless open my $file, '<', $path;
my $content = '';
while ($file->sysread(my $buffer, 131072, 0)) { $content .= $buffer }
return $content;
}
sub split_header {
my $str = shift;
my (@tree, @token);
while ($str =~ s/^[,;\s]*([^=;, ]+)\s*//) {
push @token, $1, undef;
$token[-1] = unquote($1)
if $str =~ s/^=\s*("(?:\\\\|\\"|[^"])*"|[^;, ]*)\s*//;
# Separator
$str =~ s/^;\s*//;
next unless $str =~ s/^,\s*//;
push @tree, [@token];
@token = ();
}
# Take care of final token
return [@token ? (@tree, \@token) : @tree];
}
sub spurt {
my ($content, $path) = @_;
croak qq{Can't open file "$path": $!} unless open my $file, '>', $path;
croak qq{Can't write to file "$path": $!}
unless defined $file->syswrite($content);
return $content;
}
sub squish {
my $str = trim(@_);
$str =~ s/\s+/ /g;
return $str;
}
sub steady_time () {
MONOTONIC
? Time::HiRes::clock_gettime(Time::HiRes::CLOCK_MONOTONIC())
: Time::HiRes::time;
}
sub tablify {
my $rows = shift;
my @spec;
for my $row (@$rows) {
for my $i (0 .. $#$row) {
$row->[$i] =~ s/[\r\n]//g;
my $len = length $row->[$i];
$spec[$i] = $len if $len >= ($spec[$i] // 0);
}
}
my $format = join ' ', map({"\%-${_}s"} @spec[0 .. $#spec - 1]), '%s';
return join '', map { sprintf "$format\n", @$_ } @$rows;
}
sub trim {
my $str = shift;
$str =~ s/^\s+//;
$str =~ s/\s+$//;
return $str;
}
sub unindent {
my $str = shift;
my $min = min map { m/^([ \t]*)/; length $1 || () } split "\n", $str;
$str =~ s/^[ \t]{0,$min}//gm if $min;
return $str;
}
sub unquote {
my $str = shift;
return $str unless $str =~ s/^"(.*)"$/$1/g;
$str =~ s/\\\\/\\/g;
$str =~ s/\\"/"/g;
return $str;
}
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 }
return $str;
}
sub url_unescape {
my $str = shift;
$str =~ s/%([0-9a-fA-F]{2})/chr(hex($1))/ge;
return $str;
}
sub xml_escape {
my $str = shift;
$str =~ s/([&<>"'])/$XML{$1}/ge;
return $str;
}
sub xor_encode {
my ($input, $key) = @_;
# Encode with variable key length
my $len = length $key;
my $buffer = my $output = '';
$output .= $buffer ^ $key
while length($buffer = substr($input, 0, $len, '')) == $len;
return $output .= $buffer ^ substr($key, 0, length $buffer, '');
}
sub xss_escape {
no warnings 'uninitialized';
ref $_[0] eq 'Mojo::ByteStream' ? $_[0] : xml_escape("$_[0]");
}
sub _adapt {
my ($delta, $numpoints, $firsttime) = @_;
use integer;
$delta = $firsttime ? $delta / PC_DAMP : $delta / 2;
$delta += $delta / $numpoints;
my $k = 0;
while ($delta > ((PC_BASE - PC_TMIN) * PC_TMAX) / 2) {
$delta /= PC_BASE - PC_TMIN;
$k += PC_BASE;
}
return $k + (((PC_BASE - PC_TMIN + 1) * $delta) / ($delta + PC_SKEW));
}
sub _decode {
my ($point, $name) = @_;
# Code point
return chr($point !~ /^x/ ? $point : hex $point) unless defined $name;
# Find entity name
my $rest = '';
while (length $name) {
return "$ENTITIES{$name}$rest" if exists $ENTITIES{$name};
$rest = chop($name) . $rest;
}
return "&$rest";
}
sub _encoding {
$CACHE{$_[0]} //= find_encoding($_[0]) // croak "Unknown encoding '$_[0]'";
}
sub _options {
# Hash or name (one)
return ref $_[0] eq 'HASH' ? (undef, %{shift()}) : @_ if @_ == 1;
# Name and values (odd)
return shift, @_ if @_ % 2;
# Name and hash or just values (even)
return ref $_[1] eq 'HASH' ? (shift, %{shift()}) : (undef, @_);
}
sub _stash {
my ($name, $object) = (shift, shift);
# Hash
my $dict = $object->{$name} ||= {};
return $dict unless @_;
# Get
return $dict->{$_[0]} unless @_ > 1 || ref $_[0];
# Set
my $values = ref $_[0] ? $_[0] : {@_};
@$dict{keys %$values} = values %$values;
return $object;
}
sub _teardown {
return unless my $class = shift;
# @ISA has to be cleared first because of circular references
no strict 'refs';
@{"${class}::ISA"} = ();
delete_package $class;
}
1;
__DATA__
Aacute; U+000C1
Aacute U+000C1
aacute; U+000E1
aacute U+000E1
Abreve; U+00102
abreve; U+00103
ac; U+0223E
acd; U+0223F
acE; U+0223E U+00333
Acirc; U+000C2
Acirc U+000C2
acirc; U+000E2
acirc U+000E2
acute; U+000B4
acute U+000B4
Acy; U+00410
acy; U+00430
AElig; U+000C6
AElig U+000C6
aelig; U+000E6
aelig U+000E6
af; U+02061
Afr; U+1D504
afr; U+1D51E
Agrave; U+000C0
Agrave U+000C0
agrave; U+000E0
agrave U+000E0
alefsym; U+02135
aleph; U+02135
Alpha; U+00391
alpha; U+003B1
Amacr; U+00100
amacr; U+00101
amalg; U+02A3F
AMP; U+00026
AMP U+00026
amp; U+00026
amp U+00026
And; U+02A53
and; U+02227
andand; U+02A55
andd; U+02A5C
andslope; U+02A58
andv; U+02A5A
ang; U+02220
ange; U+029A4
angle; U+02220
angmsd; U+02221
angmsdaa; U+029A8
angmsdab; U+029A9
angmsdac; U+029AA
angmsdad; U+029AB
angmsdae; U+029AC
angmsdaf; U+029AD
angmsdag; U+029AE
angmsdah; U+029AF
angrt; U+0221F
angrtvb; U+022BE
angrtvbd; U+0299D
angsph; U+02222
angst; U+000C5
angzarr; U+0237C
Aogon; U+00104
aogon; U+00105
Aopf; U+1D538
aopf; U+1D552
ap; U+02248
apacir; U+02A6F
apE; U+02A70
ape; U+0224A
apid; U+0224B
apos; U+00027
ApplyFunction; U+02061
approx; U+02248
approxeq; U+0224A
Aring; U+000C5
Aring U+000C5
aring; U+000E5
aring U+000E5
Ascr; U+1D49C
ascr; U+1D4B6
Assign; U+02254
ast; U+0002A
asymp; U+02248
asympeq; U+0224D
Atilde; U+000C3
Atilde U+000C3
atilde; U+000E3
atilde U+000E3
Auml; U+000C4
Auml U+000C4
auml; U+000E4
auml U+000E4
awconint; U+02233
awint; U+02A11
backcong; U+0224C
backepsilon; U+003F6
backprime; U+02035
backsim; U+0223D
backsimeq; U+022CD
Backslash; U+02216
Barv; U+02AE7
barvee; U+022BD
Barwed; U+02306
barwed; U+02305
barwedge; U+02305
bbrk; U+023B5
bbrktbrk; U+023B6
bcong; U+0224C
Bcy; U+00411
bcy; U+00431
bdquo; U+0201E
becaus; U+02235
Because; U+02235
because; U+02235
bemptyv; U+029B0
bepsi; U+003F6
bernou; U+0212C
Bernoullis; U+0212C
Beta; U+00392
beta; U+003B2
beth; U+02136
between; U+0226C
Bfr; U+1D505
bfr; U+1D51F
bigcap; U+022C2
bigcirc; U+025EF
bigcup; U+022C3
bigodot; U+02A00
bigoplus; U+02A01
bigotimes; U+02A02
bigsqcup; U+02A06
bigstar; U+02605
bigtriangledown; U+025BD
bigtriangleup; U+025B3
biguplus; U+02A04
bigvee; U+022C1
bigwedge; U+022C0
bkarow; U+0290D
blacklozenge; U+029EB
blacksquare; U+025AA
blacktriangle; U+025B4
blacktriangledown; U+025BE
blacktriangleleft; U+025C2
blacktriangleright; U+025B8
blank; U+02423
blk12; U+02592
blk14; U+02591
blk34; U+02593
block; U+02588
bne; U+0003D U+020E5
bnequiv; U+02261 U+020E5
bNot; U+02AED
bnot; U+02310
Bopf; U+1D539
bopf; U+1D553
bot; U+022A5
bottom; U+022A5
bowtie; U+022C8
boxbox; U+029C9
boxDL; U+02557
boxDl; U+02556
boxdL; U+02555
boxdl; U+02510
boxDR; U+02554
boxDr; U+02553
boxdR; U+02552
boxdr; U+0250C
boxH; U+02550
boxh; U+02500
boxHD; U+02566
boxHd; U+02564
boxhD; U+02565
boxhd; U+0252C
boxHU; U+02569
boxHu; U+02567
boxhU; U+02568
boxhu; U+02534
boxminus; U+0229F
boxplus; U+0229E
boxtimes; U+022A0
boxUL; U+0255D
boxUl; U+0255C
boxuL; U+0255B
boxul; U+02518
boxUR; U+0255A
boxUr; U+02559
boxuR; U+02558
boxur; U+02514
boxV; U+02551
boxv; U+02502
boxVH; U+0256C
boxVh; U+0256B
boxvH; U+0256A
boxvh; U+0253C
boxVL; U+02563
boxVl; U+02562
boxvL; U+02561
boxvl; U+02524
boxVR; U+02560
boxVr; U+0255F
boxvR; U+0255E
boxvr; U+0251C
bprime; U+02035
Breve; U+002D8
breve; U+002D8
brvbar; U+000A6
brvbar U+000A6
Bscr; U+0212C
bscr; U+1D4B7
bsemi; U+0204F
bsim; U+0223D
bsime; U+022CD
bsol; U+0005C
bsolb; U+029C5
bsolhsub; U+027C8
bull; U+02022
bullet; U+02022
bump; U+0224E
bumpE; U+02AAE
bumpe; U+0224F
Bumpeq; U+0224E
bumpeq; U+0224F
Cacute; U+00106
cacute; U+00107
Cap; U+022D2
cap; U+02229
capand; U+02A44
capbrcup; U+02A49
capcap; U+02A4B
capcup; U+02A47
capdot; U+02A40
CapitalDifferentialD; U+02145
caps; U+02229 U+0FE00
caret; U+02041
caron; U+002C7
Cayleys; U+0212D
ccaps; U+02A4D
Ccaron; U+0010C
ccaron; U+0010D
Ccedil; U+000C7
Ccedil U+000C7
ccedil; U+000E7
ccedil U+000E7
Ccirc; U+00108
ccirc; U+00109
Cconint; U+02230
ccups; U+02A4C
ccupssm; U+02A50
Cdot; U+0010A
cdot; U+0010B
cedil; U+000B8
cedil U+000B8
Cedilla; U+000B8
cemptyv; U+029B2
cent; U+000A2
cent U+000A2
CenterDot; U+000B7
centerdot; U+000B7
Cfr; U+0212D
cfr; U+1D520
CHcy; U+00427
chcy; U+00447
check; U+02713
checkmark; U+02713
Chi; U+003A7
chi; U+003C7
cir; U+025CB
circ; U+002C6
circeq; U+02257
circlearrowleft; U+021BA
circlearrowright; U+021BB
circledast; U+0229B
circledcirc; U+0229A
circleddash; U+0229D
CircleDot; U+02299
circledR; U+000AE
circledS; U+024C8
CircleMinus; U+02296
CirclePlus; U+02295
CircleTimes; U+02297
cirE; U+029C3
cire; U+02257
cirfnint; U+02A10
cirmid; U+02AEF
cirscir; U+029C2
ClockwiseContourIntegral; U+02232
CloseCurlyDoubleQuote; U+0201D
CloseCurlyQuote; U+02019
clubs; U+02663
clubsuit; U+02663
Colon; U+02237
colon; U+0003A
Colone; U+02A74
colone; U+02254
coloneq; U+02254
comma; U+0002C
commat; U+00040
comp; U+02201
compfn; U+02218
complement; U+02201
complexes; U+02102
cong; U+02245
congdot; U+02A6D
Congruent; U+02261
Conint; U+0222F
conint; U+0222E
ContourIntegral; U+0222E
Copf; U+02102
copf; U+1D554
coprod; U+02210
Coproduct; U+02210
COPY; U+000A9
COPY U+000A9
copy; U+000A9
copy U+000A9
copysr; U+02117
CounterClockwiseContourIntegral; U+02233
crarr; U+021B5
Cross; U+02A2F
cross; U+02717
Cscr; U+1D49E
cscr; U+1D4B8
csub; U+02ACF
csube; U+02AD1
csup; U+02AD0
csupe; U+02AD2
ctdot; U+022EF
cudarrl; U+02938
cudarrr; U+02935
cuepr; U+022DE
cuesc; U+022DF
cularr; U+021B6
cularrp; U+0293D
Cup; U+022D3
cup; U+0222A
cupbrcap; U+02A48
CupCap; U+0224D
cupcap; U+02A46
cupcup; U+02A4A
cupdot; U+0228D
cupor; U+02A45
cups; U+0222A U+0FE00
curarr; U+021B7
curarrm; U+0293C
curlyeqprec; U+022DE
curlyeqsucc; U+022DF
curlyvee; U+022CE
curlywedge; U+022CF
curren; U+000A4
curren U+000A4
curvearrowleft; U+021B6
curvearrowright; U+021B7
cuvee; U+022CE
cuwed; U+022CF
cwconint; U+02232
cwint; U+02231
cylcty; U+0232D
Dagger; U+02021
dagger; U+02020
daleth; U+02138
Darr; U+021A1
dArr; U+021D3
darr; U+02193
dash; U+02010
Dashv; U+02AE4
dashv; U+022A3
dbkarow; U+0290F
dblac; U+002DD
Dcaron; U+0010E
dcaron; U+0010F
Dcy; U+00414
dcy; U+00434
DD; U+02145
dd; U+02146
ddagger; U+02021
ddarr; U+021CA
DDotrahd; U+02911
ddotseq; U+02A77
deg; U+000B0
deg U+000B0
Del; U+02207
Delta; U+00394
delta; U+003B4
demptyv; U+029B1
dfisht; U+0297F
Dfr; U+1D507
dfr; U+1D521
dHar; U+02965
dharl; U+021C3
dharr; U+021C2
DiacriticalAcute; U+000B4
DiacriticalDot; U+002D9
DiacriticalDoubleAcute; U+002DD
DiacriticalGrave; U+00060
DiacriticalTilde; U+002DC
diam; U+022C4
Diamond; U+022C4
diamond; U+022C4
diamondsuit; U+02666
diams; U+02666
die; U+000A8
DifferentialD; U+02146
digamma; U+003DD
disin; U+022F2
div; U+000F7
divide; U+000F7
divide U+000F7
divideontimes; U+022C7
divonx; U+022C7
DJcy; U+00402
djcy; U+00452
dlcorn; U+0231E
dlcrop; U+0230D
dollar; U+00024
Dopf; U+1D53B
dopf; U+1D555
Dot; U+000A8
dot; U+002D9
DotDot; U+020DC
doteq; U+02250
doteqdot; U+02251
DotEqual; U+02250
dotminus; U+02238
dotplus; U+02214
dotsquare; U+022A1
doublebarwedge; U+02306
DoubleContourIntegral; U+0222F
DoubleDot; U+000A8
DoubleDownArrow; U+021D3
DoubleLeftArrow; U+021D0
DoubleLeftRightArrow; U+021D4
DoubleLeftTee; U+02AE4
DoubleLongLeftArrow; U+027F8
DoubleLongLeftRightArrow; U+027FA
DoubleLongRightArrow; U+027F9
DoubleRightArrow; U+021D2
DoubleRightTee; U+022A8
DoubleUpArrow; U+021D1
DoubleUpDownArrow; U+021D5
DoubleVerticalBar; U+02225
DownArrow; U+02193
Downarrow; U+021D3
downarrow; U+02193
DownArrowBar; U+02913
DownArrowUpArrow; U+021F5
DownBreve; U+00311
downdownarrows; U+021CA
downharpoonleft; U+021C3
downharpoonright; U+021C2
DownLeftRightVector; U+02950
DownLeftTeeVector; U+0295E
DownLeftVector; U+021BD
DownLeftVectorBar; U+02956
DownRightTeeVector; U+0295F
DownRightVector; U+021C1
DownRightVectorBar; U+02957
DownTee; U+022A4
DownTeeArrow; U+021A7
drbkarow; U+02910
drcorn; U+0231F
drcrop; U+0230C
Dscr; U+1D49F
dscr; U+1D4B9
DScy; U+00405
dscy; U+00455
dsol; U+029F6
Dstrok; U+00110
dstrok; U+00111
dtdot; U+022F1
dtri; U+025BF
dtrif; U+025BE
duarr; U+021F5
duhar; U+0296F
dwangle; U+029A6
DZcy; U+0040F
dzcy; U+0045F
dzigrarr; U+027FF
Eacute; U+000C9
Eacute U+000C9
eacute; U+000E9
eacute U+000E9
easter; U+02A6E
Ecaron; U+0011A
ecaron; U+0011B
ecir; U+02256
Ecirc; U+000CA
Ecirc U+000CA
ecirc; U+000EA
ecirc U+000EA
ecolon; U+02255
Ecy; U+0042D
ecy; U+0044D
eDDot; U+02A77
Edot; U+00116
eDot; U+02251
edot; U+00117
ee; U+02147
efDot; U+02252
Efr; U+1D508
efr; U+1D522
eg; U+02A9A
Egrave; U+000C8
Egrave U+000C8
egrave; U+000E8
egrave U+000E8
egs; U+02A96
egsdot; U+02A98
el; U+02A99
Element; U+02208
elinters; U+023E7
ell; U+02113
els; U+02A95
elsdot; U+02A97
Emacr; U+00112
emacr; U+00113
empty; U+02205
emptyset; U+02205
EmptySmallSquare; U+025FB
emptyv; U+02205
EmptyVerySmallSquare; U+025AB
emsp; U+02003
emsp13; U+02004
emsp14; U+02005
ENG; U+0014A
eng; U+0014B
ensp; U+02002
Eogon; U+00118
eogon; U+00119
Eopf; U+1D53C
eopf; U+1D556
epar; U+022D5
eparsl; U+029E3
eplus; U+02A71
epsi; U+003B5
Epsilon; U+00395
epsilon; U+003B5
epsiv; U+003F5
eqcirc; U+02256
eqcolon; U+02255
eqsim; U+02242
eqslantgtr; U+02A96
eqslantless; U+02A95
Equal; U+02A75
equals; U+0003D
EqualTilde; U+02242
equest; U+0225F
Equilibrium; U+021CC
equiv; U+02261
equivDD; U+02A78
eqvparsl; U+029E5
erarr; U+02971
erDot; U+02253
Escr; U+02130
escr; U+0212F
esdot; U+02250
Esim; U+02A73
esim; U+02242
Eta; U+00397
eta; U+003B7
ETH; U+000D0
ETH U+000D0
eth; U+000F0
eth U+000F0
Euml; U+000CB
Euml U+000CB
euml; U+000EB
euml U+000EB
euro; U+020AC
excl; U+00021
exist; U+02203
Exists; U+02203
expectation; U+02130
ExponentialE; U+02147
exponentiale; U+02147
fallingdotseq; U+02252
Fcy; U+00424
fcy; U+00444
female; U+02640
ffilig; U+0FB03
fflig; U+0FB00
ffllig; U+0FB04
Ffr; U+1D509
ffr; U+1D523
filig; U+0FB01
FilledSmallSquare; U+025FC
FilledVerySmallSquare; U+025AA
fjlig; U+00066 U+0006A
flat; U+0266D
fllig; U+0FB02
fltns; U+025B1
fnof; U+00192
Fopf; U+1D53D
fopf; U+1D557
ForAll; U+02200
forall; U+02200
fork; U+022D4
forkv; U+02AD9
Fouriertrf; U+02131
fpartint; U+02A0D
frac12; U+000BD
frac12 U+000BD
frac13; U+02153
frac14; U+000BC
frac14 U+000BC
frac15; U+02155
frac16; U+02159
frac18; U+0215B
frac23; U+02154
frac25; U+02156
frac34; U+000BE
frac34 U+000BE
frac35; U+02157
frac38; U+0215C
frac45; U+02158
frac56; U+0215A
frac58; U+0215D
frac78; U+0215E
frasl; U+02044
frown; U+02322
Fscr; U+02131
fscr; U+1D4BB
gacute; U+001F5
Gamma; U+00393
gamma; U+003B3
Gammad; U+003DC
gammad; U+003DD
gap; U+02A86
Gbreve; U+0011E
gbreve; U+0011F
Gcedil; U+00122
Gcirc; U+0011C
gcirc; U+0011D
Gcy; U+00413
gcy; U+00433
Gdot; U+00120
gdot; U+00121
gE; U+02267
ge; U+02265
gEl; U+02A8C
gel; U+022DB
geq; U+02265
geqq; U+02267
geqslant; U+02A7E
ges; U+02A7E
gescc; U+02AA9
gesdot; U+02A80
gesdoto; U+02A82
gesdotol; U+02A84
gesl; U+022DB U+0FE00
gesles; U+02A94
Gfr; U+1D50A
gfr; U+1D524
Gg; U+022D9
gg; U+0226B
ggg; U+022D9
gimel; U+02137
GJcy; U+00403
gjcy; U+00453
gl; U+02277
gla; U+02AA5
glE; U+02A92
glj; U+02AA4
gnap; U+02A8A
gnapprox; U+02A8A
gnE; U+02269
gne; U+02A88
gneq; U+02A88
gneqq; U+02269
gnsim; U+022E7
Gopf; U+1D53E
gopf; U+1D558
grave; U+00060
GreaterEqual; U+02265
GreaterEqualLess; U+022DB
GreaterFullEqual; U+02267
GreaterGreater; U+02AA2
GreaterLess; U+02277
GreaterSlantEqual; U+02A7E
GreaterTilde; U+02273
Gscr; U+1D4A2
gscr; U+0210A
gsim; U+02273
gsime; U+02A8E
gsiml; U+02A90
GT; U+0003E
GT U+0003E
Gt; U+0226B
gt; U+0003E
gt U+0003E
gtcc; U+02AA7
gtcir; U+02A7A
gtdot; U+022D7
gtlPar; U+02995
gtquest; U+02A7C
gtrapprox; U+02A86
gtrarr; U+02978
gtrdot; U+022D7
gtreqless; U+022DB
gtreqqless; U+02A8C
gtrless; U+02277
gtrsim; U+02273
gvertneqq; U+02269 U+0FE00
gvnE; U+02269 U+0FE00
Hacek; U+002C7
hairsp; U+0200A
half; U+000BD
hamilt; U+0210B
HARDcy; U+0042A
hardcy; U+0044A
hArr; U+021D4
harr; U+02194
harrcir; U+02948
harrw; U+021AD
Hat; U+0005E
hbar; U+0210F
Hcirc; U+00124
hcirc; U+00125
hearts; U+02665
heartsuit; U+02665
hellip; U+02026
hercon; U+022B9
Hfr; U+0210C
hfr; U+1D525
HilbertSpace; U+0210B
hksearow; U+02925
hkswarow; U+02926
hoarr; U+021FF
homtht; U+0223B
hookleftarrow; U+021A9
hookrightarrow; U+021AA
Hopf; U+0210D
hopf; U+1D559
horbar; U+02015
HorizontalLine; U+02500
Hscr; U+0210B
hscr; U+1D4BD
hslash; U+0210F
Hstrok; U+00126
hstrok; U+00127
HumpDownHump; U+0224E
HumpEqual; U+0224F
hybull; U+02043
hyphen; U+02010
Iacute; U+000CD
Iacute U+000CD
iacute; U+000ED
iacute U+000ED
ic; U+02063
Icirc; U+000CE
Icirc U+000CE
icirc; U+000EE
icirc U+000EE
Icy; U+00418
icy; U+00438
Idot; U+00130
IEcy; U+00415
iecy; U+00435
iexcl; U+000A1
iexcl U+000A1
iff; U+021D4
Ifr; U+02111
ifr; U+1D526
Igrave; U+000CC
Igrave U+000CC
igrave; U+000EC
igrave U+000EC
ii; U+02148
iiiint; U+02A0C
iiint; U+0222D
iinfin; U+029DC
iiota; U+02129
IJlig; U+00132
ijlig; U+00133
Im; U+02111
Imacr; U+0012A
imacr; U+0012B
image; U+02111
ImaginaryI; U+02148
imagline; U+02110
imagpart; U+02111
imath; U+00131
imof; U+022B7
imped; U+001B5
Implies; U+021D2
in; U+02208
incare; U+02105
infin; U+0221E
infintie; U+029DD
inodot; U+00131
Int; U+0222C
int; U+0222B
intcal; U+022BA
integers; U+02124
Integral; U+0222B
intercal; U+022BA
Intersection; U+022C2
intlarhk; U+02A17
intprod; U+02A3C
InvisibleComma; U+02063
InvisibleTimes; U+02062
IOcy; U+00401
iocy; U+00451
Iogon; U+0012E
iogon; U+0012F
Iopf; U+1D540
iopf; U+1D55A
Iota; U+00399
iota; U+003B9
iprod; U+02A3C
iquest; U+000BF
iquest U+000BF
Iscr; U+02110
iscr; U+1D4BE
isin; U+02208
isindot; U+022F5
isinE; U+022F9
isins; U+022F4
isinsv; U+022F3
isinv; U+02208
it; U+02062
Itilde; U+00128
itilde; U+00129
Iukcy; U+00406
iukcy; U+00456
Iuml; U+000CF
Iuml U+000CF
iuml; U+000EF
iuml U+000EF
Jcirc; U+00134
jcirc; U+00135
Jcy; U+00419
jcy; U+00439
Jfr; U+1D50D
jfr; U+1D527
jmath; U+00237
Jopf; U+1D541
jopf; U+1D55B
Jscr; U+1D4A5
jscr; U+1D4BF
Jsercy; U+00408
jsercy; U+00458
Jukcy; U+00404
jukcy; U+00454
Kappa; U+0039A
kappa; U+003BA
kappav; U+003F0
Kcedil; U+00136
kcedil; U+00137
Kcy; U+0041A
kcy; U+0043A
Kfr; U+1D50E
kfr; U+1D528
kgreen; U+00138
KHcy; U+00425
khcy; U+00445
KJcy; U+0040C
kjcy; U+0045C
Kopf; U+1D542
kopf; U+1D55C
Kscr; U+1D4A6
kscr; U+1D4C0
lAarr; U+021DA
Lacute; U+00139
lacute; U+0013A
laemptyv; U+029B4
lagran; U+02112
Lambda; U+0039B
lambda; U+003BB
Lang; U+027EA
lang; U+027E8
langd; U+02991
langle; U+027E8
lap; U+02A85
Laplacetrf; U+02112
laquo; U+000AB
laquo U+000AB
Larr; U+0219E
lArr; U+021D0
larr; U+02190
larrb; U+021E4
larrbfs; U+0291F
larrfs; U+0291D
larrhk; U+021A9
larrlp; U+021AB
larrpl; U+02939
larrsim; U+02973
larrtl; U+021A2
lat; U+02AAB
lAtail; U+0291B
latail; U+02919
late; U+02AAD
lates; U+02AAD U+0FE00
lBarr; U+0290E
lbarr; U+0290C
lbbrk; U+02772
lbrace; U+0007B
lbrack; U+0005B
lbrke; U+0298B
lbrksld; U+0298F
lbrkslu; U+0298D
Lcaron; U+0013D
lcaron; U+0013E
Lcedil; U+0013B
lcedil; U+0013C
lceil; U+02308
lcub; U+0007B
Lcy; U+0041B
lcy; U+0043B
ldca; U+02936
ldquo; U+0201C
ldquor; U+0201E
ldrdhar; U+02967
ldrushar; U+0294B
ldsh; U+021B2
lE; U+02266
le; U+02264
LeftAngleBracket; U+027E8
LeftArrow; U+02190
Leftarrow; U+021D0
leftarrow; U+02190
LeftArrowBar; U+021E4
LeftArrowRightArrow; U+021C6
leftarrowtail; U+021A2
LeftCeiling; U+02308
LeftDoubleBracket; U+027E6
LeftDownTeeVector; U+02961
LeftDownVector; U+021C3
LeftDownVectorBar; U+02959
LeftFloor; U+0230A
leftharpoondown; U+021BD
leftharpoonup; U+021BC
leftleftarrows; U+021C7
LeftRightArrow; U+02194
Leftrightarrow; U+021D4
leftrightarrow; U+02194
leftrightarrows; U+021C6
leftrightharpoons; U+021CB
leftrightsquigarrow; U+021AD
LeftRightVector; U+0294E
LeftTee; U+022A3
LeftTeeArrow; U+021A4
LeftTeeVector; U+0295A
leftthreetimes; U+022CB
LeftTriangle; U+022B2
LeftTriangleBar; U+029CF
LeftTriangleEqual; U+022B4
LeftUpDownVector; U+02951
LeftUpTeeVector; U+02960
LeftUpVector; U+021BF
LeftUpVectorBar; U+02958
LeftVector; U+021BC
LeftVectorBar; U+02952
lEg; U+02A8B
leg; U+022DA
leq; U+02264
leqq; U+02266
leqslant; U+02A7D
les; U+02A7D
lescc; U+02AA8
lesdot; U+02A7F
lesdoto; U+02A81
lesdotor; U+02A83
lesg; U+022DA U+0FE00
lesges; U+02A93
lessapprox; U+02A85
lessdot; U+022D6
lesseqgtr; U+022DA
lesseqqgtr; U+02A8B
LessEqualGreater; U+022DA
LessFullEqual; U+02266
LessGreater; U+02276
lessgtr; U+02276
LessLess; U+02AA1
lesssim; U+02272
LessSlantEqual; U+02A7D
LessTilde; U+02272
lfisht; U+0297C
lfloor; U+0230A
Lfr; U+1D50F
lfr; U+1D529
lg; U+02276
lgE; U+02A91
lHar; U+02962
lhard; U+021BD
lharu; U+021BC
lharul; U+0296A
lhblk; U+02584
LJcy; U+00409
ljcy; U+00459
Ll; U+022D8
ll; U+0226A
llarr; U+021C7
llcorner; U+0231E
Lleftarrow; U+021DA
llhard; U+0296B
lltri; U+025FA
Lmidot; U+0013F
lmidot; U+00140
lmoust; U+023B0
lmoustache; U+023B0
lnap; U+02A89
lnapprox; U+02A89
lnE; U+02268
lne; U+02A87
lneq; U+02A87
lneqq; U+02268
lnsim; U+022E6
loang; U+027EC
loarr; U+021FD
lobrk; U+027E6
LongLeftArrow; U+027F5
Longleftarrow; U+027F8
longleftarrow; U+027F5
LongLeftRightArrow; U+027F7
Longleftrightarrow; U+027FA
longleftrightarrow; U+027F7
longmapsto; U+027FC
LongRightArrow; U+027F6
Longrightarrow; U+027F9
longrightarrow; U+027F6
looparrowleft; U+021AB
looparrowright; U+021AC
lopar; U+02985
Lopf; U+1D543
lopf; U+1D55D
loplus; U+02A2D
lotimes; U+02A34
lowast; U+02217
lowbar; U+0005F
LowerLeftArrow; U+02199
LowerRightArrow; U+02198
loz; U+025CA
lozenge; U+025CA
lozf; U+029EB
lpar; U+00028
lparlt; U+02993
lrarr; U+021C6
lrcorner; U+0231F
lrhar; U+021CB
lrhard; U+0296D
lrm; U+0200E
lrtri; U+022BF
lsaquo; U+02039
Lscr; U+02112
lscr; U+1D4C1
Lsh; U+021B0
lsh; U+021B0
lsim; U+02272
lsime; U+02A8D
lsimg; U+02A8F
lsqb; U+0005B
lsquo; U+02018
lsquor; U+0201A
Lstrok; U+00141
lstrok; U+00142
LT; U+0003C
LT U+0003C
Lt; U+0226A
lt; U+0003C
lt U+0003C
ltcc; U+02AA6
ltcir; U+02A79
ltdot; U+022D6
lthree; U+022CB
ltimes; U+022C9
ltlarr; U+02976
ltquest; U+02A7B
ltri; U+025C3
ltrie; U+022B4
ltrif; U+025C2
ltrPar; U+02996
lurdshar; U+0294A
luruhar; U+02966
lvertneqq; U+02268 U+0FE00
lvnE; U+02268 U+0FE00
macr; U+000AF
macr U+000AF
male; U+02642
malt; U+02720
maltese; U+02720
Map; U+02905
map; U+021A6
mapsto; U+021A6
mapstodown; U+021A7
mapstoleft; U+021A4
mapstoup; U+021A5
marker; U+025AE
mcomma; U+02A29
Mcy; U+0041C
mcy; U+0043C
mdash; U+02014
mDDot; U+0223A
measuredangle; U+02221
MediumSpace; U+0205F
Mellintrf; U+02133
Mfr; U+1D510
mfr; U+1D52A
mho; U+02127
micro; U+000B5
micro U+000B5
mid; U+02223
midast; U+0002A
midcir; U+02AF0
middot; U+000B7
middot U+000B7
minus; U+02212
minusb; U+0229F
minusd; U+02238
minusdu; U+02A2A
MinusPlus; U+02213
mlcp; U+02ADB
mldr; U+02026
mnplus; U+02213
models; U+022A7
Mopf; U+1D544
mopf; U+1D55E
mp; U+02213
Mscr; U+02133
mscr; U+1D4C2
mstpos; U+0223E
Mu; U+0039C
mu; U+003BC
multimap; U+022B8
mumap; U+022B8
nabla; U+02207
Nacute; U+00143
nacute; U+00144
nang; U+02220 U+020D2
nap; U+02249
napE; U+02A70 U+00338
napid; U+0224B U+00338
napos; U+00149
napprox; U+02249
natur; U+0266E
natural; U+0266E
naturals; U+02115
nbsp; U+000A0
nbsp U+000A0
nbump; U+0224E U+00338
nbumpe; U+0224F U+00338
ncap; U+02A43
Ncaron; U+00147
ncaron; U+00148
Ncedil; U+00145
ncedil; U+00146
ncong; U+02247
ncongdot; U+02A6D U+00338
ncup; U+02A42
Ncy; U+0041D
ncy; U+0043D
ndash; U+02013
ne; U+02260
nearhk; U+02924
neArr; U+021D7
nearr; U+02197
nearrow; U+02197
nedot; U+02250 U+00338
NegativeMediumSpace; U+0200B
NegativeThickSpace; U+0200B
NegativeThinSpace; U+0200B
NegativeVeryThinSpace; U+0200B
nequiv; U+02262
nesear; U+02928
nesim; U+02242 U+00338
NestedGreaterGreater; U+0226B
NestedLessLess; U+0226A
NewLine; U+0000A
nexist; U+02204
nexists; U+02204
Nfr; U+1D511
nfr; U+1D52B
ngE; U+02267 U+00338
nge; U+02271
ngeq; U+02271
ngeqq; U+02267 U+00338
ngeqslant; U+02A7E U+00338
nges; U+02A7E U+00338
nGg; U+022D9 U+00338
ngsim; U+02275
nGt; U+0226B U+020D2
ngt; U+0226F
ngtr; U+0226F
nGtv; U+0226B U+00338
nhArr; U+021CE
nharr; U+021AE
nhpar; U+02AF2
ni; U+0220B
nis; U+022FC
nisd; U+022FA
niv; U+0220B
NJcy; U+0040A
njcy; U+0045A
nlArr; U+021CD
nlarr; U+0219A
nldr; U+02025
nlE; U+02266 U+00338
nle; U+02270
nLeftarrow; U+021CD
nleftarrow; U+0219A
nLeftrightarrow; U+021CE
nleftrightarrow; U+021AE
nleq; U+02270
nleqq; U+02266 U+00338
nleqslant; U+02A7D U+00338
nles; U+02A7D U+00338
nless; U+0226E
nLl; U+022D8 U+00338
nlsim; U+02274
nLt; U+0226A U+020D2
nlt; U+0226E
nltri; U+022EA
nltrie; U+022EC
nLtv; U+0226A U+00338
nmid; U+02224
NoBreak; U+02060
NonBreakingSpace; U+000A0
Nopf; U+02115
nopf; U+1D55F
Not; U+02AEC
not; U+000AC
not U+000AC
NotCongruent; U+02262
NotCupCap; U+0226D
NotDoubleVerticalBar; U+02226
NotElement; U+02209
NotEqual; U+02260
NotEqualTilde; U+02242 U+00338
NotExists; U+02204
NotGreater; U+0226F
NotGreaterEqual; U+02271
NotGreaterFullEqual; U+02267 U+00338
NotGreaterGreater; U+0226B U+00338
NotGreaterLess; U+02279
NotGreaterSlantEqual; U+02A7E U+00338
NotGreaterTilde; U+02275
NotHumpDownHump; U+0224E U+00338
NotHumpEqual; U+0224F U+00338
notin; U+02209
notindot; U+022F5 U+00338
notinE; U+022F9 U+00338
notinva; U+02209
notinvb; U+022F7
notinvc; U+022F6
NotLeftTriangle; U+022EA
NotLeftTriangleBar; U+029CF U+00338
NotLeftTriangleEqual; U+022EC
NotLess; U+0226E
NotLessEqual; U+02270
NotLessGreater; U+02278
NotLessLess; U+0226A U+00338
NotLessSlantEqual; U+02A7D U+00338
NotLessTilde; U+02274
NotNestedGreaterGreater; U+02AA2 U+00338
NotNestedLessLess; U+02AA1 U+00338
notni; U+0220C
notniva; U+0220C
notnivb; U+022FE
notnivc; U+022FD
NotPrecedes; U+02280
NotPrecedesEqual; U+02AAF U+00338
NotPrecedesSlantEqual; U+022E0
NotReverseElement; U+0220C
NotRightTriangle; U+022EB
NotRightTriangleBar; U+029D0 U+00338
NotRightTriangleEqual; U+022ED
NotSquareSubset; U+0228F U+00338
NotSquareSubsetEqual; U+022E2
NotSquareSuperset; U+02290 U+00338
NotSquareSupersetEqual; U+022E3
NotSubset; U+02282 U+020D2
NotSubsetEqual; U+02288
NotSucceeds; U+02281
NotSucceedsEqual; U+02AB0 U+00338
NotSucceedsSlantEqual; U+022E1
NotSucceedsTilde; U+0227F U+00338
NotSuperset; U+02283 U+020D2
NotSupersetEqual; U+02289
NotTilde; U+02241
NotTildeEqual; U+02244
NotTildeFullEqual; U+02247
NotTildeTilde; U+02249
NotVerticalBar; U+02224
npar; U+02226
nparallel; U+02226
nparsl; U+02AFD U+020E5
npart; U+02202 U+00338
npolint; U+02A14
npr; U+02280
nprcue; U+022E0
npre; U+02AAF U+00338
nprec; U+02280
npreceq; U+02AAF U+00338
nrArr; U+021CF
nrarr; U+0219B
nrarrc; U+02933 U+00338
nrarrw; U+0219D U+00338
nRightarrow; U+021CF
nrightarrow; U+0219B
nrtri; U+022EB
nrtrie; U+022ED
nsc; U+02281
nsccue; U+022E1
nsce; U+02AB0 U+00338
Nscr; U+1D4A9
nscr; U+1D4C3
nshortmid; U+02224
nshortparallel; U+02226
nsim; U+02241
nsime; U+02244
nsimeq; U+02244
nsmid; U+02224
nspar; U+02226
nsqsube; U+022E2
nsqsupe; U+022E3
nsub; U+02284
nsubE; U+02AC5 U+00338
nsube; U+02288
nsubset; U+02282 U+020D2
nsubseteq; U+02288
nsubseteqq; U+02AC5 U+00338
nsucc; U+02281
nsucceq; U+02AB0 U+00338
nsup; U+02285
nsupE; U+02AC6 U+00338
nsupe; U+02289
nsupset; U+02283 U+020D2
nsupseteq; U+02289
nsupseteqq; U+02AC6 U+00338
ntgl; U+02279
Ntilde; U+000D1
Ntilde U+000D1
ntilde; U+000F1
ntilde U+000F1
ntlg; U+02278
ntriangleleft; U+022EA
ntrianglelefteq; U+022EC
ntriangleright; U+022EB
ntrianglerighteq; U+022ED
Nu; U+0039D
nu; U+003BD
num; U+00023
numero; U+02116
numsp; U+02007
nvap; U+0224D U+020D2
nVDash; U+022AF
nVdash; U+022AE
nvDash; U+022AD
nvdash; U+022AC
nvge; U+02265 U+020D2
nvgt; U+0003E U+020D2
nvHarr; U+02904
nvinfin; U+029DE
nvlArr; U+02902
nvle; U+02264 U+020D2
nvlt; U+0003C U+020D2
nvltrie; U+022B4 U+020D2
nvrArr; U+02903
nvrtrie; U+022B5 U+020D2
nvsim; U+0223C U+020D2
nwarhk; U+02923
nwArr; U+021D6
nwarr; U+02196
nwarrow; U+02196
nwnear; U+02927
Oacute; U+000D3
Oacute U+000D3
oacute; U+000F3
oacute U+000F3
oast; U+0229B
ocir; U+0229A
Ocirc; U+000D4
Ocirc U+000D4
ocirc; U+000F4
ocirc U+000F4
Ocy; U+0041E
ocy; U+0043E
odash; U+0229D
Odblac; U+00150
odblac; U+00151
odiv; U+02A38
odot; U+02299
odsold; U+029BC
OElig; U+00152
oelig; U+00153
ofcir; U+029BF
Ofr; U+1D512
ofr; U+1D52C
ogon; U+002DB
Ograve; U+000D2
Ograve U+000D2
ograve; U+000F2
ograve U+000F2
ogt; U+029C1
ohbar; U+029B5
ohm; U+003A9
oint; U+0222E
olarr; U+021BA
olcir; U+029BE
olcross; U+029BB
oline; U+0203E
olt; U+029C0
Omacr; U+0014C
omacr; U+0014D
Omega; U+003A9
omega; U+003C9
Omicron; U+0039F
omicron; U+003BF
omid; U+029B6
ominus; U+02296
Oopf; U+1D546
oopf; U+1D560
opar; U+029B7
OpenCurlyDoubleQuote; U+0201C
OpenCurlyQuote; U+02018
operp; U+029B9
oplus; U+02295
Or; U+02A54
or; U+02228
orarr; U+021BB
ord; U+02A5D
order; U+02134
orderof; U+02134
ordf; U+000AA
ordf U+000AA
ordm; U+000BA
ordm U+000BA
origof; U+022B6
oror; U+02A56
orslope; U+02A57
orv; U+02A5B
oS; U+024C8
Oscr; U+1D4AA
oscr; U+02134
Oslash; U+000D8
Oslash U+000D8
oslash; U+000F8
oslash U+000F8
osol; U+02298
Otilde; U+000D5
Otilde U+000D5
otilde; U+000F5
otilde U+000F5
Otimes; U+02A37
otimes; U+02297
otimesas; U+02A36
Ouml; U+000D6
Ouml U+000D6
ouml; U+000F6
ouml U+000F6
ovbar; U+0233D
OverBar; U+0203E
OverBrace; U+023DE
OverBracket; U+023B4
OverParenthesis; U+023DC
par; U+02225
para; U+000B6
para U+000B6
parallel; U+02225
parsim; U+02AF3
parsl; U+02AFD
part; U+02202
PartialD; U+02202
Pcy; U+0041F
pcy; U+0043F
percnt; U+00025
period; U+0002E
permil; U+02030
perp; U+022A5
pertenk; U+02031
Pfr; U+1D513
pfr; U+1D52D
Phi; U+003A6
phi; U+003C6
phiv; U+003D5
phmmat; U+02133
phone; U+0260E
Pi; U+003A0
pi; U+003C0
pitchfork; U+022D4
piv; U+003D6
planck; U+0210F
planckh; U+0210E
plankv; U+0210F
plus; U+0002B
plusacir; U+02A23
plusb; U+0229E
pluscir; U+02A22
plusdo; U+02214
plusdu; U+02A25
pluse; U+02A72
PlusMinus; U+000B1
plusmn; U+000B1
plusmn U+000B1
plussim; U+02A26
plustwo; U+02A27
pm; U+000B1
Poincareplane; U+0210C
pointint; U+02A15
Popf; U+02119
popf; U+1D561
pound; U+000A3
pound U+000A3
Pr; U+02ABB
pr; U+0227A
prap; U+02AB7
prcue; U+0227C
prE; U+02AB3
pre; U+02AAF
prec; U+0227A
precapprox; U+02AB7
preccurlyeq; U+0227C
Precedes; U+0227A
PrecedesEqual; U+02AAF
PrecedesSlantEqual; U+0227C
PrecedesTilde; U+0227E
preceq; U+02AAF
precnapprox; U+02AB9
precneqq; U+02AB5
precnsim; U+022E8
precsim; U+0227E
Prime; U+02033
prime; U+02032
primes; U+02119
prnap; U+02AB9
prnE; U+02AB5
prnsim; U+022E8
prod; U+0220F
Product; U+0220F
profalar; U+0232E
profline; U+02312
profsurf; U+02313
prop; U+0221D
Proportion; U+02237
Proportional; U+0221D
propto; U+0221D
prsim; U+0227E
prurel; U+022B0
Pscr; U+1D4AB
pscr; U+1D4C5
Psi; U+003A8
psi; U+003C8
puncsp; U+02008
Qfr; U+1D514
qfr; U+1D52E
qint; U+02A0C
Qopf; U+0211A
qopf; U+1D562
qprime; U+02057
Qscr; U+1D4AC
qscr; U+1D4C6
quaternions; U+0210D
quatint; U+02A16
quest; U+0003F
questeq; U+0225F
QUOT; U+00022
QUOT U+00022
quot; U+00022
quot U+00022
rAarr; U+021DB
race; U+0223D U+00331
Racute; U+00154
racute; U+00155
radic; U+0221A
raemptyv; U+029B3
Rang; U+027EB
rang; U+027E9
rangd; U+02992
range; U+029A5
rangle; U+027E9
raquo; U+000BB
raquo U+000BB
Rarr; U+021A0
rArr; U+021D2
rarr; U+02192
rarrap; U+02975
rarrb; U+021E5
rarrbfs; U+02920
rarrc; U+02933
rarrfs; U+0291E
rarrhk; U+021AA
rarrlp; U+021AC
rarrpl; U+02945
rarrsim; U+02974
Rarrtl; U+02916
rarrtl; U+021A3
rarrw; U+0219D
rAtail; U+0291C
ratail; U+0291A
ratio; U+02236
rationals; U+0211A
RBarr; U+02910
rBarr; U+0290F
rbarr; U+0290D
rbbrk; U+02773
rbrace; U+0007D
rbrack; U+0005D
rbrke; U+0298C
rbrksld; U+0298E
rbrkslu; U+02990
Rcaron; U+00158
rcaron; U+00159
Rcedil; U+00156
rcedil; U+00157
rceil; U+02309
rcub; U+0007D
Rcy; U+00420
rcy; U+00440
rdca; U+02937
rdldhar; U+02969
rdquo; U+0201D
rdquor; U+0201D
rdsh; U+021B3
Re; U+0211C
real; U+0211C
realine; U+0211B
realpart; U+0211C
reals; U+0211D
rect; U+025AD
REG; U+000AE
REG U+000AE
reg; U+000AE
reg U+000AE
ReverseElement; U+0220B
ReverseEquilibrium; U+021CB
ReverseUpEquilibrium; U+0296F
rfisht; U+0297D
rfloor; U+0230B
Rfr; U+0211C
rfr; U+1D52F
rHar; U+02964
rhard; U+021C1
rharu; U+021C0
rharul; U+0296C
Rho; U+003A1
rho; U+003C1
rhov; U+003F1
RightAngleBracket; U+027E9
RightArrow; U+02192
Rightarrow; U+021D2
rightarrow; U+02192
RightArrowBar; U+021E5
RightArrowLeftArrow; U+021C4
rightarrowtail; U+021A3
RightCeiling; U+02309
RightDoubleBracket; U+027E7
RightDownTeeVector; U+0295D
RightDownVector; U+021C2
RightDownVectorBar; U+02955
RightFloor; U+0230B
rightharpoondown; U+021C1
rightharpoonup; U+021C0
rightleftarrows; U+021C4
rightleftharpoons; U+021CC
rightrightarrows; U+021C9
rightsquigarrow; U+0219D
RightTee; U+022A2
RightTeeArrow; U+021A6
RightTeeVector; U+0295B
rightthreetimes; U+022CC
RightTriangle; U+022B3
RightTriangleBar; U+029D0
RightTriangleEqual; U+022B5
RightUpDownVector; U+0294F
RightUpTeeVector; U+0295C
RightUpVector; U+021BE
RightUpVectorBar; U+02954
RightVector; U+021C0
RightVectorBar; U+02953
ring; U+002DA
risingdotseq; U+02253
rlarr; U+021C4
rlhar; U+021CC
rlm; U+0200F
rmoust; U+023B1
rmoustache; U+023B1
rnmid; U+02AEE
roang; U+027ED
roarr; U+021FE
robrk; U+027E7
ropar; U+02986
Ropf; U+0211D
ropf; U+1D563
roplus; U+02A2E
rotimes; U+02A35
RoundImplies; U+02970
rpar; U+00029
rpargt; U+02994
rppolint; U+02A12
rrarr; U+021C9
Rrightarrow; U+021DB
rsaquo; U+0203A
Rscr; U+0211B
rscr; U+1D4C7
Rsh; U+021B1
rsh; U+021B1
rsqb; U+0005D
rsquo; U+02019
rsquor; U+02019
rthree; U+022CC
rtimes; U+022CA
rtri; U+025B9
rtrie; U+022B5
rtrif; U+025B8
rtriltri; U+029CE
RuleDelayed; U+029F4
ruluhar; U+02968
rx; U+0211E
Sacute; U+0015A
sacute; U+0015B
sbquo; U+0201A
Sc; U+02ABC
sc; U+0227B
scap; U+02AB8
Scaron; U+00160
scaron; U+00161
sccue; U+0227D
scE; U+02AB4
sce; U+02AB0
Scedil; U+0015E
scedil; U+0015F
Scirc; U+0015C
scirc; U+0015D
scnap; U+02ABA
scnE; U+02AB6
scnsim; U+022E9
scpolint; U+02A13
scsim; U+0227F
Scy; U+00421
scy; U+00441
sdot; U+022C5
sdotb; U+022A1
sdote; U+02A66
searhk; U+02925
seArr; U+021D8
searr; U+02198
searrow; U+02198
sect; U+000A7
sect U+000A7
semi; U+0003B
seswar; U+02929
setminus; U+02216
setmn; U+02216
sext; U+02736
Sfr; U+1D516
sfr; U+1D530
sfrown; U+02322
sharp; U+0266F
SHCHcy; U+00429
shchcy; U+00449
SHcy; U+00428
shcy; U+00448
ShortDownArrow; U+02193
ShortLeftArrow; U+02190
shortmid; U+02223
shortparallel; U+02225
ShortRightArrow; U+02192
ShortUpArrow; U+02191
shy; U+000AD
shy U+000AD
Sigma; U+003A3
sigma; U+003C3
sigmaf; U+003C2
sigmav; U+003C2
sim; U+0223C
simdot; U+02A6A
sime; U+02243
simeq; U+02243
simg; U+02A9E
simgE; U+02AA0
siml; U+02A9D
simlE; U+02A9F
simne; U+02246
simplus; U+02A24
simrarr; U+02972
slarr; U+02190
SmallCircle; U+02218
smallsetminus; U+02216
smashp; U+02A33
smeparsl; U+029E4
smid; U+02223
smile; U+02323
smt; U+02AAA
smte; U+02AAC
smtes; U+02AAC U+0FE00
SOFTcy; U+0042C
softcy; U+0044C
sol; U+0002F
solb; U+029C4
solbar; U+0233F
Sopf; U+1D54A
sopf; U+1D564
spades; U+02660
spadesuit; U+02660
spar; U+02225
sqcap; U+02293
sqcaps; U+02293 U+0FE00
sqcup; U+02294
sqcups; U+02294 U+0FE00
Sqrt; U+0221A
sqsub; U+0228F
sqsube; U+02291
sqsubset; U+0228F
sqsubseteq; U+02291
sqsup; U+02290
sqsupe; U+02292
sqsupset; U+02290
sqsupseteq; U+02292
squ; U+025A1
Square; U+025A1
square; U+025A1
SquareIntersection; U+02293
SquareSubset; U+0228F
SquareSubsetEqual; U+02291
SquareSuperset; U+02290
SquareSupersetEqual; U+02292
SquareUnion; U+02294
squarf; U+025AA
squf; U+025AA
srarr; U+02192
Sscr; U+1D4AE
sscr; U+1D4C8
ssetmn; U+02216
ssmile; U+02323
sstarf; U+022C6
Star; U+022C6
star; U+02606
starf; U+02605
straightepsilon; U+003F5
straightphi; U+003D5
strns; U+000AF
Sub; U+022D0
sub; U+02282
subdot; U+02ABD
subE; U+02AC5
sube; U+02286
subedot; U+02AC3
submult; U+02AC1
subnE; U+02ACB
subne; U+0228A
subplus; U+02ABF
subrarr; U+02979
Subset; U+022D0
subset; U+02282
subseteq; U+02286
subseteqq; U+02AC5
SubsetEqual; U+02286
subsetneq; U+0228A
subsetneqq; U+02ACB
subsim; U+02AC7
subsub; U+02AD5
subsup; U+02AD3
succ; U+0227B
succapprox; U+02AB8
succcurlyeq; U+0227D
Succeeds; U+0227B
SucceedsEqual; U+02AB0
SucceedsSlantEqual; U+0227D
SucceedsTilde; U+0227F
succeq; U+02AB0
succnapprox; U+02ABA
succneqq; U+02AB6
succnsim; U+022E9
succsim; U+0227F
SuchThat; U+0220B
Sum; U+02211
sum; U+02211
sung; U+0266A
Sup; U+022D1
sup; U+02283
sup1; U+000B9
sup1 U+000B9
sup2; U+000B2
sup2 U+000B2
sup3; U+000B3
sup3 U+000B3
supdot; U+02ABE
supdsub; U+02AD8
supE; U+02AC6
supe; U+02287
supedot; U+02AC4
Superset; U+02283
SupersetEqual; U+02287
suphsol; U+027C9
suphsub; U+02AD7
suplarr; U+0297B
supmult; U+02AC2
supnE; U+02ACC
supne; U+0228B
supplus; U+02AC0
Supset; U+022D1
supset; U+02283
supseteq; U+02287
supseteqq; U+02AC6
supsetneq; U+0228B
supsetneqq; U+02ACC
supsim; U+02AC8
supsub; U+02AD4
supsup; U+02AD6
swarhk; U+02926
swArr; U+021D9
swarr; U+02199
swarrow; U+02199
swnwar; U+0292A
szlig; U+000DF
szlig U+000DF
Tab; U+00009
target; U+02316
Tau; U+003A4
tau; U+003C4
tbrk; U+023B4
Tcaron; U+00164
tcaron; U+00165
Tcedil; U+00162
tcedil; U+00163
Tcy; U+00422
tcy; U+00442
tdot; U+020DB
telrec; U+02315
Tfr; U+1D517
tfr; U+1D531
there4; U+02234
Therefore; U+02234
therefore; U+02234
Theta; U+00398
theta; U+003B8
thetasym; U+003D1
thetav; U+003D1
thickapprox; U+02248
thicksim; U+0223C
ThickSpace; U+0205F U+0200A
thinsp; U+02009
ThinSpace; U+02009
thkap; U+02248
thksim; U+0223C
THORN; U+000DE
THORN U+000DE
thorn; U+000FE
thorn U+000FE
Tilde; U+0223C
tilde; U+002DC
TildeEqual; U+02243
TildeFullEqual; U+02245
TildeTilde; U+02248
times; U+000D7
times U+000D7
timesb; U+022A0
timesbar; U+02A31
timesd; U+02A30
tint; U+0222D
toea; U+02928
top; U+022A4
topbot; U+02336
topcir; U+02AF1
Topf; U+1D54B
topf; U+1D565
topfork; U+02ADA
tosa; U+02929
tprime; U+02034
TRADE; U+02122
trade; U+02122
triangle; U+025B5
triangledown; U+025BF
triangleleft; U+025C3
trianglelefteq; U+022B4
triangleq; U+0225C
triangleright; U+025B9
trianglerighteq; U+022B5
tridot; U+025EC
trie; U+0225C
triminus; U+02A3A
TripleDot; U+020DB
triplus; U+02A39
trisb; U+029CD
tritime; U+02A3B
trpezium; U+023E2
Tscr; U+1D4AF
tscr; U+1D4C9
TScy; U+00426
tscy; U+00446
TSHcy; U+0040B
tshcy; U+0045B
Tstrok; U+00166
tstrok; U+00167
twixt; U+0226C
twoheadleftarrow; U+0219E
twoheadrightarrow; U+021A0
Uacute; U+000DA
Uacute U+000DA
uacute; U+000FA
uacute U+000FA
Uarr; U+0219F
uArr; U+021D1
uarr; U+02191
Uarrocir; U+02949
Ubrcy; U+0040E
ubrcy; U+0045E
Ubreve; U+0016C
ubreve; U+0016D
Ucirc; U+000DB
Ucirc U+000DB
ucirc; U+000FB
ucirc U+000FB
Ucy; U+00423
ucy; U+00443
udarr; U+021C5
Udblac; U+00170
udblac; U+00171
udhar; U+0296E
ufisht; U+0297E
Ufr; U+1D518
ufr; U+1D532
Ugrave; U+000D9
Ugrave U+000D9
ugrave; U+000F9
ugrave U+000F9
uHar; U+02963
uharl; U+021BF
uharr; U+021BE
uhblk; U+02580
ulcorn; U+0231C
ulcorner; U+0231C
ulcrop; U+0230F
ultri; U+025F8
Umacr; U+0016A
umacr; U+0016B
uml; U+000A8
uml U+000A8
UnderBar; U+0005F
UnderBrace; U+023DF
UnderBracket; U+023B5
UnderParenthesis; U+023DD
Union; U+022C3
UnionPlus; U+0228E
Uogon; U+00172
uogon; U+00173
Uopf; U+1D54C
uopf; U+1D566
UpArrow; U+02191
Uparrow; U+021D1
uparrow; U+02191
UpArrowBar; U+02912
UpArrowDownArrow; U+021C5
UpDownArrow; U+02195
Updownarrow; U+021D5
updownarrow; U+02195
UpEquilibrium; U+0296E
upharpoonleft; U+021BF
upharpoonright; U+021BE
uplus; U+0228E
UpperLeftArrow; U+02196
UpperRightArrow; U+02197
Upsi; U+003D2
upsi; U+003C5
upsih; U+003D2
Upsilon; U+003A5
upsilon; U+003C5
UpTee; U+022A5
UpTeeArrow; U+021A5
upuparrows; U+021C8
urcorn; U+0231D
urcorner; U+0231D
urcrop; U+0230E
Uring; U+0016E
uring; U+0016F
urtri; U+025F9
Uscr; U+1D4B0
uscr; U+1D4CA
utdot; U+022F0
Utilde; U+00168
utilde; U+00169
utri; U+025B5
utrif; U+025B4
uuarr; U+021C8
Uuml; U+000DC
Uuml U+000DC
uuml; U+000FC
uuml U+000FC
uwangle; U+029A7
vangrt; U+0299C
varepsilon; U+003F5
varkappa; U+003F0
varnothing; U+02205
varphi; U+003D5
varpi; U+003D6
varpropto; U+0221D
vArr; U+021D5
varr; U+02195
varrho; U+003F1
varsigma; U+003C2
varsubsetneq; U+0228A U+0FE00
varsubsetneqq; U+02ACB U+0FE00
varsupsetneq; U+0228B U+0FE00
varsupsetneqq; U+02ACC U+0FE00
vartheta; U+003D1
vartriangleleft; U+022B2
vartriangleright; U+022B3
Vbar; U+02AEB
vBar; U+02AE8
vBarv; U+02AE9
Vcy; U+00412
vcy; U+00432
VDash; U+022AB
Vdash; U+022A9
vDash; U+022A8
vdash; U+022A2
Vdashl; U+02AE6
Vee; U+022C1
vee; U+02228
veebar; U+022BB
veeeq; U+0225A
vellip; U+022EE
Verbar; U+02016
verbar; U+0007C
Vert; U+02016
vert; U+0007C
VerticalBar; U+02223
VerticalLine; U+0007C
VerticalSeparator; U+02758
VerticalTilde; U+02240
VeryThinSpace; U+0200A
Vfr; U+1D519
vfr; U+1D533
vltri; U+022B2
vnsub; U+02282 U+020D2
vnsup; U+02283 U+020D2
Vopf; U+1D54D
vopf; U+1D567
vprop; U+0221D
vrtri; U+022B3
Vscr; U+1D4B1
vscr; U+1D4CB
vsubnE; U+02ACB U+0FE00
vsubne; U+0228A U+0FE00
vsupnE; U+02ACC U+0FE00
vsupne; U+0228B U+0FE00
Vvdash; U+022AA
vzigzag; U+0299A
Wcirc; U+00174
wcirc; U+00175
wedbar; U+02A5F
Wedge; U+022C0
wedge; U+02227
wedgeq; U+02259
weierp; U+02118
Wfr; U+1D51A
wfr; U+1D534
Wopf; U+1D54E
wopf; U+1D568
wp; U+02118
wr; U+02240
wreath; U+02240
Wscr; U+1D4B2
wscr; U+1D4CC
xcap; U+022C2
xcirc; U+025EF
xcup; U+022C3
xdtri; U+025BD
Xfr; U+1D51B
xfr; U+1D535
xhArr; U+027FA
xharr; U+027F7
Xi; U+0039E
xi; U+003BE
xlArr; U+027F8
xlarr; U+027F5
xmap; U+027FC
xnis; U+022FB
xodot; U+02A00
Xopf; U+1D54F
xopf; U+1D569
xoplus; U+02A01
xotime; U+02A02
xrArr; U+027F9
xrarr; U+027F6
Xscr; U+1D4B3
xscr; U+1D4CD
xsqcup; U+02A06
xuplus; U+02A04
xutri; U+025B3
xvee; U+022C1
xwedge; U+022C0
Yacute; U+000DD
Yacute U+000DD
yacute; U+000FD
yacute U+000FD
YAcy; U+0042F
yacy; U+0044F
Ycirc; U+00176
ycirc; U+00177
Ycy; U+0042B
ycy; U+0044B
yen; U+000A5
yen U+000A5
Yfr; U+1D51C
yfr; U+1D536
YIcy; U+00407
yicy; U+00457
Yopf; U+1D550
yopf; U+1D56A
Yscr; U+1D4B4
yscr; U+1D4CE
YUcy; U+0042E
yucy; U+0044E
Yuml; U+00178
yuml; U+000FF
yuml U+000FF
Zacute; U+00179
zacute; U+0017A
Zcaron; U+0017D
zcaron; U+0017E
Zcy; U+00417
zcy; U+00437
Zdot; U+0017B
zdot; U+0017C
zeetrf; U+02128
ZeroWidthSpace; U+0200B
Zeta; U+00396
zeta; U+003B6
Zfr; U+02128
zfr; U+1D537
ZHcy; U+00416
zhcy; U+00436
zigrarr; U+021DD
Zopf; U+02124
zopf; U+1D56B
Zscr; U+1D4B5
zscr; U+1D4CF
zwj; U+0200D
zwnj; U+0200C
__END__
=encoding utf8
=head1 NAME
Mojo::Util - Portable utility functions
=head1 SYNOPSIS
use Mojo::Util qw(b64_encode url_escape url_unescape);
my $str = 'test=23';
my $escaped = url_escape $str;
say url_unescape $escaped;
say b64_encode $escaped, '';
=head1 DESCRIPTION
L<Mojo::Util> provides portable utility functions for L<Mojo>.
=head1 FUNCTIONS
L<Mojo::Util> implements the following functions, which can be imported
individually.
=head2 b64_decode
my $bytes = b64_decode $b64;
Base64 decode bytes.
=head2 b64_encode
my $b64 = b64_encode $bytes;
my $b64 = b64_encode $bytes, "\n";
Base64 encode bytes, the line ending defaults to a newline.
=head2 camelize
my $camelcase = camelize $snakecase;
Convert snake_case string to CamelCase and replace C<-> with C<::>.
# "FooBar"
camelize 'foo_bar';
# "FooBar::Baz"
camelize 'foo_bar-baz';
# "FooBar::Baz"
camelize 'FooBar::Baz';
=head2 class_to_file
my $file = class_to_file 'Foo::Bar';
Convert a class name to a file.
# "foo_bar"
class_to_file 'Foo::Bar';
# "foobar"
class_to_file 'FOO::Bar';
# "foo_bar"
class_to_file 'FooBar';
# "foobar"
class_to_file 'FOOBar';
=head2 class_to_path
my $path = class_to_path 'Foo::Bar';
Convert class name to path.
# "Foo/Bar.pm"
class_to_path 'Foo::Bar';
# "FooBar.pm"
class_to_path 'FooBar';
=head2 decamelize
my $snakecase = decamelize $camelcase;
Convert CamelCase string to snake_case and replace C<::> with C<->.
# "foo_bar"
decamelize 'FooBar';
# "foo_bar-baz"
decamelize 'FooBar::Baz';
# "foo_bar-baz"
decamelize 'foo_bar-baz';
=head2 decode
my $chars = decode 'UTF-8', $bytes;
Decode bytes to characters and return C<undef> if decoding failed.
=head2 deprecated
deprecated 'foo is DEPRECATED in favor of bar';
Warn about deprecated feature from perspective of caller. You can also set the
C<MOJO_FATAL_DEPRECATIONS> environment variable to make them die instead.
=head2 dumper
my $perl = dumper {some => 'data'};
Dump a Perl data structure with L<Data::Dumper>.
=head2 encode
my $bytes = encode 'UTF-8', $chars;
Encode characters to bytes.
=head2 hmac_sha1_sum
my $checksum = hmac_sha1_sum $bytes, 'passw0rd';
Generate HMAC-SHA1 checksum for bytes.
=head2 html_unescape
my $str = html_unescape $escaped;
Unescape all HTML entities in string.
=head2 md5_bytes
my $checksum = md5_bytes $bytes;
Generate binary MD5 checksum for bytes.
=head2 md5_sum
my $checksum = md5_sum $bytes;
Generate MD5 checksum for bytes.
=head2 monkey_patch
monkey_patch $package, foo => sub {...};
monkey_patch $package, foo => sub {...}, bar => sub {...};
Monkey patch functions into package.
monkey_patch 'MyApp',
one => sub { say 'One!' },
two => sub { say 'Two!' },
three => sub { say 'Three!' };
=head2 punycode_decode
my $str = punycode_decode $punycode;
Punycode decode string as described in
L<RFC 3492|http://tools.ietf.org/html/rfc3492>.
=head2 punycode_encode
my $punycode = punycode_encode $str;
Punycode encode string as described in
L<RFC 3492|http://tools.ietf.org/html/rfc3492>.
=head2 quote
my $quoted = quote $str;
Quote string.
=head2 secure_compare
my $bool = secure_compare $str1, $str2;
Constant time comparison algorithm to prevent timing attacks.
=head2 sha1_bytes
my $checksum = sha1_bytes $bytes;
Generate binary SHA1 checksum for bytes.
=head2 sha1_sum
my $checksum = sha1_sum $bytes;
Generate SHA1 checksum for bytes.
=head2 slurp
my $bytes = slurp '/etc/passwd';
Read all data at once from file.
=head2 split_header
my $tree = split_header 'foo="bar baz"; test=123, yada';
Split HTTP header value.
# "one"
split_header('one; two="three four", five=six')->[0][0];
# "three four"
split_header('one; two="three four", five=six')->[0][3];
# "five"
split_header('one; two="three four", five=six')->[1][0];
=head2 spurt
$bytes = spurt $bytes, '/etc/passwd';
Write all data at once to file.
=head2 squish
my $squished = squish $str;
Trim whitespace characters from both ends of string and then change all
consecutive groups of whitespace into one space each.
=head2 steady_time
my $time = steady_time;
High resolution time elapsed from an arbitrary fixed point in the past,
resilient to time jumps if a monotonic clock is available through
L<Time::HiRes>.
=head2 tablify
my $table = tablify [['foo', 'bar'], ['baz', 'yada']];
Row-oriented generator for text tables.
# "foo bar\nyada yada\nbaz yada\n"
tablify [['foo', 'bar'], ['yada', 'yada'], ['baz', 'yada']];
=head2 trim
my $trimmed = trim $str;
Trim whitespace characters from both ends of string.
=head2 unindent
my $unindented = unindent $str;
Unindent multiline string.
=head2 unquote
my $str = unquote $quoted;
Unquote string.
=head2 url_escape
my $escaped = url_escape $str;
my $escaped = url_escape $str, '^A-Za-z0-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\-._~>.
=head2 url_unescape
my $str = url_unescape $escaped;
Decode percent encoded characters in string as described in
L<RFC 3986|http://tools.ietf.org/html/rfc3986>.
=head2 xml_escape
my $escaped = xml_escape $str;
Escape unsafe characters C<&>, C<E<lt>>, C<E<gt>>, C<"> and C<'> in string.
=head2 xor_encode
my $encoded = xor_encode $str, $key;
XOR encode string with variable length key.
=head2 xss_escape
my $escaped = xss_escape $str;
Same as L</"xml_escape">, but does not escape L<Mojo::ByteStream> objects.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJO_UTIL
$fatpacked{"Mojolicious.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS';
package Mojolicious;
use Mojo::Base 'Mojo';
# "Fry: Shut up and take my money!"
use Carp ();
use Mojo::Exception;
use Mojo::Util;
use Mojolicious::Commands;
use Mojolicious::Controller;
use Mojolicious::Plugins;
use Mojolicious::Renderer;
use Mojolicious::Routes;
use Mojolicious::Sessions;
use Mojolicious::Static;
use Mojolicious::Types;
use Mojolicious::Validator;
use Scalar::Util ();
use Time::HiRes ();
has commands => sub {
my $commands = Mojolicious::Commands->new(app => shift);
Scalar::Util::weaken $commands->{app};
return $commands;
};
has controller_class => 'Mojolicious::Controller';
has mode => sub { $ENV{MOJO_MODE} || $ENV{PLACK_ENV} || 'development' };
has moniker => sub { Mojo::Util::decamelize ref shift };
has plugins => sub { Mojolicious::Plugins->new };
has renderer => sub { Mojolicious::Renderer->new };
has routes => sub { Mojolicious::Routes->new };
has secrets => sub {
my $self = shift;
# Warn developers about insecure default
$self->log->debug('Your secret passphrase needs to be changed!!!');
# Default to moniker
return [$self->moniker];
};
has sessions => sub { Mojolicious::Sessions->new };
has static => sub { Mojolicious::Static->new };
has types => sub { Mojolicious::Types->new };
has validator => sub { Mojolicious::Validator->new };
our $CODENAME = 'Tiger Face';
our $VERSION = '5.61';
sub AUTOLOAD {
my $self = shift;
my ($package, $method) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
Carp::croak "Undefined subroutine &${package}::$method called"
unless Scalar::Util::blessed $self && $self->isa(__PACKAGE__);
# Call helper with fresh controller
Carp::croak qq{Can't locate object method "$method" via package "$package"}
unless my $helper = $self->renderer->get_helper($method);
return $self->build_controller->$helper(@_);
}
sub build_controller {
my ($self, $tx) = @_;
$tx ||= $self->build_tx;
# Embedded application
my $stash = {};
if (my $sub = $tx->can('stash')) { ($stash, $tx) = ($tx->$sub, $tx->tx) }
$stash->{'mojo.secrets'} //= $self->secrets;
# Build default controller
my $defaults = $self->defaults;
@$stash{keys %$defaults} = values %$defaults;
my $c
= $self->controller_class->new(app => $self, stash => $stash, tx => $tx);
Scalar::Util::weaken $c->{app};
return $c;
}
sub build_tx {
my $self = shift;
my $tx = Mojo::Transaction::HTTP->new;
$self->plugins->emit_hook(after_build_tx => $tx, $self);
return $tx;
}
sub defaults { Mojo::Util::_stash(defaults => @_) }
sub dispatch {
my ($self, $c) = @_;
# Prepare transaction
my $tx = $c->tx;
$tx->res->code(undef) if $tx->is_websocket;
$self->sessions->load($c);
my $plugins = $self->plugins->emit_hook(before_dispatch => $c);
# Try to find a static file
$self->static->dispatch($c) and $plugins->emit_hook(after_static => $c)
unless $tx->res->code;
# Start timer (ignore static files)
my $stash = $c->stash;
unless ($stash->{'mojo.static'} || $stash->{'mojo.started'}) {
my $req = $c->req;
my $method = $req->method;
my $path = $req->url->path->to_abs_string;
$self->log->debug(qq{$method "$path".});
$stash->{'mojo.started'} = [Time::HiRes::gettimeofday];
}
# Routes
$plugins->emit_hook(before_routes => $c);
my $res = $tx->res;
return if $res->code;
if (my $code = ($tx->req->error // {})->{advice}) { $res->code($code) }
elsif ($tx->is_websocket) { $res->code(426) }
$c->render_not_found unless $self->routes->dispatch($c) || $tx->res->code;
}
sub handler {
my $self = shift;
# Dispatcher has to be last in the chain
++$self->{dispatch}
and $self->hook(around_action => sub { $_[2]->($_[1]) })
and $self->hook(around_dispatch => sub { $_[1]->app->dispatch($_[1]) })
unless $self->{dispatch};
# Process with chain
my $c = $self->build_controller(@_);
Scalar::Util::weaken $c->{tx};
$self->plugins->emit_chain(around_dispatch => $c);
# 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.})
if exists $r->helpers->{$name};
$r->add_helper($name => $cb);
}
sub hook { shift->plugins->on(@_) }
sub new {
my $self = shift->SUPER::new(@_);
my $home = $self->home;
push @{$self->renderer->paths}, $home->rel_dir('templates');
push @{$self->static->paths}, $home->rel_dir('public');
# Default to controller and application namespace
my $r = $self->routes->namespaces(["@{[ref $self]}::Controller", ref $self]);
# Hide controller attributes/methods and "handler"
$r->hide(qw(app continue cookie every_cookie every_param));
$r->hide(qw(every_signed_cookie finish flash handler helpers match on));
$r->hide(qw(param redirect_to render render_exception render_later));
$r->hide(qw(render_maybe render_not_found render_to_string rendered req));
$r->hide(qw(res respond_to send session signed_cookie stash tx url_for));
$r->hide(qw(validation write write_chunk));
# DEPRECATED in Tiger Face!
$r->hide('render_static');
# Check if we have a log directory that is writable
my $mode = $self->mode;
$self->log->path($home->rel_file("log/$mode.log"))
if -d $home->rel_file('log') && -w _;
$self->plugin($_)
for qw(HeaderCondition DefaultHelpers TagHelpers EPLRenderer EPRenderer);
# Exception handling should be first in chain
$self->hook(around_dispatch => \&_exception);
# Reduced log output outside of development mode
$self->log->level('info') unless $mode eq 'development';
$self->startup;
return $self;
}
sub plugin {
my $self = shift;
$self->plugins->register_plugin(shift, $self, @_);
}
sub start {
my $self = shift;
$_->_warmup for $self->static, $self->renderer;
return $self->commands->run(@_ ? @_ : @ARGV);
}
sub startup { }
sub _exception {
my ($next, $c) = @_;
local $SIG{__DIE__}
= sub { ref $_[0] ? CORE::die($_[0]) : Mojo::Exception->throw(@_) };
$c->render_exception($@) unless eval { $next->(); 1 };
}
1;
=encoding utf8
=head1 NAME
Mojolicious - Real-time web framework
=head1 SYNOPSIS
# Application
package MyApp;
use Mojo::Base 'Mojolicious';
# Route
sub startup {
my $self = shift;
$self->routes->get('/hello')->to('foo#hello');
}
# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';
# Action
sub hello {
my $self = shift;
$self->render(text => 'Hello World!');
}
=head1 DESCRIPTION
Take a look at our excellent documentation in L<Mojolicious::Guides>!
=head1 HOOKS
L<Mojolicious> will emit the following hooks in the listed order.
=head2 after_build_tx
Emitted right after the transaction is built and before the HTTP request gets
parsed.
$app->hook(after_build_tx => sub {
my ($tx, $app) = @_;
...
});
This is a very powerful hook and should not be used lightly, it makes some
rather advanced features such as upload progress bars possible. Note that this
hook will not work for embedded applications. (Passed the transaction and
application object)
=head2 before_dispatch
Emitted right before the static file server and router start their work.
$app->hook(before_dispatch => sub {
my $c = shift;
...
});
Very useful for rewriting incoming requests and other preprocessing tasks.
(Passed the default controller object)
=head2 after_static
Emitted after a static file response has been generated by the static file
server.
$app->hook(after_static => sub {
my $c = shift;
...
});
Mostly used for post-processing static file responses. (Passed the default
controller object)
=head2 before_routes
Emitted after the static file server determined if a static file should be
served and before the router starts its work.
$app->hook(before_routes => sub {
my $c = shift;
...
});
Mostly used for custom dispatchers and collecting metrics. (Passed the default
controller object)
=head2 around_action
Emitted right before an action gets invoked and wraps around it, so you have
to manually forward to the next hook if you want to continue the chain.
Default action dispatching is the last hook in the chain, yours will run
before it.
$app->hook(around_action => sub {
my ($next, $c, $action, $last) = @_;
...
return $next->();
});
This is a very powerful hook and should not be used lightly, it allows you for
example to pass additional arguments to actions or handle return values
differently. (Passed a callback leading to the next hook, the current
controller object, the action callback and a flag indicating if this action is
an endpoint)
=head2 before_render
Emitted before content is generated by the renderer. Note that this hook can
trigger out of order due to its dynamic nature, and with embedded applications
will only work for the application that is rendering.
$app->hook(before_render => sub {
my ($c, $args) = @_;
...
});
Mostly used for pre-processing arguments passed to the renderer. (Passed the
current controller object and the render arguments)
=head2 after_render
Emitted after content has been generated by the renderer that will be assigned
to the response. Note that this hook can trigger out of order due to its
dynamic nature, and with embedded applications will only work for the
application that is rendering.
$app->hook(after_render => sub {
my ($c, $output, $format) = @_;
...
});
Mostly used for post-processing dynamically generated content. (Passed the
current controller object, a reference to the content and the format)
=head2 after_dispatch
Emitted in reverse order after a response has been rendered. Note that this
hook can trigger out of order due to its dynamic nature, and with embedded
applications will only work for the application that is rendering.
$app->hook(after_dispatch => sub {
my $c = shift;
...
});
Useful for rewriting outgoing responses and other post-processing tasks.
(Passed the current controller object)
=head2 around_dispatch
Emitted right before the L</"before_dispatch"> hook and wraps around the whole
dispatch process, so you have to manually forward to the next hook if you want
to continue the chain. Default exception handling with
L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>exception"> is the first
hook in the chain and a call to L</"dispatch"> the last, yours will be in
between.
$app->hook(around_dispatch => sub {
my ($next, $c) = @_;
...
$next->();
...
});
This is a very powerful hook and should not be used lightly, it allows you for
example to customize application wide exception handling, consider it the
sledgehammer in your toolbox. (Passed a callback leading to the next hook and
the default controller object)
=head1 ATTRIBUTES
L<Mojolicious> inherits all attributes from L<Mojo> and implements the
following new ones.
=head2 commands
my $commands = $app->commands;
$app = $app->commands(Mojolicious::Commands->new);
Command line interface for your application, defaults to a
L<Mojolicious::Commands> object.
# Add another namespace to load commands from
push @{$app->commands->namespaces}, 'MyApp::Command';
=head2 controller_class
my $class = $app->controller_class;
$app = $app->controller_class('Mojolicious::Controller');
Class to be used for the default controller, defaults to
L<Mojolicious::Controller>.
=head2 mode
my $mode = $app->mode;
$app = $app->mode('production');
The operating mode for your application, defaults to a value from the
C<MOJO_MODE> and C<PLACK_ENV> environment variables or C<development>. Right
before calling L</"startup">, L<Mojolicious> will pick up the current mode,
name the log file after it and raise the log level from C<debug> to C<info> if
it has a value other than C<development>.
=head2 moniker
my $moniker = $app->moniker;
$app = $app->moniker('foo_bar');
Moniker of this application, often used as default filename for configuration
files and the like, defaults to decamelizing the application class with
L<Mojo::Util/"decamelize">.
=head2 plugins
my $plugins = $app->plugins;
$app = $app->plugins(Mojolicious::Plugins->new);
The plugin manager, defaults to a L<Mojolicious::Plugins> object. See the
L</"plugin"> method below if you want to load a plugin.
# Add another namespace to load plugins from
push @{$app->plugins->namespaces}, 'MyApp::Plugin';
=head2 renderer
my $renderer = $app->renderer;
$app = $app->renderer(Mojolicious::Renderer->new);
Used in your application to render content, defaults to a
L<Mojolicious::Renderer> object. The two main renderer plugins
L<Mojolicious::Plugin::EPRenderer> and L<Mojolicious::Plugin::EPLRenderer>
contain more information.
# Add another "templates" directory
push @{$app->renderer->paths}, '/home/sri/templates';
# Add another class with templates in DATA section
push @{$app->renderer->classes}, 'Mojolicious::Plugin::Fun';
=head2 routes
my $routes = $app->routes;
$app = $app->routes(Mojolicious::Routes->new);
The router, defaults to a L<Mojolicious::Routes> object. You use this in your
startup method to define the url endpoints for your application.
# Add routes
my $r = $app->routes;
$r->get('/foo/bar')->to('test#foo', title => 'Hello Mojo!');
$r->post('/baz')->to('test#baz');
# Add another namespace to load controllers from
push @{$app->routes->namespaces}, 'MyApp::MyController';
=head2 secrets
my $secrets = $app->secrets;
$app = $app->secrets(['passw0rd']);
Secret passphrases used for signed cookies and the like, defaults to the
L</"moniker"> of this application, which is not very secure, so you should
change it!!! As long as you are using the insecure default there will be debug
messages in the log file reminding you to change your passphrase. Only the
first passphrase is used to create new signatures, but all of them for
verification. So you can increase security without invalidating all your
existing signed cookies by rotating passphrases, just add new ones to the
front and remove old ones from the back.
# Rotate passphrases
$app->secrets(['new_passw0rd', 'old_passw0rd', 'very_old_passw0rd']);
=head2 sessions
my $sessions = $app->sessions;
$app = $app->sessions(Mojolicious::Sessions->new);
Signed cookie based session manager, defaults to a L<Mojolicious::Sessions>
object. You can usually leave this alone, see
L<Mojolicious::Controller/"session"> for more information about working with
session data.
# Change name of cookie used for all sessions
$app->sessions->cookie_name('mysession');
=head2 static
my $static = $app->static;
$app = $app->static(Mojolicious::Static->new);
For serving static files from your C<public> directories, defaults to a
L<Mojolicious::Static> object.
# Add another "public" directory
push @{$app->static->paths}, '/home/sri/public';
# Add another class with static files in DATA section
push @{$app->static->classes}, 'Mojolicious::Plugin::Fun';
=head2 types
my $types = $app->types;
$app = $app->types(Mojolicious::Types->new);
Responsible for connecting file extensions with MIME types, defaults to a
L<Mojolicious::Types> object.
# Add custom MIME type
$app->types->type(twt => 'text/tweet');
=head2 validator
my $validator = $app->validator;
$app = $app->validator(Mojolicious::Validator->new);
Validate parameters, defaults to a L<Mojolicious::Validator> object.
=head1 METHODS
L<Mojolicious> inherits all methods from L<Mojo> and implements the following
new ones.
=head2 build_controller
my $c = $app->build_controller;
my $c = $app->build_controller(Mojo::Transaction::HTTP->new);
my $c = $app->build_controller(Mojolicious::Controller->new);
Build default controller object with L</"controller_class">.
# Render template from application
my $foo = $app->build_controller->render_to_string(template => 'foo');
=head2 build_tx
my $tx = $app->build_tx;
Build L<Mojo::Transaction::HTTP> object and emit L</"after_build_tx"> hook.
=head2 defaults
my $hash = $app->defaults;
my $foo = $app->defaults('foo');
$app = $app->defaults({foo => 'bar'});
$app = $app->defaults(foo => 'bar');
Default values for L<Mojolicious::Controller/"stash">, assigned for every new
request.
# Remove value
my $foo = delete $app->defaults->{foo};
=head2 dispatch
$app->dispatch(Mojolicious::Controller->new);
The heart of every L<Mojolicious> application, calls the L</"static"> and
L</"routes"> dispatchers for every request and passes them a
L<Mojolicious::Controller> object.
=head2 handler
$app->handler(Mojo::Transaction::HTTP->new);
$app->handler(Mojolicious::Controller->new);
Sets up the default controller and emits the L</"around_dispatch"> hook for
every request.
=head2 helper
$app->helper(foo => sub {...});
Add a new helper that will be available as a method of the controller object
and the application object, as well as a function in C<ep> templates.
# Helper
$app->helper(cache => sub { state $cache = {} });
# Application
$app->cache->{foo} = 'bar';
my $result = $app->cache->{foo};
# Controller
$c->cache->{foo} = 'bar';
my $result = $c->cache->{foo};
# Template
% cache->{foo} = 'bar';
%= cache->{foo}
=head2 hook
$app->hook(after_dispatch => sub {...});
Extend L<Mojolicious> with hooks, which allow code to be shared with all
requests indiscriminately, for a full list of available hooks see L</"HOOKS">.
# Dispatchers will not run if there's already a response code defined
$app->hook(before_dispatch => sub {
my $c = shift;
$c->render(text => 'Skipped static file server and router!')
if $c->req->url->path->to_route =~ /do_not_dispatch/;
});
=head2 new
my $app = Mojolicious->new;
Construct a new L<Mojolicious> application and call L</"startup">. Will
automatically detect your home directory and set up logging based on your
current operating mode. Also sets up the renderer, static file server, a
default set of plugins and an L</"around_dispatch"> hook with the default
exception handling.
=head2 plugin
$app->plugin('some_thing');
$app->plugin('some_thing', foo => 23);
$app->plugin('some_thing', {foo => 23});
$app->plugin('SomeThing');
$app->plugin('SomeThing', foo => 23);
$app->plugin('SomeThing', {foo => 23});
$app->plugin('MyApp::Plugin::SomeThing');
$app->plugin('MyApp::Plugin::SomeThing', foo => 23);
$app->plugin('MyApp::Plugin::SomeThing', {foo => 23});
Load a plugin, for a full list of example plugins included in the
L<Mojolicious> distribution see L<Mojolicious::Plugins/"PLUGINS">.
=head2 start
$app->start;
$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">.
# Always start daemon and ignore @ARGV
$app->start('daemon', '-l', 'http://*:8080');
=head2 startup
$app->startup;
This is your main hook into the application, it will be called at application
startup. Meant to be overloaded in a subclass.
sub startup {
my $self = shift;
...
}
=head1 AUTOLOAD
In addition to the L</"ATTRIBUTES"> and L</"METHODS"> above you can also call
helpers on L<Mojolicious> objects. This includes all helpers from
L<Mojolicious::Plugin::DefaultHelpers> and L<Mojolicious::Plugin::TagHelpers>.
Note that application helpers are always called with a new default controller
object, so they can't depend on or change controller state, which includes
request, response and stash.
# Call helper
say $app->dumper({foo => 'bar'});
# Longer version
say $app->build_controller->helpers->dumper({foo => 'bar'});
=head1 BUNDLED FILES
The L<Mojolicious> distribution includes a few files with different licenses
that have been bundled for internal use.
=head2 Mojolicious Artwork
Copyright (C) 2010-2014, Sebastian Riedel.
Licensed under the CC-SA License, Version 4.0
L<http://creativecommons.org/licenses/by-sa/4.0>.
=head2 jQuery
Copyright (C) 2005, 2014 jQuery Foundation, Inc.
Licensed under the MIT License, L<http://creativecommons.org/licenses/MIT>.
=head2 prettify.js
Copyright (C) 2006, 2013 Google Inc.
Licensed under the Apache License, Version 2.0
L<http://www.apache.org/licenses/LICENSE-2.0>.
=head1 CODE NAMES
Every major release of L<Mojolicious> has a code name, these are the ones that
have been used in the past.
5.0, C<Tiger Face> (u1F42F)
4.0, C<Top Hat> (u1F3A9)
3.0, C<Rainbow> (u1F308)
2.0, C<Leaf Fluttering In Wind> (u1F343)
1.4, C<Smiling Face With Sunglasses> (u1F60E)
1.3, C<Tropical Drink> (u1F379)
1.1, C<Smiling Cat Face With Heart-Shaped Eyes> (u1F63B)
1.0, C<Snowflake> (u2744)
0.999930, C<Hot Beverage> (u2615)
0.999927, C<Comet> (u2604)
0.999920, C<Snowman> (u2603)
=head1 SPONSORS
Some of the work on this distribution has been sponsored by
L<The Perl Foundation|http://www.perlfoundation.org>, thank you!
=head1 PROJECT FOUNDER
Sebastian Riedel, C<sri@cpan.org>
=head1 CORE DEVELOPERS
Current members of the core team in alphabetical order:
=over 2
Abhijit Menon-Sen, C<ams@cpan.org>
Glen Hinkle, C<tempire@cpan.org>
Jan Henning Thorsen, C<jhthorsen@cpan.org>
Joel Berger, C<jberger@cpan.org>
Marcus Ramberg, C<mramberg@cpan.org>
=back
=head1 CREDITS
In alphabetical order:
=over 2
Adam Kennedy
Adriano Ferreira
Al Newkirk
Alex Efros
Alex Salimon
Alexey Likhatskiy
Anatoly Sharifulin
Andre Vieth
Andreas Jaekel
Andreas Koenig
Andrew Fresh
Andrey Khozov
Andy Grundman
Aristotle Pagaltzis
Ashley Dev
Ask Bjoern Hansen
Audrey Tang
Ben Tyler
Ben van Staveren
Benjamin Erhart
Bernhard Graf
Breno G. de Oliveira
Brian Duggan
Brian Medley
Burak Gursoy
Ch Lamprecht
Charlie Brady
Chas. J. Owens IV
Christian Hansen
chromatic
Curt Tilmes
Daniel Kimsey
Danijel Tasov
Danny Thomas
David Davis
David Webb
Diego Kuperman
Dmitriy Shalashov
Dmitry Konstantinov
Dominik Jarmulowicz
Dominique Dumont
Douglas Christopher Wilson
Eugene Toropov
Gisle Aas
Graham Barr
Graham Knop
Henry Tang
Hideki Yamamura
Hiroki Toyokawa
Ian Goodacre
Ilya Chesnokov
James Duncan
Jan Jona Javorsek
Jan Schmidt
Jaroslav Muhin
Jesse Vincent
Johannes Plunien
John Kingsley
Jonathan Yu
Josh Leder
Kazuhiro Shibuya
Kevin Old
Kitamura Akatsuki
Klaus S. Madsen
Lars Balker Rasmussen
Leon Brocard
Magnus Holm
Maik Fischer
Mark Fowler
Mark Grimes
Mark Stosberg
Marty Tennison
Matthew Lineen
Maksym Komar
Maxim Vuets
Michael Gregorowicz
Michael Harris
Mike Magowan
Mirko Westermeier
Mons Anderson
Moritz Lenz
Neil Watkiss
Nic Sandfield
Nils Diewald
Oleg Zhelo
Pascal Gaudette
Paul Evans
Paul Tomlin
Pavel Shaydo
Pedro Melo
Peter Edwards
Pierre-Yves Ritschard
Quentin Carbonneaux
Rafal Pocztarski
Randal Schwartz
Rick Delaney
Robert Hicks
Robin Lee
Roland Lammel
Ryan Jendoubi
Sascha Kiefer
Scott Wiersdorf
Sergey Zasenko
Simon Bertrang
Simone Tampieri
Shu Cho
Skye Shaw
Stanis Trendelenburg
Steffen Ullrich
Stephane Este-Gracias
Tatsuhiko Miyagawa
Terrence Brannon
Tianon Gravi
Tomas Znamenacek
Ulrich Habel
Ulrich Kautz
Uwe Voelker
Viacheslav Tykhanovskyi
Victor Engmark
Viliam Pucik
Wes Cravens
Yaroslav Korshak
Yuki Kimoto
Zak B. Elep
=back
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2008-2014, Sebastian Riedel.
This program is free software, you can redistribute it and/or modify it under
the terms of the Artistic License version 2.0.
=head1 SEE ALSO
L<https://github.com/kraih/mojo>, L<Mojolicious::Guides>,
L<http://mojolicio.us>.
=cut
MOJOLICIOUS
$fatpacked{"Mojolicious/Command.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND';
package Mojolicious::Command;
use Mojo::Base -base;
use Carp 'croak';
use Cwd 'getcwd';
use File::Basename 'dirname';
use File::Path 'mkpath';
use File::Spec::Functions qw(catdir catfile);
use Mojo::Loader;
use Mojo::Server;
use Mojo::Template;
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 'quiet';
has usage => "Usage: APPLICATION\n";
sub chmod_file {
my ($self, $path, $mod) = @_;
chmod $mod, $path or croak qq{Can't chmod file "$path": $!};
say " [chmod] $path " . sprintf('%lo', $mod) unless $self->quiet;
return $self;
}
sub chmod_rel_file { $_[0]->chmod_file($_[0]->rel_file($_[1]), $_[2]) }
sub create_dir {
my ($self, $path) = @_;
if (-d $path) { say " [exist] $path" unless $self->quiet }
else {
mkpath $path or croak qq{Can't make directory "$path": $!};
say " [mkdir] $path" unless $self->quiet;
}
return $self;
}
sub create_rel_dir { $_[0]->create_dir($_[0]->rel_dir($_[1])) }
sub extract_usage {
my $self = shift;
open my $handle, '>', \my $output;
pod2usage -exitval => 'noexit', -input => (caller)[1], -output => $handle;
$output =~ s/^.*\n//;
$output =~ s/\n$//;
return unindent $output;
}
sub help { print shift->usage }
sub rel_dir { catdir(getcwd(), split '/', pop) }
sub rel_file { catfile(getcwd(), split '/', pop) }
sub render_data {
my ($self, $name) = (shift, shift);
Mojo::Template->new->name("template $name from DATA section")
->render(Mojo::Loader->new->data(ref $self, $name), @_);
}
sub render_to_file {
my ($self, $data, $path) = (shift, shift, shift);
return $self->write_file($path, $self->render_data($data, @_));
}
sub render_to_rel_file {
my $self = shift;
$self->render_to_file(shift, $self->rel_dir(shift), @_);
}
sub run { croak 'Method "run" not implemented by subclass' }
sub write_file {
my ($self, $path, $data) = @_;
$self->create_dir(dirname $path);
spurt $data, $path;
say " [write] $path" unless $self->quiet;
return $self;
}
sub write_rel_file { $_[0]->write_file($_[0]->rel_file($_[1]), $_[2]) }
1;
=encoding utf8
=head1 NAME
Mojolicious::Command - Command base class
=head1 SYNOPSIS
# Lowercase command name
package Mojolicious::Command::mycommand;
use Mojo::Base 'Mojolicious::Command';
# Short description
has description => 'My first Mojo command.';
# Short usage message
has usage => <<EOF;
Usage: APPLICATION mycommand [OPTIONS]
Options:
-s, --something Does something.
EOF
sub run {
my ($self, @args) = @_;
# Magic here! :)
}
=head1 DESCRIPTION
L<Mojolicious::Command> is an abstract base class for L<Mojolicious> commands.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command> implements the following attributes.
=head2 app
my $app = $command->app;
$command = $command->app(MyApp->new);
Application for command, defaults to a L<Mojo::HelloWorld> object.
# Introspect
say "Template path: $_" for @{$command->app->renderer->paths};
=head2 description
my $description = $command->description;
$command = $command->description('Foo!');
Short description of command, used for the command list.
=head2 quiet
my $bool = $command->quiet;
$command = $command->quiet($bool);
Limited command output.
=head2 usage
my $usage = $command->usage;
$command = $command->usage('Foo!');
Usage information for command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 chmod_file
$command = $command->chmod_file('/home/sri/foo.txt', 0644);
Change mode of a file.
=head2 chmod_rel_file
$command = $command->chmod_rel_file('foo/foo.txt', 0644);
Portably change mode of a file relative to the current working directory.
=head2 create_dir
$command = $command->create_dir('/home/sri/foo/bar');
Create a directory.
=head2 create_rel_dir
$command = $command->create_rel_dir('foo/bar/baz');
Portably create a directory relative to the current working directory.
=head2 extract_usage
my $usage = $command->extract_usage;
Extract usage message from the SYNOPSIS section of the file this method was
called from.
=head2 help
$command->help;
Print usage information for command.
=head2 rel_dir
my $path = $command->rel_dir('foo/bar');
Portably generate an absolute path for a directory relative to the current
working directory.
=head2 rel_file
my $path = $command->rel_file('foo/bar.txt');
Portably generate an absolute path for a file relative to the current working
directory.
=head2 render_data
my $data = $command->render_data('foo_bar');
my $data = $command->render_data('foo_bar', @args);
Render a template from the C<DATA> section of the command class with
L<Mojo::Template>.
=head2 render_to_file
$command = $command->render_to_file('foo_bar', '/home/sri/foo.txt');
$command = $command->render_to_file('foo_bar', '/home/sri/foo.txt', @args);
Render a template from the C<DATA> section of the command class with
L<Mojo::Template> to a file and create directory if necessary.
=head2 render_to_rel_file
$command = $command->render_to_rel_file('foo_bar', 'foo/bar.txt');
$command = $command->render_to_rel_file('foo_bar', 'foo/bar.txt', @args);
Portably render a template from the C<DATA> section of the command class with
L<Mojo::Template> to a file relative to the current working directory and
create directory if necessary.
=head2 run
$command->run;
$command->run(@ARGV);
Run command. Meant to be overloaded in a subclass.
=head2 write_file
$command = $command->write_file('/home/sri/foo.txt', 'Hello World!');
Write text to a file and create directory if necessary.
=head2 write_rel_file
$command = $command->write_rel_file('foo/bar.txt', 'Hello World!');
Portably write text to a file relative to the current working directory and
create directory if necessary.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND
$fatpacked{"Mojolicious/Command/cgi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_CGI';
package Mojolicious::Command::cgi;
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 usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args, nph => \(my $nph = 0);
Mojo::Server::CGI->new(app => $self->app, nph => $nph)->run;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::cgi - CGI command
=head1 SYNOPSIS
Usage: APPLICATION cgi [OPTIONS]
Options:
--nph Enable non-parsed-header mode.
=head1 DESCRIPTION
L<Mojolicious::Command::cgi> starts applications with L<Mojo::Server::CGI>
backend.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::cgi> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $cgi->description;
$cgi = $cgi->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $cgi->usage;
$cgi = $cgi->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::cgi> inherits all methods from L<Mojolicious::Command>
and implements the following new ones.
=head2 run
$cgi->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_CGI
$fatpacked{"Mojolicious/Command/cpanify.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_CPANIFY';
package Mojolicious::Command::cpanify;
use Mojo::Base 'Mojolicious::Command';
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 usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args,
'p|password=s' => \(my $password = ''),
'u|user=s' => \(my $user = '');
die $self->usage unless my $file = shift @args;
my $tx = Mojo::UserAgent->new->tap(sub { $_->proxy->detect })->post(
"https://$user:$password\@pause.perl.org/pause/authenquery" => form => {
HIDDENNAME => $user,
CAN_MULTIPART => 1,
pause99_add_uri_upload => basename($file),
SUBMIT_pause99_add_uri_httpupload => ' Upload this file from my disk ',
pause99_add_uri_uri => '',
pause99_add_uri_httpupload => {file => $file},
}
);
unless ($tx->success) {
my $code = $tx->res->code // 0;
my $msg = $tx->error->{message};
if ($code == 401) { $msg = 'Wrong username or password.' }
elsif ($code == 409) { $msg = 'File already exists on CPAN.' }
die qq{Problem uploading file "$file": $msg\n};
}
say 'Upload successful!';
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::cpanify - Cpanify command
=head1 SYNOPSIS
Usage: APPLICATION cpanify [OPTIONS] [FILE]
mojo cpanify -u sri -p secr3t Mojolicious-Plugin-MyPlugin-0.01.tar.gz
Options:
-p, --password <password> PAUSE password.
-u, --user <name> PAUSE username.
=head1 DESCRIPTION
L<Mojolicious::Command::cpanify> uploads files to CPAN.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::cpanify> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $cpanify->description;
$cpanify = $cpanify->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $cpanify->usage;
$cpanify = $cpanify->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::cpanify> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$cpanify->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_CPANIFY
$fatpacked{"Mojolicious/Command/daemon.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_DAEMON';
package Mojolicious::Command::daemon;
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 usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
my $daemon = Mojo::Server::Daemon->new(app => $self->app);
GetOptionsFromArray \@args,
'b|backlog=i' => sub { $daemon->backlog($_[1]) },
'c|clients=i' => sub { $daemon->max_clients($_[1]) },
'g|group=s' => sub { $daemon->group($_[1]) },
'i|inactivity=i' => sub { $daemon->inactivity_timeout($_[1]) },
'l|listen=s' => \my @listen,
'p|proxy' => sub { $daemon->reverse_proxy(1) },
'r|requests=i' => sub { $daemon->max_requests($_[1]) },
'u|user=s' => sub { $daemon->user($_[1]) };
$daemon->listen(\@listen) if @listen;
$daemon->run;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::daemon - Daemon command
=head1 SYNOPSIS
Usage: APPLICATION daemon [OPTIONS]
./myapp.pl daemon -m production -l http://*:8080
./myapp.pl daemon -l http://127.0.0.1:8080 -l https://[::]:8081
./myapp.pl daemon -l 'https://*:443?cert=./server.crt&key=./server.key'
Options:
-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.
-i, --inactivity <seconds> Inactivity timeout, defaults to the value of
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".
-p, --proxy Activate reverse proxy support, defaults to
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.
=head1 DESCRIPTION
L<Mojolicious::Command::daemon> starts applications with
L<Mojo::Server::Daemon> backend.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::daemon> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $daemon->description;
$daemon = $daemon->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $daemon->usage;
$daemon = $daemon->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::daemon> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$daemon->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_DAEMON
$fatpacked{"Mojolicious/Command/eval.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_EVAL';
package Mojolicious::Command::eval;
use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
has description => 'Run code against application.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args, 'v|verbose' => \my $v1, 'V' => \my $v2;
my $code = shift @args || '';
# Run code against application
my $app = $self->app;
no warnings;
my $result = eval "package main; sub app; local *app = sub { \$app }; $code";
return $@ ? die $@ : $result unless defined $result && ($v1 || $v2);
$v2 ? print($app->dumper($result)) : say $result;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::eval - Eval command
=head1 SYNOPSIS
Usage: APPLICATION eval [OPTIONS] CODE
./myapp.pl eval 'say app->ua->get("/")->res->body'
./myapp.pl eval -v 'app->home'
./myapp.pl eval -V 'app->renderer->paths'
Options:
-v, --verbose Print return value to STDOUT.
-V Print returned data structure to STDOUT.
=head1 DESCRIPTION
L<Mojolicious::Command::eval> runs code against applications.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::eval> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $eval->description;
$eval = $eval->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $eval->usage;
$eval = $eval->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::eval> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$eval->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_EVAL
$fatpacked{"Mojolicious/Command/generate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GENERATE';
package Mojolicious::Command::generate;
use Mojo::Base 'Mojolicious::Commands';
has description => 'Generate files and directories from templates.';
has hint => <<EOF;
See 'APPLICATION generate help GENERATOR' for more information on a specific
generator.
EOF
has message => sub { shift->extract_usage . "\nGenerators:\n" };
has namespaces => sub { ['Mojolicious::Command::generate'] };
sub help { shift->run(@_) }
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::generate - Generator command
=head1 SYNOPSIS
Usage: APPLICATION generate GENERATOR [OPTIONS]
=head1 DESCRIPTION
L<Mojolicious::Command::generate> lists available generators.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::generate> inherits all attributes from
L<Mojolicious::Commands> and implements the following new ones.
=head2 description
my $description = $generator->description;
$generator = $generator->description('Foo!');
Short description of this command, used for the command list.
=head2 hint
my $hint = $generator->hint;
$generator = $generator->hint('Foo!');
Short hint shown after listing available generator commands.
=head2 message
my $msg = $generator->message;
$generator = $generator->message('Bar!');
Short usage message shown before listing available generator commands.
=head2 namespaces
my $namespaces = $generator->namespaces;
$generator = $generator->namespaces(['MyApp::Command::generate']);
Namespaces to search for available generator commands, defaults to
L<Mojolicious::Command::generate>.
=head1 METHODS
L<Mojolicious::Command::generate> inherits all methods from
L<Mojolicious::Commands> and implements the following new ones.
=head2 help
$generator->help('app');
Print usage information for generator command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GENERATE
$fatpacked{"Mojolicious/Command/generate/app.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GENERATE_APP';
package Mojolicious::Command::generate::app;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(class_to_file class_to_path);
has description => 'Generate Mojolicious application directory structure.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, $class) = @_;
$class ||= 'MyApp';
# Prevent bad applications
die <<EOF unless $class =~ /^[A-Z](?:\w|::)+$/;
Your application name has to be a well formed (CamelCase) Perl module name
like "MyApp".
EOF
# Script
my $name = class_to_file $class;
$self->render_to_rel_file('mojo', "$name/script/$name", $class);
$self->chmod_rel_file("$name/script/$name", 0744);
# Application class
my $app = class_to_path $class;
$self->render_to_rel_file('appclass', "$name/lib/$app", $class);
# Controller
my $controller = "${class}::Controller::Example";
my $path = class_to_path $controller;
$self->render_to_rel_file('controller', "$name/lib/$path", $controller);
# Test
$self->render_to_rel_file('test', "$name/t/basic.t", $class);
# Log directory
$self->create_rel_dir("$name/log");
# Static file
$self->render_to_rel_file('static', "$name/public/index.html");
# Templates
$self->render_to_rel_file('layout',
"$name/templates/layouts/default.html.ep");
$self->render_to_rel_file('welcome',
"$name/templates/example/welcome.html.ep");
}
1;
__DATA__
@@ mojo
% my $class = shift;
#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../lib" }
# Start command line interface for application
require Mojolicious::Commands;
Mojolicious::Commands->start_app('<%= $class %>');
@@ appclass
% my $class = shift;
package <%= $class %>;
use Mojo::Base 'Mojolicious';
# This method will run once at server start
sub startup {
my $self = shift;
# Documentation browser under "/perldoc"
$self->plugin('PODRenderer');
# Router
my $r = $self->routes;
# Normal route to controller
$r->get('/')->to('example#welcome');
}
1;
@@ controller
% my $class = shift;
package <%= $class %>;
use Mojo::Base 'Mojolicious::Controller';
# This action will render a template
sub welcome {
my $self = shift;
# Render template "example/welcome.html.ep" with message
$self->render(msg => 'Welcome to the Mojolicious real-time web framework!');
}
1;
@@ static
<!DOCTYPE html>
<html>
<head>
<title>Welcome to the Mojolicious real-time web framework!</title>
</head>
<body>
<h2>Welcome to the Mojolicious real-time web framework!</h2>
This is the static document "public/index.html",
<a href="/">click here</a> to get back to the start.
</body>
</html>
@@ test
% my $class = shift;
use Mojo::Base -strict;
use Test::More;
use Test::Mojo;
my $t = Test::Mojo->new('<%= $class %>');
$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
done_testing();
@@ layout
<!DOCTYPE html>
<html>
<head><title><%%= title %></title></head>
<body><%%= content %></body>
</html>
@@ welcome
%% layout 'default';
%% title 'Welcome';
<h2><%%= $msg %></h2>
This page was generated from the template "templates/example/welcome.html.ep"
and the layout "templates/layouts/default.html.ep",
<a href="<%%== url_for %>">click here</a> to reload the page or
<a href="/index.html">here</a> to move forward to a static page.
__END__
=encoding utf8
=head1 NAME
Mojolicious::Command::generate::app - App generator command
=head1 SYNOPSIS
Usage: APPLICATION generate app [NAME]
=head1 DESCRIPTION
L<Mojolicious::Command::generate::app> generates application directory
structures for fully functional L<Mojolicious> applications.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::generate::app> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $app->description;
$app = $app->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $app->usage;
$app = $app->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::generate::app> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$app->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GENERATE_APP
$fatpacked{"Mojolicious/Command/generate/lite_app.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GENERATE_LITE_APP';
package Mojolicious::Command::generate::lite_app;
use Mojo::Base 'Mojolicious::Command';
has description => 'Generate Mojolicious::Lite application.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, $name) = @_;
$name ||= 'myapp.pl';
$self->render_to_rel_file('liteapp', $name);
$self->chmod_rel_file($name, 0744);
}
1;
__DATA__
@@ liteapp
#!/usr/bin/env perl
use Mojolicious::Lite;
# Documentation browser under "/perldoc"
plugin 'PODRenderer';
get '/' => sub {
my $c = shift;
$c->render('index');
};
app->start;
<% %>__DATA__
<% %>@@ index.html.ep
%% layout 'default';
%% title 'Welcome';
Welcome to the Mojolicious real-time web framework!
<% %>@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head><title><%%= title %></title></head>
<body><%%= content %></body>
</html>
__END__
=encoding utf8
=head1 NAME
Mojolicious::Command::generate::lite_app - Lite app generator command
=head1 SYNOPSIS
Usage: APPLICATION generate lite_app [NAME]
=head1 DESCRIPTION
L<Mojolicious::Command::generate::lite_app> generate fully functional
L<Mojolicious::Lite> applications.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::generate::lite_app> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $app->description;
$app = $app->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $app->usage;
$app = $app->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::generate::lite_app> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$app->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GENERATE_LITE_APP
$fatpacked{"Mojolicious/Command/generate/makefile.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GENERATE_MAKEFILE';
package Mojolicious::Command::generate::makefile;
use Mojo::Base 'Mojolicious::Command';
use Mojolicious;
has description => 'Generate "Makefile.PL".';
has usage => sub { shift->extract_usage };
sub run { shift->render_to_rel_file('makefile', 'Makefile.PL') }
1;
__DATA__
@@ makefile
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
VERSION => '0.01',
PREREQ_PM => {'Mojolicious' => '<%= $Mojolicious::VERSION %>'},
test => {TESTS => 't/*.t'}
);
__END__
=encoding utf8
=head1 NAME
Mojolicious::Command::generate::makefile - Makefile generator command
=head1 SYNOPSIS
Usage: APPLICATION generate makefile
=head1 DESCRIPTION
L<Mojolicious::Command::generate::makefile> generates C<Makefile.PL> files for
applications.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::generate::makefile> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $makefile->description;
$makefile = $makefile->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $makefile->usage;
$makefile = $makefile->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::generate::makefile> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$makefile->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GENERATE_MAKEFILE
$fatpacked{"Mojolicious/Command/generate/plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GENERATE_PLUGIN';
package Mojolicious::Command::generate::plugin;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Util qw(camelize class_to_path);
use Mojolicious;
has description => 'Generate Mojolicious plugin directory structure.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, $name) = @_;
$name ||= 'MyPlugin';
# Class
my $class = $name =~ /^[a-z]/ ? camelize($name) : $name;
$class = "Mojolicious::Plugin::$class";
my $app = class_to_path $class;
my $dir = join '-', split '::', $class;
$self->render_to_rel_file('class', "$dir/lib/$app", $class, $name);
# Test
$self->render_to_rel_file('test', "$dir/t/basic.t", $name);
# Makefile
$self->render_to_rel_file('makefile', "$dir/Makefile.PL", $class, $app);
}
1;
__DATA__
@@ class
% my ($class, $name) = @_;
package <%= $class %>;
use Mojo::Base 'Mojolicious::Plugin';
our $VERSION = '0.01';
sub register {
my ($self, $app) = @_;
}
1;
<% %>__END__
<% %>=encoding utf8
<% %>=head1 NAME
<%= $class %> - Mojolicious Plugin
<% %>=head1 SYNOPSIS
# Mojolicious
$self->plugin('<%= $name %>');
# Mojolicious::Lite
plugin '<%= $name %>';
<% %>=head1 DESCRIPTION
L<<%= $class %>> is a L<Mojolicious> plugin.
<% %>=head1 METHODS
L<<%= $class %>> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
<% %>=head2 register
$plugin->register(Mojolicious->new);
Register plugin in L<Mojolicious> application.
<% %>=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
<% %>=cut
@@ test
% my $name = shift;
use Mojo::Base -strict;
use Test::More;
use Mojolicious::Lite;
use Test::Mojo;
plugin '<%= $name %>';
get '/' => sub {
my $c = shift;
$c->render(text => 'Hello Mojo!');
};
my $t = Test::Mojo->new;
$t->get_ok('/')->status_is(200)->content_is('Hello Mojo!');
done_testing();
@@ makefile
% my ($class, $path) = @_;
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => '<%= $class %>',
VERSION_FROM => 'lib/<%= $path %>',
AUTHOR => 'A Good Programmer <nospam@cpan.org>',
PREREQ_PM => {'Mojolicious' => '<%= $Mojolicious::VERSION %>'},
test => {TESTS => 't/*.t'}
);
__END__
=encoding utf8
=head1 NAME
Mojolicious::Command::generate::plugin - Plugin generator command
=head1 SYNOPSIS
Usage: APPLICATION generate plugin [NAME]
=head1 DESCRIPTION
L<Mojolicious::Command::generate::plugin> generates directory structures for
fully functional L<Mojolicious> plugins.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::generate::plugin> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $plugin->description;
$plugin = $plugin->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $plugin->usage;
$plugin = $plugin->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::generate::plugin> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$plugin->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GENERATE_PLUGIN
$fatpacked{"Mojolicious/Command/get.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_GET';
package Mojolicious::Command::get;
use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
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 usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args,
'C|charset=s' => \my $charset,
'c|content=s' => \(my $content = ''),
'H|header=s' => \my @headers,
'M|method=s' => \(my $method = 'GET'),
'r|redirect' => \my $redirect,
'v|verbose' => \my $verbose;
@args = map { decode 'UTF-8', $_ } @args;
die $self->usage unless my $url = shift @args;
my $selector = shift @args;
# Parse header pairs
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);
$ua->max_redirects(10) if $redirect;
my $buffer = '';
$ua->on(
start => sub {
my ($ua, $tx) = @_;
# Verbose
weaken $tx;
$tx->res->content->on(
body => sub {
warn $tx->req->$_ for qw(build_start_line build_headers);
warn $tx->res->$_ for qw(build_start_line build_headers);
}
) if $verbose;
# Stream content (ignore redirects)
$tx->res->content->unsubscribe('read')->on(
read => sub {
return if $redirect && $tx->res->is_status_class(300);
defined $selector ? ($buffer .= pop) : print pop;
}
);
}
);
# Switch to verbose for HEAD requests
$verbose = 1 if $method eq 'HEAD';
STDOUT->autoflush(1);
my $tx = $ua->start($ua->build_tx($method, $url, \%headers, $content));
my $err = $tx->error;
warn qq{Problem loading URL "@{[$tx->req->url]}": $err->{message}\n}
if $err && !$err->{code};
# JSON Pointer
return unless defined $selector;
return _json($buffer, $selector) if $selector eq '' || $selector =~ m!^/!;
# Selector
_select($buffer, $selector, $charset // $tx->res->content->charset, @args);
}
sub _json {
return unless my $data = j(shift);
return unless defined($data = Mojo::JSON::Pointer->new($data)->get(shift));
return _say($data) unless ref $data eq 'HASH' || ref $data eq 'ARRAY';
say encode_json($data);
}
sub _say { length && say encode('UTF-8', $_) for @_ }
sub _select {
my ($buffer, $selector, $charset, @args) = @_;
# Keep a strong reference to the root
$buffer = decode($charset, $buffer) // $buffer if $charset;
my $dom = Mojo::DOM->new($buffer);
my $results = $dom->find($selector);
while (defined(my $command = shift @args)) {
# Number
($results = $results->slice($command)) and next if $command =~ /^\d+$/;
# Text
return _say($results->text->each) if $command eq 'text';
# All text
return _say($results->all_text->each) if $command eq 'all';
# Attribute
return _say($results->attr($args[0] // '')->each) if $command eq 'attr';
# Unknown
die qq{Unknown command "$command".\n};
}
_say($results->each);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::get - Get command
=head1 SYNOPSIS
Usage: APPLICATION get [OPTIONS] URL [SELECTOR|JSON-POINTER] [COMMANDS]
./myapp.pl get /
mojo get mojolicio.us
mojo get -v -r google.com
mojo get -v -H 'Host: mojolicious.org' -H 'Accept: */*' mojolicio.us
mojo get -M POST -c 'trololo' mojolicio.us
mojo get mojolicio.us 'head > title' text
mojo get mojolicio.us .footer all
mojo get mojolicio.us a attr href
mojo get mojolicio.us '*' attr id
mojo get mojolicio.us 'h1, h2, h3' 3 text
mojo get https://api.metacpan.org/v0/author/SRI /name
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.
=head1 DESCRIPTION
L<Mojolicious::Command::get> is a command line interface for
L<Mojo::UserAgent>.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::get> performs requests to remote hosts or local
applications.
=head2 description
my $description = $get->description;
$get = $get->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $get->usage;
$get = $get->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::get> inherits all methods from L<Mojolicious::Command>
and implements the following new ones.
=head2 run
$get->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_GET
$fatpacked{"Mojolicious/Command/inflate.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_INFLATE';
package Mojolicious::Command::inflate;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Loader;
use Mojo::Util 'encode';
has description => 'Inflate embedded files to real files.';
has usage => sub { shift->extract_usage };
sub run {
my $self = shift;
# Find all embedded files
my %all;
my $app = $self->app;
my $loader = Mojo::Loader->new;
for my $class (@{$app->renderer->classes}, @{$app->static->classes}) {
for my $name (keys %{$loader->data($class)}) {
my $data = $loader->data($class, $name);
$all{$name}
= $loader->is_binary($class, $name) ? $data : encode('UTF-8', $data);
}
}
# Turn them into real files
for my $name (grep {/\.\w+$/} keys %all) {
my $prefix = $name =~ /\.\w+\.\w+$/ ? 'templates' : 'public';
$self->write_file($self->rel_file("$prefix/$name"), $all{$name});
}
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::inflate - Inflate command
=head1 SYNOPSIS
Usage: APPLICATION inflate
=head1 DESCRIPTION
L<Mojolicious::Command::inflate> turns templates and static files embedded in
the C<DATA> sections of your application into real files.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::inflate> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $inflate->description;
$inflate = $inflate->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $inflate->usage;
$inflate = $inflate->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::inflate> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$inflate->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_INFLATE
$fatpacked{"Mojolicious/Command/prefork.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_PREFORK';
package Mojolicious::Command::prefork;
use Mojo::Base 'Mojolicious::Command';
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.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
my $prefork = Mojo::Server::Prefork->new(app => $self->app);
GetOptionsFromArray \@args,
'A|accepts=i' => sub { $prefork->accepts($_[1]) },
'a|accept-interval=f' => sub { $prefork->accept_interval($_[1]) },
'b|backlog=i' => sub { $prefork->backlog($_[1]) },
'c|clients=i' => sub { $prefork->max_clients($_[1]) },
'G|graceful-timeout=i' => sub { $prefork->graceful_timeout($_[1]) },
'g|group=s' => sub { $prefork->group($_[1]) },
'heartbeat-interval=i' => sub { $prefork->heartbeat_interval($_[1]) },
'H|heartbeat-timeout=i' => sub { $prefork->heartbeat_timeout($_[1]) },
'i|inactivity=i' => sub { $prefork->inactivity_timeout($_[1]) },
'lock-file=s' => sub { $prefork->lock_file($_[1]) },
'L|lock-timeout=f' => sub { $prefork->lock_timeout($_[1]) },
'l|listen=s' => \my @listen,
'multi-accept=i' => sub { $prefork->multi_accept($_[1]) },
'P|pid-file=s' => sub { $prefork->pid_file($_[1]) },
'p|proxy' => sub { $prefork->reverse_proxy(1) },
'r|requests=i' => sub { $prefork->max_requests($_[1]) },
'u|user=s' => sub { $prefork->user($_[1]) },
'w|workers=i' => sub { $prefork->workers($_[1]) };
$prefork->listen(\@listen) if @listen;
$prefork->run;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::prefork - Prefork command
=head1 SYNOPSIS
Usage: APPLICATION prefork [OPTIONS]
./myapp.pl prefork -m production -l http://*:8080
./myapp.pl prefork -l http://127.0.0.1:8080 -l https://[::]:8081
./myapp.pl prefork -l 'https://*:443?cert=./server.crt&key=./server.key'
Options:
-A, --accepts <number> Number of connections for workers to
accept, defaults to 1000.
-a, --accept-interval <seconds> Accept interval, defaults to 0.025.
-b, --backlog <size> Listen backlog size, defaults to
SOMAXCONN.
-c, --clients <number> Maximum number of concurrent clients,
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.
-i, --inactivity <seconds> Inactivity timeout, defaults to the
value of MOJO_INACTIVITY_TIMEOUT or
15.
--lock-file <path> Path to lock file, defaults to a
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".
--multi-accept <number> Number of connection to accept at
once, defaults to 50.
-P, --pid-file <path> Path to process id file, defaults to
a random file.
-p, --proxy Activate reverse proxy support,
defaults to 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.
-w, --workers <number> Number of workers, defaults to 4.
=head1 DESCRIPTION
L<Mojolicious::Command::prefork> starts applications with
L<Mojo::Server::Prefork> backend.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::prefork> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $prefork->description;
$prefork = $prefork->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $prefork->usage;
$prefork = $prefork->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::prefork> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$prefork->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_PREFORK
$fatpacked{"Mojolicious/Command/psgi.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_PSGI';
package Mojolicious::Command::psgi;
use Mojo::Base 'Mojolicious::Command';
use Mojo::Server::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 }
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::psgi - PSGI command
=head1 SYNOPSIS
Usage: APPLICATION psgi
=head1 DESCRIPTION
L<Mojolicious::Command::psgi> starts applications with L<Mojo::Server::PSGI>
backend.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::psgi> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $psgi->description;
$psgi = $psgi->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $psgi->usage;
$psgi = $psgi->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::psgi> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
my $app = $psgi->run;
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_PSGI
$fatpacked{"Mojolicious/Command/routes.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_ROUTES';
package Mojolicious::Command::routes;
use Mojo::Base 'Mojolicious::Command';
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 usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args, 'v|verbose' => \my $verbose;
my $rows = [];
_walk($_, 0, $rows, $verbose) for @{$self->app->routes->children};
print encode('UTF-8', tablify($rows));
}
sub _walk {
my ($route, $depth, $rows, $verbose) = @_;
# Pattern
my $prefix = '';
if (my $i = $depth * 2) { $prefix .= ' ' x $i . '+' }
push @$rows, my $row = [$prefix . ($route->pattern->pattern || '/')];
# Flags
my @flags;
push @flags, @{$route->over || []} ? 'C' : '.';
push @flags, (my $partial = $route->partial) ? 'D' : '.';
push @flags, $route->inline ? 'U' : '.';
push @flags, $route->is_websocket ? 'W' : '.';
push @$row, join('', @flags) if $verbose;
# Methods
my $via = $route->via;
push @$row, !$via ? '*' : uc join ',', @$via;
# Name
my $name = $route->name;
push @$row, $route->has_custom_name ? qq{"$name"} : $name;
# Regex (verbose)
my $pattern = $route->pattern;
$pattern->match('/', $route->is_endpoint && !$partial);
my $regex = (regexp_pattern $pattern->regex)[0];
my $format = (regexp_pattern($pattern->format_regex))[0];
push @$row, $regex, $format ? $format : '' if $verbose;
$depth++;
_walk($_, $depth, $rows, $verbose) for @{$route->children};
$depth--;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::routes - Routes command
=head1 SYNOPSIS
Usage: APPLICATION routes [OPTIONS]
Options:
-v, --verbose Print additional details about routes, flags indicate
C=Conditions, D=Detour, U=Under and W=WebSocket.
=head1 DESCRIPTION
L<Mojolicious::Command::routes> lists all your application routes.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::routes> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $routes->description;
$routes = $routes->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $routes->usage;
$routes = $routes->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::routes> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$routes->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_ROUTES
$fatpacked{"Mojolicious/Command/test.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_TEST';
package Mojolicious::Command::test;
use Mojo::Base 'Mojolicious::Command';
use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);
has description => 'Run tests.';
has usage => sub { shift->extract_usage };
sub run {
my ($self, @args) = @_;
GetOptionsFromArray \@args, 'v|verbose' => \$ENV{HARNESS_VERBOSE};
if (!@args && (my $home = $self->app->home)) {
die "Can't find test directory.\n" unless -d $home->rel_dir('t');
my $files = $home->list_files('t');
/\.t$/ and push @args, $home->rel_file("t/$_") for @$files;
say qq{Running tests from "}, $home->rel_dir('t') . '".';
}
$ENV{HARNESS_OPTIONS} //= 'c';
require Test::Harness;
Test::Harness::runtests(sort @args);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::test - Test command
=head1 SYNOPSIS
Usage: APPLICATION test [OPTIONS] [TESTS]
./myapp.pl test -v
./myapp.pl test t/foo.t
./myapp.pl test t/foo/*.t
Options:
-v, --verbose Print verbose debug information to STDERR.
=head1 DESCRIPTION
L<Mojolicious::Command::test> runs application tests from the C<t> directory.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::test> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $test->description;
$test = $test->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $test->usage;
$test = $test->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::test> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$test->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_TEST
$fatpacked{"Mojolicious/Command/version.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMAND_VERSION';
package Mojolicious::Command::version;
use Mojo::Base 'Mojolicious::Command';
use Mojo::IOLoop::Client;
use Mojo::UserAgent;
use Mojolicious;
has description => 'Show versions of installed modules.';
has usage => sub { shift->extract_usage };
sub run {
my $self = shift;
my $ev = eval 'use Mojo::Reactor::EV; 1' ? $EV::VERSION : 'not installed';
my $class = 'Mojo::IOLoop::Client';
my $socks = $class->SOCKS ? $IO::Socket::Socks::VERSION : 'not installed';
my $tls = $class->TLS ? $IO::Socket::SSL::VERSION : 'not installed';
my $ndn = $class->NDN ? $Net::DNS::Native::VERSION : 'not installed';
print <<EOF;
CORE
Perl ($^V, $^O)
Mojolicious ($Mojolicious::VERSION, $Mojolicious::CODENAME)
OPTIONAL
EV 4.0+ ($ev)
IO::Socket::Socks 0.64+ ($socks)
IO::Socket::SSL 1.84+ ($tls)
Net::DNS::Native ($ndn)
EOF
# Check latest version on CPAN
my $latest = eval {
Mojo::UserAgent->new(max_redirects => 10)->tap(sub { $_->proxy->detect })
->get('api.metacpan.org/v0/release/Mojolicious')->res->json->{version};
} or return;
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."
if $latest > $Mojolicious::VERSION;
say $msg;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Command::version - Version command
=head1 SYNOPSIS
Usage: APPLICATION version
=head1 DESCRIPTION
L<Mojolicious::Command::version> shows version information for installed core
and optional modules.
This is a core command, that means it is always enabled and its code a good
example for learning to build new commands, you're welcome to fork it.
See L<Mojolicious::Commands/"COMMANDS"> for a list of commands that are
available by default.
=head1 ATTRIBUTES
L<Mojolicious::Command::version> inherits all attributes from
L<Mojolicious::Command> and implements the following new ones.
=head2 description
my $description = $v->description;
$v = $v->description('Foo!');
Short description of this command, used for the command list.
=head2 usage
my $usage = $v->usage;
$v = $v->usage('Foo!');
Usage information for this command, used for the help screen.
=head1 METHODS
L<Mojolicious::Command::version> inherits all methods from
L<Mojolicious::Command> and implements the following new ones.
=head2 run
$v->run(@ARGV);
Run this command.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMAND_VERSION
$fatpacked{"Mojolicious/Commands.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_COMMANDS';
package Mojolicious::Commands;
use Mojo::Base 'Mojolicious::Command';
use Getopt::Long 'GetOptionsFromArray';
use Mojo::Server;
use Mojo::Util 'tablify';
has hint => <<EOF;
See 'APPLICATION help COMMAND' for more information on a specific command.
EOF
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};
# CGI
return 'cgi' if defined $ENV{PATH_INFO} || defined $ENV{GATEWAY_INTERFACE};
# Nothing
return $guess;
}
# Command line options for MOJO_HELP, MOJO_HOME and MOJO_MODE
sub _args {
return if __PACKAGE__->detect;
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('default');
}
BEGIN { _args([@ARGV]) }
sub run {
my ($self, $name, @args) = @_;
# Application loader
return $self->app if defined $ENV{MOJO_APP_LOADER};
# Try to detect environment
$name = $self->detect($name) unless $ENV{MOJO_NO_DETECT};
# 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;
my $module;
$module = _command("${_}::$name", 1) and last for @{$self->namespaces};
# Unknown command
die qq{Unknown command "$name", maybe you need to install it?\n}
unless $module;
# Run command (remove options shared by all commands)
_args(\@args);
my $command = $module->new(app => $self->app);
return $help ? $command->help(@args) : $command->run(@args);
}
# Hide list for tests
return 1 if $ENV{HARNESS_ACTIVE};
# Find all available commands
my (@rows, %seen);
my $loader = Mojo::Loader->new;
for my $ns (@{$self->namespaces}) {
for my $module (@{$loader->search($ns)}) {
next unless my $command = _command($module);
$command =~ s/^\Q$ns\E:://;
next if $seen{$command}++;
push @rows, [" $command", $module->new->description];
}
}
@rows = sort { $a->[0] cmp $b->[0] } @rows;
return print $self->message, tablify(\@rows), $self->hint;
}
sub start_app { shift; Mojo::Server->new->build_app(shift)->start(@_) }
sub _command {
my ($module, $fatal) = @_;
return $module->isa('Mojolicious::Command') ? $module : undef
unless my $e = Mojo::Loader->new->load($module);
$fatal && ref $e ? die $e : return undef;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Commands - Command line interface
=head1 SYNOPSIS
Usage: APPLICATION COMMAND [OPTIONS]
Tip: CGI and PSGI environments can be automatically detected very often and
work without commands.
Options (for all commands):
-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.
-m, --mode <name> Operating mode for your application, defaults to the
value of MOJO_MODE/PLACK_ENV or "development".
=head1 DESCRIPTION
L<Mojolicious::Commands> is the interactive command line interface for the
L<Mojolicious> framework. It will automatically detect available commands in
the C<Mojolicious::Command> namespace.
=head1 COMMANDS
These commands are available by default.
=head2 cgi
$ ./myapp.pl cgi
Use L<Mojolicious::Command::cgi> to start application with CGI backend,
usually auto detected.
=head2 cpanify
$ mojo cpanify -u sri -p secr3t Mojolicious-Plugin-Fun-0.1.tar.gz
Use L<Mojolicious::Command::cpanify> for uploading files to CPAN.
=head2 daemon
$ ./myapp.pl daemon
Use L<Mojolicious::Command::daemon> to start application with standalone HTTP
and WebSocket server.
=head2 eval
$ ./myapp.pl eval 'say app->home'
Use L<Mojolicious::Command::eval> to run code against application.
=head2 generate
$ mojo generate
$ mojo generate help
$ ./myapp.pl generate help
List available generator commands with short descriptions.
$ mojo generate help <generator>
$ ./myapp.pl generate help <generator>
List available options for generator command with short descriptions.
=head2 generate app
$ mojo generate app <AppName>
Use L<Mojolicious::Command::generate::app> to generate application directory
structure for a fully functional L<Mojolicious> application.
=head2 generate lite_app
$ mojo generate lite_app
Use L<Mojolicious::Command::generate::lite_app> to generate a fully functional
L<Mojolicious::Lite> application.
=head2 generate makefile
$ mojo generate makefile
$ ./myapp.pl generate makefile
Use L<Mojolicious::Command::generate::makefile> to generate C<Makefile.PL>
file for application.
=head2 generate plugin
$ mojo generate plugin <PluginName>
Use L<Mojolicious::Command::generate::plugin> to generate directory structure
for a fully functional L<Mojolicious> plugin.
=head2 get
$ mojo get http://mojolicio.us
$ ./myapp.pl get /foo
Use L<Mojolicious::Command::get> to perform requests to remote host or local
application.
=head2 help
$ mojo
$ mojo help
$ ./myapp.pl help
List available commands with short descriptions.
$ mojo help <command>
$ ./myapp.pl help <command>
List available options for the command with short descriptions.
=head2 inflate
$ ./myapp.pl inflate
Use L<Mojolicious::Command::inflate> to turn templates and static files
embedded in the C<DATA> sections of your application into real files.
=head2 prefork
$ ./myapp.pl prefork
Use L<Mojolicious::Command::prefork> to start application with standalone
preforking HTTP and WebSocket server.
=head2 psgi
$ ./myapp.pl psgi
Use L<Mojolicious::Command::psgi> to start application with PSGI backend,
usually auto detected.
=head2 routes
$ ./myapp.pl routes
Use L<Mojolicious::Command::routes> to list application routes.
=head2 test
$ ./myapp.pl test
$ ./myapp.pl test t/fun.t
Use L<Mojolicious::Command::test> to run application tests from the C<t>
directory.
=head2 version
$ mojo version
$ ./myapp.pl version
Use L<Mojolicious::Command::version> to show version information for installed
core and optional modules, very useful for debugging.
=head1 ATTRIBUTES
L<Mojolicious::Commands> inherits all attributes from L<Mojolicious::Command>
and implements the following new ones.
=head2 hint
my $hint = $commands->hint;
$commands = $commands->hint('Foo!');
Short hint shown after listing available commands.
=head2 message
my $msg = $commands->message;
$commands = $commands->message('Hello World!');
Short usage message shown before listing available commands.
=head2 namespaces
my $namespaces = $commands->namespaces;
$commands = $commands->namespaces(['MyApp::Command']);
Namespaces to load commands from, defaults to C<Mojolicious::Command>.
# Add another namespace to load commands from
push @{$commands->namespaces}, 'MyApp::Command';
=head1 METHODS
L<Mojolicious::Commands> inherits all methods from L<Mojolicious::Command> and
implements the following new ones.
=head2 detect
my $env = $commands->detect;
my $env = $commands->detect($guess);
Try to detect environment.
=head2 run
$commands->run;
$commands->run(@ARGV);
Load and run commands. Automatic deployment environment detection can be
disabled with the C<MOJO_NO_DETECT> environment variable.
=head2 start_app
Mojolicious::Commands->start_app('MyApp');
Mojolicious::Commands->start_app(MyApp => @ARGV);
Load application from class and start the command line interface for it.
# Always start daemon for application and ignore @ARGV
Mojolicious::Commands->start_app('MyApp', 'daemon', '-l', 'http://*:8080');
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_COMMANDS
$fatpacked{"Mojolicious/Controller.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_CONTROLLER';
package Mojolicious::Controller;
use Mojo::Base -base;
# No imports, for security reasons!
use Carp ();
use Mojo::ByteStream;
use Mojo::Transaction::HTTP;
use Mojo::URL;
use Mojo::Util;
use Mojolicious;
use Mojolicious::Routes::Match;
use Scalar::Util ();
use Time::HiRes ();
has app => sub { Mojolicious->new };
has match =>
sub { Mojolicious::Routes::Match->new(root => shift->app->routes) };
has tx => sub { Mojo::Transaction::HTTP->new };
# Reserved stash values
my %RESERVED = map { $_ => 1 } (
qw(action app cb controller data extends format handler json layout),
qw(namespace path status template text variant)
);
sub AUTOLOAD {
my $self = shift;
my ($package, $method) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
Carp::croak "Undefined subroutine &${package}::$method called"
unless Scalar::Util::blessed $self && $self->isa(__PACKAGE__);
# Call helper with current controller
Carp::croak qq{Can't locate object method "$method" via package "$package"}
unless my $helper = $self->app->renderer->get_helper($method);
return $self->$helper(@_);
}
sub continue { $_[0]->app->routes->continue($_[0]) }
sub cookie {
my ($self, $name) = (shift, shift);
# Multiple names
return map { $self->cookie($_) } @$name if ref $name eq 'ARRAY';
# Response cookie
if (@_) {
# Cookie too big
my $cookie = {name => $name, value => shift, %{shift || {}}};
$self->app->log->error(qq{Cookie "$name" is bigger than 4096 bytes.})
if length $cookie->{value} > 4096;
$self->res->cookies($cookie);
return $self;
}
# Request cookies
return undef unless my $cookie = $self->req->cookie($name);
return $cookie->value;
}
sub every_cookie {
[map { $_->value } @{shift->req->every_cookie(shift)}];
}
sub every_param { _param(@_) }
sub every_signed_cookie { _signed_cookie(@_) }
sub finish {
my $self = shift;
# WebSocket
my $tx = $self->tx;
$tx->finish(@_) and return $self if $tx->is_websocket;
# Chunked stream
return @_ ? $self->write_chunk(@_)->write_chunk('') : $self->write_chunk('')
if $tx->res->content->is_chunked;
# Normal stream
return @_ ? $self->write(@_)->write('') : $self->write('');
}
sub flash {
my $self = shift;
# Check old flash
my $session = $self->session;
return $session->{flash} ? $session->{flash}{$_[0]} : undef
if @_ == 1 && !ref $_[0];
# Initialize new flash and merge values
my $values = ref $_[0] ? $_[0] : {@_};
@{$session->{new_flash} ||= {}}{keys %$values} = values %$values;
return $self;
}
sub helpers { $_[0]->app->renderer->get_helper('')->($_[0]) }
sub on {
my ($self, $name, $cb) = @_;
my $tx = $self->tx;
$self->rendered(101) if $tx->is_websocket;
return $tx->on($name => sub { shift; $self->$cb(@_) });
}
sub param {
my ($self, $name) = (shift, shift);
# Multiple names
return map { $self->param($_) } @$name if ref $name eq 'ARRAY';
# List names
my $captures = $self->stash->{'mojo.captures'} ||= {};
my $req = $self->req;
unless (defined $name) {
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;
}
# Value
return _param($self, $name)->[-1] unless @_;
# Override values
$captures->{$name} = @_ > 1 ? [@_] : $_[0];
return $self;
}
sub redirect_to {
my $self = shift;
# Don't override 3xx status
my $res = $self->res;
$res->headers->location($self->url_for(@_));
return $self->rendered($res->is_status_class(300) ? () : 302);
}
sub render {
my $self = shift;
# Template may be first argument
my ($template, $args) = (@_ % 2 ? shift : undef, {@_});
$args->{template} = $template if $template;
my $app = $self->app;
my $plugins = $app->plugins->emit_hook(before_render => $self, $args);
my $maybe = delete $args->{'mojo.maybe'};
# Render
my $ts = $args->{'mojo.to_string'};
my ($output, $format) = $app->renderer->render($self, $args);
return defined $output ? Mojo::ByteStream->new($output) : undef if $ts;
# Maybe
return $maybe ? undef : !$self->render_not_found unless defined $output;
# Prepare response
$plugins->emit_hook(after_render => $self, \$output, $format);
my $headers = $self->res->body($output)->headers;
$headers->content_type($app->types->type($format) || 'text/plain')
unless $headers->content_type;
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 }
# DEPRECATED in Tiger Face!
sub render_static {
Mojo::Util::deprecated 'Mojolicious::Controller::render_static is DEPRECATED'
. ' in favor of the reply->static helper';
shift->helpers->reply->static(@_);
}
sub render_to_string { shift->render(@_, 'mojo.to_string' => 1) }
sub rendered {
my ($self, $status) = @_;
# Disable auto rendering and make sure we have a status
my $res = $self->render_later->res;
$res->code($status || 200) if $status || !$res->code;
# Finish transaction
my $stash = $self->stash;
unless ($stash->{'mojo.finished'}++) {
# Stop timer
my $app = $self->app;
if (my $started = delete $stash->{'mojo.started'}) {
my $elapsed = sprintf '%f',
Time::HiRes::tv_interval($started, [Time::HiRes::gettimeofday()]);
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->plugins->emit_hook_reverse(after_dispatch => $self);
$app->sessions->store($self);
}
$self->tx->resume;
return $self;
}
sub req { shift->tx->req }
sub res { shift->tx->res }
sub respond_to {
my $self = shift;
my $args = ref $_[0] ? $_[0] : {@_};
# Find target
my $target;
my $renderer = $self->app->renderer;
my @formats = @{$renderer->accepts($self)};
for my $format (@formats ? @formats : ($renderer->default_format)) {
next unless $target = $args->{$format};
$self->stash->{format} = $format;
last;
}
# Fallback
unless ($target) {
return $self->rendered(204) unless $target = $args->{any};
delete $self->stash->{format};
}
# Dispatch
ref $target eq 'CODE' ? $target->($self) : $self->render(%$target);
return $self;
}
sub send {
my ($self, $msg, $cb) = @_;
my $tx = $self->tx;
Carp::croak 'No WebSocket connection to send message to'
unless $tx->is_websocket;
$tx->send($msg, $cb ? sub { shift; $self->$cb(@_) } : ());
return $self->rendered(101);
}
sub session {
my $self = shift;
# Hash
my $session = $self->stash->{'mojo.session'} ||= {};
return $session unless @_;
# Get
return $session->{$_[0]} unless @_ > 1 || ref $_[0];
# Set
my $values = ref $_[0] ? $_[0] : {@_};
@$session{keys %$values} = values %$values;
return $self;
}
sub signed_cookie {
my ($self, $name, $value, $options) = @_;
# Multiple names
return map { $self->signed_cookie($_) } @$name if ref $name eq 'ARRAY';
# Request cookie
return _signed_cookie($self, $name)->[-1] unless defined $value;
# Response cookie
my $checksum
= Mojo::Util::hmac_sha1_sum($value, $self->stash->{'mojo.secrets'}[0]);
return $self->cookie($name, "$value--$checksum", $options);
}
sub stash { Mojo::Util::_stash(stash => @_) }
sub url_for {
my $self = shift;
my $target = shift // '';
# Absolute URL
return $target if Scalar::Util::blessed $target && $target->isa('Mojo::URL');
return Mojo::URL->new($target) if $target =~ m!^(?:[^:/?#]+:|//)!;
# Base
my $url = Mojo::URL->new;
my $req = $self->req;
my $base = $url->base($req->url->base->clone)->base->userinfo(undef);
# Relative URL
my $path = $url->path;
if ($target =~ m!^/!) {
if (my $prefix = $self->stash->{path}) {
my $real = $req->url->path->to_route;
$real =~ s!/?\Q$prefix\E$!$target!;
$target = $real;
}
$url->parse($target);
}
# Route
else {
my $generated = $self->match->path_for($target, @_);
$path->parse($generated->{path}) if $generated->{path};
$base->scheme($base->protocol eq 'https' ? 'wss' : 'ws')
if $generated->{websocket};
}
# Make path absolute
my $base_path = $base->path;
unshift @{$path->parts}, @{$base_path->parts};
$base_path->parts([])->trailing_slash(0);
return $url;
}
sub validation {
my $self = shift;
my $stash = $self->stash;
return $stash->{'mojo.validation'} if $stash->{'mojo.validation'};
my $req = $self->req;
my $token = $self->session->{csrf_token};
my $header = $req->headers->header('X-CSRF-Token');
my $hash = $req->params->to_hash;
$hash->{csrf_token} //= $header if $token && $header;
my $validation = $self->app->validator->validation->input($hash);
return $stash->{'mojo.validation'} = $validation->csrf_token($token);
}
sub write {
my ($self, $chunk, $cb) = @_;
($cb, $chunk) = ($chunk, undef) if ref $chunk eq 'CODE';
$self->res->content->write($chunk, $cb ? sub { shift; $self->$cb(@_) } : ());
return $self->rendered;
}
sub write_chunk {
my ($self, $chunk, $cb) = @_;
($cb, $chunk) = ($chunk, undef) if ref $chunk eq 'CODE';
my $content = $self->res->content;
$content->write_chunk($chunk, $cb ? sub { shift; $self->$cb(@_) } : ());
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
=head1 NAME
Mojolicious::Controller - Controller base class
=head1 SYNOPSIS
# Controller
package MyApp::Controller::Foo;
use Mojo::Base 'Mojolicious::Controller';
# Action
sub bar {
my $self = shift;
my $name = $self->param('name');
$self->res->headers->cache_control('max-age=1, no-cache');
$self->render(json => {hello => $name});
}
=head1 DESCRIPTION
L<Mojolicious::Controller> is the base class for your L<Mojolicious>
controllers. It is also the default controller class unless you set
L<Mojolicious/"controller_class">.
=head1 ATTRIBUTES
L<Mojolicious::Controller> inherits all attributes from L<Mojo::Base> and
implements the following new ones.
=head2 app
my $app = $c->app;
$c = $c->app(Mojolicious->new);
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!');
# Generate path
my $path = $c->app->home->rel_file('templates/foo/bar.html.ep');
=head2 match
my $m = $c->match;
$c = $c->match(Mojolicious::Routes::Match->new);
Router results for the current request, defaults to a
L<Mojolicious::Routes::Match> object.
# Introspect
my $controller = $c->match->endpoint->pattern->defaults->{controller};
my $action = $c->match->stack->[-1]{action};
=head2 tx
my $tx = $c->tx;
$c = $c->tx(Mojo::Transaction::HTTP->new);
The transaction that is currently being processed, usually a
L<Mojo::Transaction::HTTP> or L<Mojo::Transaction::WebSocket> object. Note
that this reference is usually weakened, so the object needs to be referenced
elsewhere as well when you're performing non-blocking operations and the
underlying connection might get closed early.
# Check peer information
my $address = $c->tx->remote_address;
my $port = $c->tx->remote_port;
# 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.');
});
=head1 METHODS
L<Mojolicious::Controller> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 continue
$c->continue;
Continue dispatch chain with L<Mojolicious::Routes/"continue">.
=head2 cookie
my $value = $c->cookie('foo');
my ($foo, $bar) = $c->cookie(['foo', 'bar']);
$c = $c->cookie(foo => 'bar');
$c = $c->cookie(foo => 'bar', {path => '/'});
Access request cookie values and create new response cookies. If there are
multiple values sharing the same name, and you want to access more than just
the last one, you can use L</"every_cookie">.
# Create response cookie with domain and expiration date
$c->cookie(user => 'sri', {domain => 'example.com', expires => time + 60});
=head2 every_cookie
my $values = $c->every_cookie('foo');
Similar to L</"cookie">, but returns all request cookie values sharing the
same name as an array reference.
$ Get first cookie value
my $first = $c->every_cookie('foo')->[0];
=head2 every_param
my $values = $c->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an
array reference.
# Get first value
my $first = $c->every_param('foo')->[0];
=head2 every_signed_cookie
my $values = $c->every_signed_cookie('foo');
Similar to L</"signed_cookie">, but returns all signed request cookie values
sharing the same name as an array reference.
# Get first signed cookie value
my $first = $c->every_signed_cookie('foo')->[0];
=head2 finish
$c = $c->finish;
$c = $c->finish(1000);
$c = $c->finish(1003 => 'Cannot accept data!');
$c = $c->finish('Bye!');
Close WebSocket connection or long poll stream gracefully.
=head2 flash
my $foo = $c->flash('foo');
$c = $c->flash({foo => 'bar'});
$c = $c->flash(foo => 'bar');
Data storage persistent only for the next request, stored in the
L</"session">.
# Show message after redirect
$c->flash(message => 'User created successfully!');
$c->redirect_to('show_user', id => 23);
=head2 helpers
my $helpers = $c->helpers;
Return a proxy object containing the current controller object and on which
helpers provided by L</"app"> can be called. This includes all helpers from
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!');
=head2 on
my $cb = $c->on(finish => sub {...});
Subscribe to events of L</"tx">, which is usually a L<Mojo::Transaction::HTTP>
or L<Mojo::Transaction::WebSocket> object. Note that this method will
automatically respond to WebSocket handshake requests with a C<101> response
status.
# Do something after the transaction has been finished
$c->on(finish => sub {
my $c = shift;
$c->app->log->debug('We are done!');
});
# Receive WebSocket message
$c->on(message => sub {
my ($c, $msg) = @_;
$c->app->log->debug("Message: $msg");
});
# Receive JSON object via WebSocket message
$c->on(json => sub {
my ($c, $hash) = @_;
$c->app->log->debug("Test: $hash->{test}");
});
# Receive WebSocket "Binary" message
$c->on(binary => sub {
my ($c, $bytes) = @_;
my $len = length $bytes;
$c->app->log->debug("Received $len bytes.");
});
=head2 param
my @names = $c->param;
my $value = $c->param('foo');
my ($foo, $bar) = $c->param(['foo', 'bar']);
$c = $c->param(foo => 'ba;r');
$c = $c->param(foo => qw(ba;r baz));
$c = $c->param(foo => ['ba;r', 'baz']);
Access route placeholder values that are not reserved stash values, file
uploads as well as C<GET> and C<POST> parameters extracted from the query
string and C<application/x-www-form-urlencoded> or C<multipart/form-data>
message body, in that order. If there are multiple values sharing the same
name, and you want to access more than just the last one, you can use
L</"every_param">. Parts of the request body need to be loaded into memory to
parse C<POST> parameters, so you have to make sure it is not excessively
large, there's a 10MB limit by default.
# Get first value
my $first = $c->every_param('foo')->[0];
For more control you can also access request information directly.
# Only GET parameters
my $foo = $c->req->url->query->param('foo');
# Only POST parameters
my $foo = $c->req->body_params->param('foo');
# Only GET and POST parameters
my $foo = $c->req->param('foo');
# Only file uploads
my $foo = $c->req->upload('foo');
=head2 redirect_to
$c = $c->redirect_to('named', foo => 'bar');
$c = $c->redirect_to('named', {foo => 'bar'});
$c = $c->redirect_to('/perldoc');
$c = $c->redirect_to('http://mojolicio.us/perldoc');
Prepare a C<302> redirect response, takes the same arguments as L</"url_for">.
# Moved permanently
$c->res->code(301);
$c->redirect_to('some_route');
=head2 render
my $bool = $c->render;
my $bool = $c->render(controller => 'foo', action => 'bar');
my $bool = $c->render(template => 'foo/index');
my $bool = $c->render(template => 'index', format => 'html');
my $bool = $c->render(data => $bytes);
my $bool = $c->render(text => 'Hello!');
my $bool = $c->render(json => {foo => 'bar'});
my $bool = $c->render(handler => 'something');
my $bool = $c->render('foo/index');
Render content with L<Mojolicious::Renderer/"render"> and emit hooks
L<Mojolicious/"before_render"> as well as L<Mojolicious/"after_render">. If no
template is provided a default one based on controller and action or route
name will be generated with L<Mojolicious::Renderer/"template_for">, all
additional pairs get merged into the L</"stash">.
# Render characters
$c->render(text => 'I ♥ Mojolicious!');
# Render binary data
use Mojo::JSON 'encode_json';
$c->render(data => encode_json({test => 'I ♥ Mojolicious!'}));
# Render JSON
$c->render(json => {test => 'I ♥ Mojolicious!'});
# 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.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;
Disable automatic rendering to delay response generation, only necessary if
automatic rendering would result in a response.
# Delayed rendering
$c->render_later;
Mojo::IOLoop->timer(2 => sub {
$c->render(text => 'Delayed by 2 seconds!');
});
=head2 render_maybe
my $bool = $c->render_maybe;
my $bool = $c->render_maybe(controller => 'foo', action => 'bar');
my $bool = $c->render_maybe('foo/index', format => 'html');
Try to render content, but do not call
L<Mojolicious::Plugin::DefaultHelpers/"reply-E<gt>not_found"> if no response
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');
Try to render content and return it wrapped in a L<Mojo::ByteStream> object or
return C<undef>, all arguments get localized automatically and are only
available during this render operation, takes the same arguments as
L</"render">.
=head2 rendered
$c = $c->rendered;
$c = $c->rendered(302);
Finalize response and emit hook L<Mojolicious/"after_dispatch">, defaults to
using a C<200> response code.
# Custom response
$c->res->headers->content_type('text/plain');
$c->res->body('Hello World!');
$c->rendered(200);
# Accept WebSocket handshake without subscribing to an event
$c->rendered(101);
=head2 req
my $req = $c->req;
Get L<Mojo::Message::Request> object from L</"tx">.
# Longer version
my $req = $c->tx->req;
# Extract request information
my $url = $c->req->url->to_abs;
my $info = $c->req->url->to_abs->userinfo;
my $host = $c->req->url->to_abs->host;
my $agent = $c->req->headers->user_agent;
my $bytes = $c->req->body;
my $str = $c->req->text;
my $hash = $c->req->params->to_hash;
my $value = $c->req->json;
my $foo = $c->req->json('/23/foo');
my $dom = $c->req->dom;
my $bar = $c->req->dom('div.bar')->first->text;
=head2 res
my $res = $c->res;
Get L<Mojo::Message::Response> object from L</"tx">.
# Longer version
my $res = $c->tx->res;
# Force file download by setting a custom response header
$c->res->headers->content_disposition('attachment; filename=foo.png;');
=head2 respond_to
$c = $c->respond_to(
json => {json => {message => 'Welcome!'}},
html => {template => 'welcome'},
any => sub {...}
);
Automatically select best possible representation for resource from C<Accept>
request header, C<format> stash value or C<format> C<GET>/C<POST> parameter,
defaults to rendering an empty C<204> response. Since browsers often don't
really know what they actually want, unspecific C<Accept> request headers with
more than one MIME type will be ignored, unless the C<X-Requested-With> header
is set to the value C<XMLHttpRequest>.
$c->respond_to(
json => sub { $c->render(json => {just => 'works'}) },
xml => {text => '<just>works</just>'},
any => {data => '', status => 204}
);
For more advanced negotiation logic you can also use the helper
L<Mojolicious::Plugin::DefaultHelpers/"accepts">.
=head2 send
$c = $c->send({binary => $bytes});
$c = $c->send({text => $bytes});
$c = $c->send({json => {test => [1, 2, 3]}});
$c = $c->send([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
$c = $c->send($chars);
$c = $c->send($chars => sub {...});
Send message or frame non-blocking via WebSocket, the optional drain callback
will be invoked once all data has been written. Note that this method will
automatically respond to WebSocket handshake requests with a C<101> response
status.
# Send "Text" message
$c->send('I ♥ Mojolicious!');
# Send JSON object as "Text" message
$c->send({json => {test => 'I ♥ Mojolicious!'}});
# Send JSON object as "Binary" message
use Mojo::JSON 'encode_json';
$c->send({binary => encode_json({test => 'I ♥ Mojolicious!'})});
# Send "Ping" frame
$c->send([1, 0, 0, 0, 9, 'Hello World!']);
# Make sure previous message has been written before continuing
$c->send('First message!' => sub {
my $c = shift;
$c->send('Second message!');
});
For mostly idle WebSockets you might also want to increase the inactivity
timeout with L<Mojolicious::Plugin::DefaultHelpers/"inactivity_timeout">,
which usually defaults to C<15> seconds.
# Increase inactivity timeout for connection to 300 seconds
$c->inactivity_timeout(300);
=head2 session
my $session = $c->session;
my $foo = $c->session('foo');
$c = $c->session({foo => 'bar'});
$c = $c->session(foo => 'bar');
Persistent data storage for the next few requests, all session data gets
serialized with L<Mojo::JSON> and stored Base64 encoded in HMAC-SHA1 signed
cookies. Note that cookies usually have a C<4096> byte (4KB) limit, depending
on browser.
# Manipulate session
$c->session->{foo} = 'bar';
my $foo = $c->session->{foo};
delete $c->session->{foo};
# Expiration date in seconds from now (persists between requests)
$c->session(expiration => 604800);
# Expiration date as absolute epoch time (only valid for one request)
$c->session(expires => time + 604800);
# Delete whole session by setting an expiration date in the past
$c->session(expires => 1);
=head2 signed_cookie
my $value = $c->signed_cookie('foo');
my ($foo, $bar) = $c->signed_cookie(['foo', 'bar']);
$c = $c->signed_cookie(foo => 'bar');
$c = $c->signed_cookie(foo => 'bar', {path => '/'});
Access signed request cookie values and create new signed response cookies. If
there are multiple values sharing the same name, and you want to access more
than just the last one, you can use L</"every_signed_cookie">. Cookies failing
HMAC-SHA1 signature verification will be automatically discarded.
=head2 stash
my $hash = $c->stash;
my $foo = $c->stash('foo');
$c = $c->stash({foo => 'bar'});
$c = $c->stash(foo => 'bar');
Non-persistent data storage and exchange for the current request, application
wide default values can be set with L<Mojolicious/"defaults">. Some stash
values have a special meaning and are reserved, the full list is currently
C<action>, C<app>, C<cb>, C<controller>, C<data>, C<extends>, C<format>,
C<handler>, C<json>, C<layout>, C<namespace>, C<path>, C<status>, C<template>,
C<text> and C<variant>. Note that all stash values with a C<mojo.*> prefix are
reserved for internal use.
# Remove value
my $foo = delete $c->stash->{foo};
=head2 url_for
my $url = $c->url_for;
my $url = $c->url_for(name => 'sebastian');
my $url = $c->url_for({name => 'sebastian'});
my $url = $c->url_for('test', name => 'sebastian');
my $url = $c->url_for('test', {name => 'sebastian'});
my $url = $c->url_for('/perldoc');
my $url = $c->url_for('//mojolicio.us/perldoc');
my $url = $c->url_for('http://mojolicio.us/perldoc');
my $url = $c->url_for('mailto:sri@example.com');
Generate a portable L<Mojo::URL> object with base for a path, URL or route.
# "http://127.0.0.1:3000/perldoc" if application has been started with Morbo
$c->url_for('/perldoc')->to_abs;
# "/perldoc?foo=bar" if application is deployed under "/"
$c->url_for('/perldoc')->query(foo => 'bar');
# "/myapp/perldoc?foo=bar" if application is deployed under "/myapp"
$c->url_for('/perldoc')->query(foo => 'bar');
You can also use the helper L<Mojolicious::Plugin::DefaultHelpers/"url_with">
to inherit query parameters from the current request.
# "/list?q=mojo&page=2" if current request was for "/list?q=mojo&page=1"
$c->url_with->query([page => 2]);
=head2 validation
my $validation = $c->validation;
Get L<Mojolicious::Validator::Validation> object for current request to
validate C<GET> and C<POST> parameters extracted from the query string and
C<application/x-www-form-urlencoded> or C<multipart/form-data> message body.
Parts of the request body need to be loaded into memory to parse C<POST>
parameters, so you have to make sure it is not excessively large, there's a
10MB limit by default.
my $validation = $c->validation;
$validation->required('title')->size(3, 50);
my $title = $validation->param('title');
=head2 write
$c = $c->write;
$c = $c->write($bytes);
$c = $c->write(sub {...});
$c = $c->write($bytes => sub {...});
Write dynamic content non-blocking, the optional drain callback will be
invoked once all data has been written.
# Keep connection alive (with Content-Length header)
$c->res->headers->content_length(6);
$c->write('Hel' => sub {
my $c = shift;
$c->write('lo!')
});
# Close connection when finished (without Content-Length header)
$c->write('Hel' => sub {
my $c = shift;
$c->write('lo!' => sub {
my $c = shift;
$c->finish;
});
});
For Comet (long polling) you might also want to increase the inactivity
timeout with L<Mojolicious::Plugin::DefaultHelpers/"inactivity_timeout">,
which usually defaults to C<15> seconds.
# Increase inactivity timeout for connection to 300 seconds
$c->inactivity_timeout(300);
=head2 write_chunk
$c = $c->write_chunk;
$c = $c->write_chunk($bytes);
$c = $c->write_chunk(sub {...});
$c = $c->write_chunk($bytes => sub {...});
Write dynamic content non-blocking with C<chunked> transfer encoding, the
optional drain callback will be invoked once all data has been written.
# Make sure previous chunk has been written before continuing
$c->write_chunk('He' => sub {
my $c = shift;
$c->write_chunk('ll' => sub {
my $c = shift;
$c->finish('o!');
});
});
You can call L</"finish"> at any time to end the stream.
2
He
2
ll
2
o!
0
=head1 AUTOLOAD
In addition to the L</"ATTRIBUTES"> and L</"METHODS"> above you can also call
helpers provided by L</"app"> on L<Mojolicious::Controller> objects. This
includes all helpers from L<Mojolicious::Plugin::DefaultHelpers> and
L<Mojolicious::Plugin::TagHelpers>.
# Call helpers
$c->layout('green');
$c->title('Welcome!');
# Longer version
$c->helpers->layout('green');
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_CONTROLLER
$fatpacked{"Mojolicious/Lite.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_LITE';
package Mojolicious::Lite;
use Mojo::Base 'Mojolicious';
# "Bender: Bite my shiny metal ass!"
use File::Basename qw(basename dirname);
use File::Spec::Functions 'catdir';
use Mojo::UserAgent::Server;
use Mojo::Util 'monkey_patch';
sub import {
# Remember executable for later
$ENV{MOJO_EXE} ||= (caller)[1];
# Reuse home directory if possible
local $ENV{MOJO_HOME} = catdir(split '/', dirname $ENV{MOJO_EXE})
unless $ENV{MOJO_HOME};
# Initialize application class
my $caller = caller;
no strict 'refs';
push @{"${caller}::ISA"}, 'Mojo';
# Generate moniker based on filename
my $moniker = basename $ENV{MOJO_EXE};
$moniker =~ s/\.(?:pl|pm|t)$//i;
my $app = shift->new(moniker => $moniker);
# Initialize routes without namespaces
my $routes = $app->routes->namespaces([]);
$app->static->classes->[0] = $app->renderer->classes->[0] = $caller;
# The Mojolicious::Lite DSL
my $root = $routes;
for my $name (qw(any get options patch post put websocket)) {
monkey_patch $caller, $name, sub { $routes->$name(@_) };
}
monkey_patch $caller, $_, sub {$app}
for qw(new app);
monkey_patch $caller, del => sub { $routes->delete(@_) };
monkey_patch $caller, group => sub (&) {
(my $old, $root) = ($root, $routes);
shift->();
($routes, $root) = ($root, $old);
};
monkey_patch $caller,
helper => sub { $app->helper(@_) },
hook => sub { $app->hook(@_) },
plugin => sub { $app->plugin(@_) },
under => sub { $routes = $root->under(@_) };
# Make sure there's a default application for testing
Mojo::UserAgent::Server->app($app) unless Mojo::UserAgent::Server->app;
# Lite apps are strict!
Mojo::Base->import(-strict);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Lite - Real-time micro web framework
=head1 SYNOPSIS
# Automatically enables "strict", "warnings", "utf8" and Perl 5.10 features
use Mojolicious::Lite;
# Route with placeholder
get '/:foo' => sub {
my $c = shift;
my $foo = $c->param('foo');
$c->render(text => "Hello from $foo.");
};
# Start the Mojolicious command system
app->start;
=head1 DESCRIPTION
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!
=head1 FUNCTIONS
L<Mojolicious::Lite> implements the following functions, which are
automatically exported.
=head2 any
my $route = any '/:foo' => sub {...};
my $route = any '/:foo' => {foo => 'bar'} => sub {...};
my $route = any '/:foo' => [foo => qr/\w+/] => sub {...};
my $route = any [qw(GET POST)] => '/:foo' => sub {...};
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.
=head2 app
my $app = app;
Returns the L<Mojolicious::Lite> application object, which is a subclass of
L<Mojolicious>.
# Use all the available attributes and methods
app->log->level('error');
app->defaults(foo => 'bar');
=head2 del
my $route = del '/:foo' => sub {...};
my $route = del '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 get
my $route = get '/:foo' => sub {...};
my $route = get '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 group
group {...};
Start a new route group.
=head2 helper
helper foo => sub {...};
Add a new helper with L<Mojolicious/"helper">.
=head2 hook
hook after_dispatch => sub {...};
Share code with L<Mojolicious/"hook">.
=head2 options
my $route = options '/:foo' => sub {...};
my $route = options '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 patch
my $route = patch '/:foo' => sub {...};
my $route = patch '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 plugin
plugin SomePlugin => {foo => 23};
Load a plugin with L<Mojolicious/"plugin">.
=head2 post
my $route = post '/:foo' => sub {...};
my $route = post '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 put
my $route = put '/:foo' => sub {...};
my $route = put '/:foo' => {foo => 'bar'} => sub {...};
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.
=head2 under
my $route = under sub {...};
my $route = under '/:foo' => sub {...};
my $route = under '/:foo' => {foo => 'bar'};
my $route = under '/:foo' => [foo => qr/\w+/];
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.
=head2 websocket
my $route = websocket '/:foo' => sub {...};
my $route = websocket '/:foo' => {foo => 'bar'} => sub {...};
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.
=head1 ATTRIBUTES
L<Mojolicious::Lite> inherits all attributes from L<Mojolicious>.
=head1 METHODS
L<Mojolicious::Lite> inherits all methods from L<Mojolicious>.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_LITE
$fatpacked{"Mojolicious/Plugin.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN';
package Mojolicious::Plugin;
use Mojo::Base -base;
use Carp 'croak';
sub register { croak 'Method "register" not implemented by subclass' }
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin - Plugin base class
=head1 SYNOPSIS
# CamelCase plugin name
package Mojolicious::Plugin::MyPlugin;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app, $conf) = @_;
# Magic here! :)
}
=head1 DESCRIPTION
L<Mojolicious::Plugin> is an abstract base class for L<Mojolicious> plugins.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 METHODS
L<Mojolicious::Plugin> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
$plugin->register(Mojolicious->new, {foo => 'bar'});
This method will be called by L<Mojolicious::Plugins> at startup time. Meant
to be overloaded in a subclass.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN
$fatpacked{"Mojolicious/Plugin/Charset.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_CHARSET';
package Mojolicious::Plugin::Charset;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app, $conf) = @_;
return unless my $c = $conf->{charset};
$app->types->type(html => "text/html;charset=$c");
$app->renderer->encoding($c);
$app->hook(before_dispatch =>
sub { shift->req->default_charset($c)->url->query->charset($c) });
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::Charset - Charset plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin(Charset => {charset => 'Shift_JIS'});
# Mojolicious::Lite
plugin Charset => {charset => 'Shift_JIS'};
=head1 DESCRIPTION
L<Mojolicious::Plugin::Charset> is a plugin to easily set the default charset
and encoding on all layers of L<Mojolicious>.
The code of this plugin is a good example for learning to build new plugins,
you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 OPTIONS
L<Mojolicious::Plugin::Charset> supports the following options.
=head2 charset
# Mojolicious::Lite
plugin Charset => {charset => 'Shift_JIS'};
Application charset.
=head1 METHODS
L<Mojolicious::Plugin::Charset> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new, {charset => 'Shift_JIS'});
Register hook L<Mojolicious/"before_dispatch"> in application and change a few
defaults.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_CHARSET
$fatpacked{"Mojolicious/Plugin/Config.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_CONFIG';
package Mojolicious::Plugin::Config;
use Mojo::Base 'Mojolicious::Plugin';
use File::Spec::Functions 'file_name_is_absolute';
use Mojo::Util qw(decode slurp);
sub load {
my ($self, $file, $conf, $app) = @_;
$app->log->debug(qq{Reading configuration file "$file".});
return $self->parse(decode('UTF-8', slurp $file), $file, $conf, $app);
}
sub parse {
my ($self, $content, $file, $conf, $app) = @_;
# Run Perl code
my $config
= eval 'package Mojolicious::Plugin::Config::Sandbox; no warnings;'
. "sub app; local *app = sub { \$app }; use Mojo::Base -strict; $content";
die qq{Can't load configuration from file "$file": $@} if !$config && $@;
die qq{Configuration file "$file" did not return a hash reference.\n}
unless ref $config eq 'HASH';
return $config;
}
sub register {
my ($self, $app, $conf) = @_;
# Config file
my $file = $conf->{file} || $ENV{MOJO_CONFIG};
$file ||= $app->moniker . '.' . ($conf->{ext} || 'conf');
# Mode specific config file
my $mode = $file =~ /^(.*)\.([^.]+)$/ ? join('.', $1, $app->mode, $2) : '';
my $home = $app->home;
$file = $home->rel_file($file) unless file_name_is_absolute $file;
$mode = $home->rel_file($mode) if $mode && !file_name_is_absolute $mode;
$mode = undef unless $mode && -e $mode;
# Read config file
my $config = {};
if (-e $file) { $config = $self->load($file, $conf, $app) }
# Check for default and mode specific config file
elsif (!$conf->{default} && !$mode) {
die qq{Configuration file "$file" missing, maybe you need to create it?\n};
}
# Merge everything
$config = {%$config, %{$self->load($mode, $conf, $app)}} if $mode;
$config = {%{$conf->{default}}, %$config} if $conf->{default};
return $app->defaults(config => $app->config)->config($config)->config;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::Config - Perl-ish configuration plugin
=head1 SYNOPSIS
# myapp.conf (it's just Perl returning a hash)
{
foo => "bar",
music_dir => app->home->rel_dir('music')
};
# Mojolicious
my $config = $self->plugin('Config');
say $config->{foo};
# Mojolicious::Lite
my $config = plugin 'Config';
say $config->{foo};
# foo.html.ep
%= $config->{foo}
# The configuration is available application wide
my $config = app->config;
say $config->{foo};
# Everything can be customized with options
my $config = plugin Config => {file => '/etc/myapp.stuff'};
=head1 DESCRIPTION
L<Mojolicious::Plugin::Config> is a Perl-ish configuration plugin.
The application object can be accessed via C<$app> or the C<app> function,
L<strict>, L<warnings>, L<utf8> and Perl 5.10 features are automatically
enabled. You can extend the normal configuration file C<$moniker.conf> with
C<mode> specific ones like C<$moniker.$mode.conf>. A default configuration
filename will be generated from the value of L<Mojolicious/"moniker">.
The code of this plugin is a good example for learning to build new plugins,
you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 OPTIONS
L<Mojolicious::Plugin::Config> supports the following options.
=head2 default
# Mojolicious::Lite
plugin Config => {default => {foo => 'bar'}};
Default configuration, making configuration files optional.
=head2 ext
# Mojolicious::Lite
plugin Config => {ext => 'stuff'};
File extension for generated configuration filenames, defaults to C<conf>.
=head2 file
# Mojolicious::Lite
plugin Config => {file => 'myapp.conf'};
plugin Config => {file => '/etc/foo.stuff'};
Full path to configuration file, defaults to the value of the C<MOJO_CONFIG>
environment variable or C<$moniker.conf> in the application home directory.
=head1 METHODS
L<Mojolicious::Plugin::Config> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 load
$plugin->load($file, $conf, $app);
Loads configuration file and passes the content to L</"parse">.
sub load {
my ($self, $file, $conf, $app) = @_;
...
return $self->parse($content, $file, $conf, $app);
}
=head2 parse
$plugin->parse($content, $file, $conf, $app);
Parse configuration file.
sub parse {
my ($self, $content, $file, $conf, $app) = @_;
...
return $hash;
}
=head2 register
my $config = $plugin->register(Mojolicious->new);
my $config = $plugin->register(Mojolicious->new, {file => '/etc/app.conf'});
Register plugin in L<Mojolicious> application and merge configuration.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_CONFIG
$fatpacked{"Mojolicious/Plugin/DefaultHelpers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_DEFAULTHELPERS';
package Mojolicious::Plugin::DefaultHelpers;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::ByteStream;
use Mojo::Collection;
use Mojo::Exception;
use Mojo::IOLoop;
use Mojo::Util qw(dumper sha1_sum steady_time);
sub register {
my ($self, $app) = @_;
# Controller alias helpers
for my $name (qw(app flash param stash session url_for validation)) {
$app->helper($name => sub { shift->$name(@_) });
}
# Stash key shortcuts (should not generate log messages)
for my $name (qw(extends layout title)) {
$app->helper($name => sub { shift->stash($name, @_) });
}
$app->helper(accepts => sub { $_[0]->app->renderer->accepts(@_) });
$app->helper(b => sub { shift; Mojo::ByteStream->new(@_) });
$app->helper(c => sub { shift; Mojo::Collection->new(@_) });
$app->helper(config => sub { shift->app->config(@_) });
$app->helper($_ => $self->can("_$_"))
for qw(content content_for csrf_token current_route delay),
qw(inactivity_timeout is_fresh url_with);
$app->helper(dumper => sub { shift; dumper(@_) });
$app->helper(include => sub { shift->render_to_string(@_) });
$app->helper("reply.$_" => $self->can("_$_")) for qw(asset static);
$app->helper('reply.exception' => sub { _development('exception', @_) });
$app->helper('reply.not_found' => sub { _development('not_found', @_) });
$app->helper(ua => sub { shift->app->ua });
}
sub _asset {
my $c = shift;
$c->app->static->serve_asset($c, @_);
$c->rendered;
}
sub _content {
my ($c, $name, $content) = @_;
$name ||= 'content';
# Set (first come)
my $hash = $c->stash->{'mojo.content'} ||= {};
$hash->{$name} //= ref $content eq 'CODE' ? $content->() : $content
if defined $content;
# Get
return Mojo::ByteStream->new($hash->{$name} // '');
}
sub _content_for {
my ($c, $name, $content) = @_;
return _content($c, $name) unless defined $content;
my $hash = $c->stash->{'mojo.content'} ||= {};
return $hash->{$name} .= ref $content eq 'CODE' ? $content->() : $content;
}
sub _csrf_token {
my $c = shift;
$c->session->{csrf_token}
||= sha1_sum($c->app->secrets->[0] . steady_time . rand 999);
}
sub _current_route {
return '' unless my $route = shift->match->endpoint;
return @_ ? $route->name eq shift : $route->name;
}
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;
}
sub _development {
my ($page, $c, $e) = @_;
my $app = $c->app;
$app->log->error($e = Mojo::Exception->new($e)) if $page eq 'exception';
# Filtered stash snapshot
my $stash = $c->stash;
my %snapshot = map { $_ => $stash->{$_} }
grep { !/^mojo\./ and defined $stash->{$_} } keys %$stash;
# Render with fallbacks
my $mode = $app->mode;
my $renderer = $app->renderer;
my $options = {
exception => $page eq 'exception' ? $e : undef,
format => $stash->{format} || $renderer->default_format,
handler => undef,
snapshot => \%snapshot,
status => $page eq 'exception' ? 500 : 404,
template => "$page.$mode"
};
my $inline = $renderer->_bundled($mode eq 'development' ? $mode : $page);
return $c if _fallbacks($c, $options, $page, $inline);
_fallbacks($c, {%$options, format => 'html'}, $page, $inline);
return $c;
}
sub _fallbacks {
my ($c, $options, $template, $inline) = @_;
# Mode specific template
return 1 if $c->render_maybe(%$options);
# Normal template
return 1 if $c->render_maybe(%$options, template => $template);
# Inline template
my $stash = $c->stash;
return undef unless $stash->{format} eq 'html';
delete @$stash{qw(extends layout)};
return $c->render_maybe(%$options, inline => $inline, handler => 'ep');
}
sub _inactivity_timeout {
return unless my $stream = Mojo::IOLoop->stream(shift->tx->connection // '');
$stream->timeout(shift);
}
sub _is_fresh {
my ($c, %options) = @_;
return $c->app->static->is_fresh($c, \%options);
}
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;
}
sub _url_with {
my $c = shift;
return $c->url_for(@_)->query($c->req->url->query->clone);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::DefaultHelpers - Default helpers plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin('DefaultHelpers');
# Mojolicious::Lite
plugin 'DefaultHelpers';
=head1 DESCRIPTION
L<Mojolicious::Plugin::DefaultHelpers> is a collection of helpers for
L<Mojolicious>.
This is a core plugin, that means it is always enabled and its code a good
example for learning to build new plugins, you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 HELPERS
L<Mojolicious::Plugin::DefaultHelpers> implements the following helpers.
=head2 accepts
my $formats = $c->accepts;
my $format = $c->accepts('html', 'json', 'txt');
Select best possible representation for resource from C<Accept> request
header, C<format> stash value or C<format> C<GET>/C<POST> parameter with
L<Mojolicious::Renderer/"accepts">, defaults to returning the first extension
if no preference could be detected.
# Check if JSON is acceptable
$c->render(json => {hello => 'world'}) if $c->accepts('json');
# Check if JSON was specifically requested
$c->render(json => {hello => 'world'}) if $c->accepts('', 'json');
# Unsupported representation
$c->render(data => '', status => 204)
unless my $format = $c->accepts('html', 'json');
# Detected representations to select from
my @formats = @{$c->accepts};
=head2 app
%= app->secrets->[0]
Alias for L<Mojolicious::Controller/"app">.
=head2 b
%= b('test 123')->b64_encode
Turn string into a L<Mojo::ByteStream> object.
=head2 c
%= c(qw(a b c))->shuffle->join
Turn list into a L<Mojo::Collection> object.
=head2 config
%= config 'something'
Alias for L<Mojo/"config">.
=head2 content
%= content foo => begin
test
% end
%= content bar => 'Hello World!'
%= content 'foo'
%= content 'bar'
%= content
Store partial rendered content in named buffer and retrieve it, defaults to
retrieving the named buffer C<content>, which is commonly used for the
renderers C<layout> and C<extends> features. Note that new content will be
ignored if the named buffer is already in use.
=head2 content_for
% content_for foo => begin
test
% end
%= content_for 'foo'
Append partial rendered content to named buffer and retrieve it. Note that
named buffers are shared with the L</"content"> helper.
% content_for message => begin
Hello
% end
% content_for message => begin
world!
% end
%= content_for 'message'
=head2 csrf_token
%= csrf_token
Get CSRF token from L</"session">, and generate one if none exists.
=head2 current_route
% if (current_route 'login') {
Welcome to Mojolicious!
% }
%= current_route
Check or get name of current route.
=head2 delay
$c->delay(sub {...}, sub {...});
Disable automatic rendering and use L<Mojo::IOLoop/"delay"> to manage
callbacks and control the flow of events, which can help you avoid deep nested
closures and memory leaks that often result from continuation-passing style.
Also keeps a reference to L<Mojolicious::Controller/"tx"> in case the
underlying connection gets closed early, and calls L</"reply-E<gt>exception">
if an exception gets thrown in one of the steps, breaking the chain.
# Longer version
$c->render_later;
my $tx = $c->tx;
my $delay = Mojo::IOLoop->delay(sub {...}, sub {...});
$delay->catch(sub { $c->reply->exception(pop) and undef $tx })->wait;
# Non-blocking request
$c->delay(
sub {
my $delay = shift;
$c->ua->get('http://mojolicio.us' => $delay->begin);
},
sub {
my ($delay, $tx) = @_;
$c->render(json => {title => $tx->res->dom->at('title')->text});
}
);
=head2 dumper
%= dumper {some => 'data'}
Dump a Perl data structure with L<Mojo::Util/"dumper">.
=head2 extends
% extends 'blue';
% extends 'blue', title => 'Blue!';
Set C<extends> stash value, all additional pairs get merged into the
L</"stash">.
=head2 flash
%= flash 'foo'
Alias for L<Mojolicious::Controller/"flash">.
=head2 inactivity_timeout
$c->inactivity_timeout(3600);
Use L<Mojo::IOLoop/"stream"> to find the current connection and increase
timeout if possible.
# Longer version
Mojo::IOLoop->stream($c->tx->connection)->timeout(3600);
=head2 include
%= include 'menubar'
%= include 'menubar', format => 'txt'
Alias for C<Mojolicious::Controller/"render_to_string">.
=head2 is_fresh
my $bool = $c->is_fresh;
my $bool = $c->is_fresh(etag => 'abc');
my $bool = $c->is_fresh(last_modified => $epoch);
Check freshness of request by comparing the C<If-None-Match> and
C<If-Modified-Since> request headers to the C<ETag> and C<Last-Modified>
response headers with L<Mojolicious::Static/"is_fresh">.
# Add ETag header and check freshness before rendering
$c->is_fresh(etag => 'abc')
? $c->rendered(304)
: $c->render(text => 'I ♥ Mojolicious!');
=head2 layout
% layout 'green';
% layout 'green', title => 'Green!';
Set C<layout> stash value, all additional pairs get merged into the
L</"stash">.
=head2 param
%= param 'foo'
Alias for L<Mojolicious::Controller/"param">.
=head2 reply->asset
$c->reply->asset(Mojo::Asset::File->new);
Reply with a L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object using
L<Mojolicious::Static/"serve_asset">, and perform content negotiation with
C<Range>, C<If-Modified-Since> and C<If-None-Match> headers.
# Serve asset with custom modification time
my $asset = Mojo::Asset::Memory->new;
$asset->add_chunk('Hello World!')->mtime(784111777);
$c->res->headers->content_type('text/plain');
$c->reply->asset($asset);
=head2 reply->exception
$c = $c->reply->exception('Oops!');
$c = $c->reply->exception(Mojo::Exception->new('Oops!'));
Render the exception template C<exception.$mode.$format.*> or
C<exception.$format.*> and set the response status code to C<500>. Also sets
the stash values C<exception> to a L<Mojo::Exception> object and C<snapshot>
to a copy of the L</"stash"> for use in the templates.
=head2 reply->not_found
$c = $c->reply->not_found;
Render the not found template C<not_found.$mode.$format.*> or
C<not_found.$format.*> and set the response status code to C<404>. Also sets
the stash value C<snapshot> to a copy of the L</"stash"> for use in the
templates.
=head2 reply->static
my $bool = $c->reply->static('images/logo.png');
my $bool = $c->reply->static('../lib/MyApp.pm');
Reply with a static file using L<Mojolicious::Static/"serve">, usually from
the C<public> directories or C<DATA> sections of your application. Note that
this helper does not protect from traversing to parent directories.
# Serve file with a custom content type
$c->res->headers->content_type('application/myapp');
$c->reply->static('foo.txt');
=head2 session
%= session 'foo'
Alias for L<Mojolicious::Controller/"session">.
=head2 stash
%= stash 'foo'
% stash foo => 'bar';
Alias for L<Mojolicious::Controller/"stash">.
%= stash('name') // 'Somebody'
=head2 title
%= title
% title 'Welcome!';
% title 'Welcome!', foo => 'bar';
Get of set C<title> stash value, all additional pairs get merged into the
L</"stash">.
=head2 ua
%= ua->get('mojolicio.us')->res->dom->at('title')->text
Alias for L<Mojo/"ua">.
=head2 url_for
%= url_for 'named', controller => 'bar', action => 'baz'
Alias for L<Mojolicious::Controller/"url_for">.
=head2 url_with
%= url_with 'named', controller => 'bar', action => 'baz'
Does the same as L</"url_for">, but inherits query parameters from the current
request.
%= url_with->query([page => 2])
=head2 validation
%= validation->param('foo')
Alias for L<Mojolicious::Controller/"validation">.
=head1 METHODS
L<Mojolicious::Plugin::DefaultHelpers> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
Register helpers in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_DEFAULTHELPERS
$fatpacked{"Mojolicious/Plugin/EPLRenderer.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_EPLRENDERER';
package Mojolicious::Plugin::EPLRenderer;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Template;
use Mojo::Util qw(encode md5_sum);
sub register { $_[1]->renderer->add_handler(epl => \&_epl) }
sub _epl {
my ($renderer, $c, $output, $options) = @_;
# Cached
my $mt = delete $options->{'mojo.template'} || Mojo::Template->new;
my $log = $c->app->log;
if ($mt->compiled) {
$log->debug("Rendering cached @{[$mt->name]}.");
$$output = $mt->interpret($c);
}
# Not cached
else {
my $inline = $options->{inline};
my $name = defined $inline ? md5_sum encode('UTF-8', $inline) : undef;
return undef unless defined($name //= $renderer->template_name($options));
# Inline
if (defined $inline) {
$log->debug(qq{Rendering inline template "$name".});
$$output = $mt->name(qq{inline template "$name"})->render($inline, $c);
}
# File
else {
if (my $encoding = $renderer->encoding) { $mt->encoding($encoding) }
# Try template
if (defined(my $path = $renderer->template_path($options))) {
$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.});
$$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 }
}
}
# Exception or success
return ref $$output ? die $$output : 1;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::EPLRenderer - Embedded Perl Lite renderer plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin('EPLRenderer');
# Mojolicious::Lite
plugin 'EPLRenderer';
=head1 DESCRIPTION
L<Mojolicious::Plugin::EPLRenderer> is a renderer for C<epl> templates, which
are pretty much just raw L<Mojo::Template>.
This is a core plugin, that means it is always enabled and its code a good
example for learning to build new plugins, you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 METHODS
L<Mojolicious::Plugin::EPLRenderer> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
Register renderer in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_EPLRENDERER
$fatpacked{"Mojolicious/Plugin/EPRenderer.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_EPRENDERER';
package Mojolicious::Plugin::EPRenderer;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Template;
use Mojo::Util qw(encode md5_sum monkey_patch);
sub DESTROY { Mojo::Util::_teardown(shift->{namespace}) }
sub register {
my ($self, $app, $conf) = @_;
# Auto escape by default to prevent XSS attacks
my $template = {auto_escape => 1, %{$conf->{template} || {}}};
my $ns = $self->{namespace} = $template->{namespace}
//= 'Mojo::Template::Sandbox::' . md5_sum "$self";
# Add "ep" handler and make it the default
$app->renderer->default_handler('ep')->add_handler(
$conf->{name} || 'ep' => sub {
my ($renderer, $c, $output, $options) = @_;
my $name = $options->{inline} // $renderer->template_name($options);
return undef unless defined $name;
my @keys = sort grep {/^\w+$/} keys %{$c->stash};
my $key = md5_sum encode 'UTF-8', join(',', $name, @keys);
# Prepare template for "epl" handler
my $cache = $renderer->cache;
unless ($options->{'mojo.template'} = $cache->get($key)) {
my $mt = $options->{'mojo.template'} = Mojo::Template->new($template);
# Helpers (only once)
++$self->{helpers} and _helpers($ns, $renderer->helpers)
unless $self->{helpers};
# Stash values (every time)
my $prepend = 'my $self = my $c = shift; my $_S = $c->stash; {';
$prepend .= join '', map {" my \$$_ = \$_S->{'$_'};"} @keys;
$mt->prepend($prepend . $mt->prepend)->append('}' . $mt->append);
$cache->set($key => $mt);
}
# Make current controller available
no strict 'refs';
no warnings 'redefine';
local *{"${ns}::_C"} = sub {$c};
# Render with "epl" handler
return $renderer->handlers->{epl}->($renderer, $c, $output, $options);
}
);
}
sub _helpers {
my ($class, $helpers) = @_;
for my $method (grep {/^\w+$/} keys %$helpers) {
my $sub = $helpers->{$method};
monkey_patch $class, $method, sub { $class->_C->$sub(@_) };
}
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::EPRenderer - Embedded Perl renderer plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin('EPRenderer');
$self->plugin(EPRenderer => {name => 'foo'});
$self->plugin(EPRenderer => {template => {line_start => '.'}});
# Mojolicious::Lite
plugin 'EPRenderer';
plugin EPRenderer => {name => 'foo'};
plugin EPRenderer => {template => {line_start => '.'}};
=head1 DESCRIPTION
L<Mojolicious::Plugin::EPRenderer> is a renderer for C<ep> or C<Embedded Perl>
templates.
C<Embedded Perl> is a simple template format where you embed perl code into
documents. It is based on L<Mojo::Template>, but extends it with some
convenient syntax sugar designed specifically for L<Mojolicious>. It supports
L<Mojolicious> template helpers and exposes the stash directly as Perl
variables.
This is a core plugin, that means it is always enabled and its code a good
example for learning to build new plugins, you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 OPTIONS
L<Mojolicious::Plugin::EPRenderer> supports the following options.
=head2 name
# Mojolicious::Lite
plugin EPRenderer => {name => 'foo'};
Handler name, defaults to C<ep>.
=head2 template
# Mojolicious::Lite
plugin EPRenderer => {template => {line_start => '.'}};
Attribute values passed to L<Mojo::Template> object used to render templates.
=head1 METHODS
L<Mojolicious::Plugin::EPRenderer> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
$plugin->register(Mojolicious->new, {name => 'foo'});
Register renderer in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_EPRENDERER
$fatpacked{"Mojolicious/Plugin/HeaderCondition.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_HEADERCONDITION';
package Mojolicious::Plugin::HeaderCondition;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app) = @_;
$app->routes->add_condition(headers => \&_headers);
$app->routes->add_condition(
agent => sub { _headers(@_[0 .. 2], {'User-Agent' => $_[3]}) });
$app->routes->add_condition(
host => sub { _check($_[1]->req->url->to_abs->host, $_[3]) });
}
sub _check {
my ($value, $pattern) = @_;
return 1
if $value && $pattern && ref $pattern eq 'Regexp' && $value =~ $pattern;
return $value && defined $pattern && $pattern eq $value;
}
sub _headers {
my ($route, $c, $captures, $patterns) = @_;
return undef unless $patterns && ref $patterns eq 'HASH' && keys %$patterns;
# All headers need to match
my $headers = $c->req->headers;
_check(scalar $headers->header($_), $patterns->{$_}) || return undef
for keys %$patterns;
return 1;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::HeaderCondition - Header condition plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin('HeaderCondition');
$self->routes->get('/:controller/:action')
->over(headers => {Referer => qr/example\.com/});
# Mojolicious::Lite
plugin 'HeaderCondition';
get '/' => (headers => {Referer => qr/example\.com/}) => sub {...};
# All headers need to match
$self->routes->get('/:controller/:action')->over(headers => {
'X-Secret-Header' => 'Foo',
Referer => qr/example\.com/
});
# The "agent" condition is a shortcut for the "User-Agent" header
get '/' => (agent => qr/Firefox/) => sub {...};
# The "host" condition is a shortcut for the detected host
get '/' => (host => qr/mojolicio\.us/) => sub {...};
=head1 DESCRIPTION
L<Mojolicious::Plugin::HeaderCondition> is a route condition for header based
routes.
This is a core plugin, that means it is always enabled and its code a good
example for learning to build new plugins, you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 METHODS
L<Mojolicious::Plugin::HeaderCondition> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
Register conditions in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_HEADERCONDITION
$fatpacked{"Mojolicious/Plugin/JSONConfig.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_JSONCONFIG';
package Mojolicious::Plugin::JSONConfig;
use Mojo::Base 'Mojolicious::Plugin::Config';
use Mojo::JSON 'from_json';
use Mojo::Template;
sub parse {
my ($self, $content, $file, $conf, $app) = @_;
my $config = eval { from_json $self->render($content, $file, $conf, $app) };
die qq{Can't parse config "$file": $@} if !$config && $@;
die qq{Invalid config "$file"} unless ref $config eq 'HASH';
return $config;
}
sub register { shift->SUPER::register(shift, {ext => 'json', %{shift()}}) }
sub render {
my ($self, $content, $file, $conf, $app) = @_;
# Application instance and helper
my $prepend = q[my $app = shift; no strict 'refs'; no warnings 'redefine';];
$prepend .= q[sub app; local *app = sub { $app }; use Mojo::Base -strict;];
my $mt = Mojo::Template->new($conf->{template} || {})->name($file);
my $output = $mt->prepend($prepend . $mt->prepend)->render($content, $app);
return ref $output ? die $output : $output;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::JSONConfig - JSON configuration plugin
=head1 SYNOPSIS
# myapp.json (it's just JSON with embedded Perl)
{
"foo" : "bar",
"music_dir" : "<%= app->home->rel_dir('music') %>"
}
# Mojolicious
my $config = $self->plugin('JSONConfig');
say $config->{foo};
# Mojolicious::Lite
my $config = plugin 'JSONConfig';
say $config->{foo};
# foo.html.ep
%= $config->{foo}
# The configuration is available application wide
my $config = app->config;
say $config->{foo};
# Everything can be customized with options
my $config = plugin JSONConfig => {file => '/etc/myapp.conf'};
=head1 DESCRIPTION
L<Mojolicious::Plugin::JSONConfig> is a JSON configuration plugin that
preprocesses its input with L<Mojo::Template>.
The application object can be accessed via C<$app> or the C<app> function. You
can extend the normal configuration file C<$moniker.json> with C<mode>
specific ones like C<$moniker.$mode.json>. A default configuration filename
will be generated from the value of L<Mojolicious/"moniker">.
The code of this plugin is a good example for learning to build new plugins,
you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 OPTIONS
L<Mojolicious::Plugin::JSONConfig> inherits all options from
L<Mojolicious::Plugin::Config> and supports the following new ones.
=head2 template
# Mojolicious::Lite
plugin JSONConfig => {template => {line_start => '.'}};
Attribute values passed to L<Mojo::Template> object used to preprocess
configuration files.
=head1 METHODS
L<Mojolicious::Plugin::JSONConfig> inherits all methods from
L<Mojolicious::Plugin::Config> and implements the following new ones.
=head2 parse
$plugin->parse($content, $file, $conf, $app);
Process content with L</"render"> and parse it with L<Mojo::JSON>.
sub parse {
my ($self, $content, $file, $conf, $app) = @_;
...
$content = $self->render($content, $file, $conf, $app);
...
return $hash;
}
=head2 register
my $config = $plugin->register(Mojolicious->new);
my $config = $plugin->register(Mojolicious->new, {file => '/etc/foo.conf'});
Register plugin in L<Mojolicious> application and merge configuration.
=head2 render
$plugin->render($content, $file, $conf, $app);
Process configuration file with L<Mojo::Template>.
sub render {
my ($self, $content, $file, $conf, $app) = @_;
...
return $content;
}
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_JSONCONFIG
$fatpacked{"Mojolicious/Plugin/Mount.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_MOUNT';
package Mojolicious::Plugin::Mount;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Server;
sub register {
my ($self, $app, $conf) = @_;
my $path = (keys %$conf)[0];
my $embed = Mojo::Server->new->load_app($conf->{$path});
# Extract host
my $host;
if ($path =~ m!^(\*\.)?([^/]+)(/.*)?$!) {
$host = $1 ? qr/^(?:.*\.)?\Q$2\E$/i : qr/^\Q$2\E$/i;
$path = $3;
}
my $route = $app->routes->route($path)->detour(app => $embed);
return $host ? $route->over(host => $host) : $route;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::Mount - Application mount plugin
=head1 SYNOPSIS
# Mojolicious
my $route = $self->plugin(Mount => {'/prefix' => '/home/sri/myapp.pl'});
# Mojolicious::Lite
my $route = plugin Mount => {'/prefix' => '/home/sri/myapp.pl'};
# Adjust the generated route
my $example = plugin Mount => {'/example' => '/home/sri/example.pl'};
$example->to(message => 'It works great!');
my $app = $example->pattern->defaults->{app};
$app->config(foo => 'bar');
# Mount application with host
plugin Mount => {'example.com' => '/home/sri/myapp.pl'};
# Host and path
plugin Mount => {'example.com/myapp' => '/home/sri/myapp.pl'};
# Or even hosts with wildcard subdomains
plugin Mount => {'*.example.com/myapp' => '/home/sri/myapp.pl'};
=head1 DESCRIPTION
L<Mojolicious::Plugin::Mount> is a plugin that allows you to mount whole
L<Mojolicious> applications.
The code of this plugin is a good example for learning to build new plugins,
you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 METHODS
L<Mojolicious::Plugin::Mount> inherits all methods from L<Mojolicious::Plugin>
and implements the following new ones.
=head2 register
my $route = $plugin->register(Mojolicious->new, {'/foo' => '/some/app.pl'});
Mount L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_MOUNT
$fatpacked{"Mojolicious/Plugin/PODRenderer.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_PODRENDERER';
package Mojolicious::Plugin::PODRenderer;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::Asset::File;
use Mojo::ByteStream 'b';
use Mojo::DOM;
use Mojo::URL;
use Mojo::Util qw(slurp unindent url_escape);
use Pod::Simple::XHTML 3.09;
use Pod::Simple::Search;
sub register {
my ($self, $app, $conf) = @_;
my $preprocess = $conf->{preprocess} || 'ep';
$app->renderer->add_handler(
$conf->{name} || 'pod' => sub {
my ($renderer, $c, $output, $options) = @_;
# Preprocess and render
my $handler = $renderer->handlers->{$preprocess};
return undef unless $handler->($renderer, $c, $output, $options);
$$output = _pod_to_html($$output);
return 1;
}
);
$app->helper(pod_to_html => sub { shift; b(_pod_to_html(@_)) });
# Perldoc browser
return undef if $conf->{no_perldoc};
my $defaults = {module => 'Mojolicious/Guides', format => 'html'};
return $app->routes->any(
'/perldoc/:module' => $defaults => [module => qr/[^.]+/] => \&_perldoc);
}
sub _html {
my ($c, $src) = @_;
# Rewrite links
my $dom = Mojo::DOM->new(_pod_to_html($src));
my $perldoc = $c->url_for('/perldoc/');
$_->{href} =~ s!^https://metacpan\.org/pod/!$perldoc!
and $_->{href} =~ s!::!/!gi
for $dom->find('a[href]')->map('attr')->each;
# Rewrite code blocks for syntax highlighting and correct indentation
for my $e ($dom->find('pre > code')->each) {
$e->content(my $str = unindent $e->content);
next if $str =~ /^\s*(?:\$|Usage:)\s+/m || $str !~ /[\$\@\%]\w|-&gt;\w/m;
my $attrs = $e->attr;
my $class = $attrs->{class};
$attrs->{class} = defined $class ? "$class prettyprint" : 'prettyprint';
}
# Rewrite headers
my $toc = Mojo::URL->new->fragment('toc');
my @parts;
for my $e ($dom->find('h1, h2, h3')->each) {
push @parts, [] if $e->type eq 'h1' || !@parts;
my $anchor = $e->{id};
my $link = Mojo::URL->new->fragment($anchor);
push @{$parts[-1]}, my $text = $e->all_text, $link;
my $permalink = $c->link_to('#' => $link, class => 'permalink');
$e->content($permalink . $c->link_to($text => $toc, id => $anchor));
}
# Try to find a title
my $title = 'Perldoc';
$dom->find('h1 + p')->first(sub { $title = shift->text });
# Combine everything to a proper response
$c->content_for(perldoc => "$dom");
my $template = $c->app->renderer->_bundled('perldoc');
$c->render(inline => $template, title => $title, parts => \@parts);
}
sub _perldoc {
my $c = shift;
# Find module or redirect to CPAN
my $module = join '::', split '/', scalar $c->param('module');
my $path
= Pod::Simple::Search->new->find($module, map { $_, "$_/pods" } @INC);
return $c->redirect_to("https://metacpan.org/pod/$module")
unless $path && -r $path;
my $src = slurp $path;
$c->respond_to(txt => {data => $src}, html => sub { _html($c, $src) });
}
sub _pod_to_html {
return '' unless defined(my $pod = ref $_[0] eq 'CODE' ? shift->() : shift);
my $parser = Pod::Simple::XHTML->new;
$parser->perldoc_url_prefix('https://metacpan.org/pod/');
$parser->$_('') for qw(html_header html_footer);
$parser->output_string(\(my $output));
return $@ unless eval { $parser->parse_string_document("$pod"); 1 };
return $output;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::PODRenderer - POD renderer plugin
=head1 SYNOPSIS
# Mojolicious
my $route = $self->plugin('PODRenderer');
my $route = $self->plugin(PODRenderer => {name => 'foo'});
my $route = $self->plugin(PODRenderer => {preprocess => 'epl'});
# Mojolicious::Lite
my $route = plugin 'PODRenderer';
my $route = plugin PODRenderer => {name => 'foo'};
my $route = plugin PODRenderer => {preprocess => 'epl'};
# foo.html.ep
%= pod_to_html "=head1 TEST\n\nC<123>"
=head1 DESCRIPTION
L<Mojolicious::Plugin::PODRenderer> is a renderer for true Perl hackers, rawr!
The code of this plugin is a good example for learning to build new plugins,
you're welcome to fork it.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 OPTIONS
L<Mojolicious::Plugin::PODRenderer> supports the following options.
=head2 name
# Mojolicious::Lite
plugin PODRenderer => {name => 'foo'};
Handler name, defaults to C<pod>.
=head2 no_perldoc
# Mojolicious::Lite
plugin PODRenderer => {no_perldoc => 1};
Disable L<Mojolicious::Guides> documentation browser that will otherwise be
available under C</perldoc>.
=head2 preprocess
# Mojolicious::Lite
plugin PODRenderer => {preprocess => 'epl'};
Name of handler used to preprocess POD, defaults to C<ep>.
=head1 HELPERS
L<Mojolicious::Plugin::PODRenderer> implements the following helpers.
=head2 pod_to_html
%= pod_to_html '=head2 lalala'
<%= pod_to_html begin %>=head2 lalala<% end %>
Render POD to HTML without preprocessing.
=head1 METHODS
L<Mojolicious::Plugin::PODRenderer> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
my $route = $plugin->register(Mojolicious->new);
my $route = $plugin->register(Mojolicious->new, {name => 'foo'});
Register renderer and helper in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_PODRENDERER
$fatpacked{"Mojolicious/Plugin/TagHelpers.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGIN_TAGHELPERS';
package Mojolicious::Plugin::TagHelpers;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::ByteStream;
use Mojo::Util 'xss_escape';
use Scalar::Util 'blessed';
sub register {
my ($self, $app) = @_;
# Text field variations
my @time = qw(date datetime month time week);
for my $name (@time, qw(color email number range search tel text url)) {
$app->helper("${name}_field" => sub { _input(@_, type => $name) });
}
$app->helper(check_box =>
sub { _input(shift, shift, value => shift, @_, type => 'checkbox') });
$app->helper(csrf_field => \&_csrf_field);
$app->helper(file_field =>
sub { shift; _tag('input', name => shift, @_, type => 'file') });
$app->helper(form_for => \&_form_for);
$app->helper(hidden_field => \&_hidden_field);
$app->helper(image => sub { _tag('img', src => shift->url_for(shift), @_) });
$app->helper(input_tag => sub { _input(@_) });
$app->helper(javascript => \&_javascript);
$app->helper(label_for => \&_label_for);
$app->helper(link_to => \&_link_to);
$app->helper(password_field => \&_password_field);
$app->helper(radio_button =>
sub { _input(shift, shift, value => shift, @_, type => 'radio') });
$app->helper(select_field => \&_select_field);
$app->helper(stylesheet => \&_stylesheet);
$app->helper(submit_button => \&_submit_button);
# "t" is just a shortcut for the "tag" helper
$app->helper($_ => sub { shift; _tag(@_) }) for qw(t tag);
$app->helper(tag_with_error => \&_tag_with_error);
$app->helper(text_area => \&_text_area);
}
sub _csrf_field {
my $c = shift;
return _hidden_field($c, csrf_token => $c->helpers->csrf_token, @_);
}
sub _form_for {
my ($c, @url) = (shift, shift);
push @url, shift if ref $_[0] eq 'HASH';
# POST detection
my @post;
if (my $r = $c->app->routes->lookup($url[0])) {
my %methods = (GET => 1, POST => 1);
do {
my @via = @{$r->via || []};
%methods = map { $_ => 1 } grep { $methods{$_} } @via if @via;
} while $r = $r->parent;
@post = (method => 'POST') if $methods{POST} && !$methods{GET};
}
return _tag('form', action => $c->url_for(@url), @post, @_);
}
sub _hidden_field {
my $c = shift;
return _tag('input', name => shift, value => shift, @_, type => 'hidden');
}
sub _input {
my ($c, $name) = (shift, shift);
my %attrs = @_ % 2 ? (value => shift, @_) : @_;
# Special selection value
my @values = @{$c->every_param($name)};
my $type = $attrs{type} || '';
if (@values && $type ne 'submit') {
# Checkbox or radiobutton
my $value = $attrs{value} // '';
if ($type eq 'checkbox' || $type eq 'radio') {
$attrs{value} = $value;
$attrs{checked} = 'checked' if grep { $_ eq $value } @values;
}
# Others
else { $attrs{value} = $values[0] }
}
return _validation($c, $name, 'input', %attrs, name => $name);
}
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);
}
sub _label_for {
my ($c, $name) = (shift, shift);
my $content = ref $_[-1] eq 'CODE' ? pop : shift;
return _validation($c, $name, 'label', for => $name, @_, $content);
}
sub _link_to {
my ($c, $content) = (shift, shift);
my @url = ($content);
# Content
unless (ref $_[-1] eq 'CODE') {
@url = (shift);
push @_, $content;
}
# Captures
push @url, shift if ref $_[0] eq 'HASH';
return _tag('a', href => $c->url_for(@url), @_);
}
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]);
}
sub _password_field {
my ($c, $name) = (shift, shift);
return _validation($c, $name, 'input', @_, name => $name,
type => 'password');
}
sub _select_field {
my ($c, $name, $options, %attrs) = (shift, shift, shift, @_);
my %values = map { $_ => 1 } @{$c->every_param($name)};
my $groups = '';
for my $group (@$options) {
# "optgroup" tag
if (blessed $group && $group->isa('Mojo::Collection')) {
my ($label, $values, %attrs) = @$group;
my $content = join '', map { _option(\%values, $_) } @$values;
$groups .= _tag('optgroup', label => $label, %attrs, sub {$content});
}
# "option" tag
else { $groups .= _option(\%values, $group) }
}
return _validation($c, $name, 'select', %attrs, name => $name,
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);
}
sub _submit_button {
my $c = shift;
return _tag('input', value => shift // 'Ok', @_, type => 'submit');
}
sub _tag {
my $name = shift;
# Content
my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
my $content = @_ % 2 ? pop : undef;
# Start tag
my $tag = "<$name";
# Attributes
my %attrs = @_;
if ($attrs{data} && ref $attrs{data} eq 'HASH') {
while (my ($key, $value) = each %{$attrs{data}}) {
$key =~ y/_/-/;
$attrs{lc("data-$key")} = $value;
}
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);
}
sub _tag_with_error {
my ($c, $tag) = (shift, shift);
my ($content, %attrs) = (@_ % 2 ? pop : undef, @_);
$attrs{class} .= $attrs{class} ? ' field-with-error' : 'field-with-error';
return _tag($tag, %attrs, defined $content ? $content : ());
}
sub _text_area {
my ($c, $name) = (shift, shift);
my $cb = ref $_[-1] eq 'CODE' ? pop : undef;
my $content = @_ % 2 ? shift : undef;
$content = $c->param($name) // $content // $cb // '';
return _validation($c, $name, 'textarea', @_, name => $name, $content);
}
sub _validation {
my ($c, $name) = (shift, shift);
return _tag(@_) unless $c->validation->has_error($name);
return $c->helpers->tag_with_error(@_);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::TagHelpers - Tag helpers plugin
=head1 SYNOPSIS
# Mojolicious
$self->plugin('TagHelpers');
# Mojolicious::Lite
plugin 'TagHelpers';
=head1 DESCRIPTION
L<Mojolicious::Plugin::TagHelpers> is a collection of HTML tag helpers for
L<Mojolicious>.
Most form helpers can automatically pick up previous input values and will
show them as default. You can also use
L<Mojolicious::Plugin::DefaultHelpers/"param"> to set them manually and let
necessary attributes always be generated automatically.
% param country => 'germany' unless param 'country';
<%= radio_button country => 'germany' %> Germany
<%= radio_button country => 'france' %> France
<%= 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.
<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.
See L<Mojolicious::Plugins/"PLUGINS"> for a list of plugins that are available
by default.
=head1 HELPERS
L<Mojolicious::Plugin::TagHelpers> implements the following helpers.
=head2 check_box
%= check_box employed => 1
%= check_box employed => 1, disabled => 'disabled'
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" />
=head2 color_field
%= color_field 'background'
%= color_field background => '#ffffff'
%= color_field background => '#ffffff', id => 'foo'
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" />
=head2 csrf_field
%= csrf_field
Generate C<input> tag of type C<hidden> with
L<Mojolicious::Plugin::DefaultHelpers/"csrf_token">.
<input name="csrf_token" type="hidden" value="fa6a08..." />
=head2 date_field
%= date_field 'end'
%= date_field end => '2012-12-21'
%= date_field end => '2012-12-21', id => 'foo'
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" />
=head2 datetime_field
%= datetime_field 'end'
%= datetime_field end => '2012-12-21T23:59:59Z'
%= datetime_field end => '2012-12-21T23:59:59Z', id => 'foo'
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" />
=head2 email_field
%= email_field 'notify'
%= email_field notify => 'nospam@example.com'
%= email_field notify => 'nospam@example.com', id => 'foo'
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" />
=head2 file_field
%= file_field 'avatar'
%= file_field 'avatar', id => 'foo'
Generate C<input> tag of type C<file>.
<input name="avatar" type="file" />
<input id="foo" name="avatar" type="file" />
=head2 form_for
%= form_for login => begin
%= text_field 'first_name'
%= submit_button
% end
%= form_for login => {format => 'txt'} => (method => 'POST') => begin
%= text_field 'first_name'
%= submit_button
% end
%= form_for '/login' => (enctype => 'multipart/form-data') => begin
%= text_field 'first_name', disabled => 'disabled'
%= submit_button
% end
%= form_for 'http://example.com/login' => (method => 'POST') => begin
%= text_field 'first_name'
%= submit_button
% end
Generate portable C<form> tag with L<Mojolicious::Controller/"url_for">. For
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" />
</form>
<form action="/path/to/login.txt" method="POST">
<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" />
</form>
<form action="http://example.com/login" method="POST">
<input name="first_name" type="text" />
<input value="Ok" type="submit" />
</form>
=head2 hidden_field
%= hidden_field foo => 'bar'
%= hidden_field foo => 'bar', id => 'bar'
Generate C<input> tag of type C<hidden>.
<input name="foo" type="hidden" value="bar" />
<input id="bar" name="foo" type="hidden" value="bar" />
=head2 image
%= image '/images/foo.png'
%= image '/images/foo.png', alt => 'Foo'
Generate portable C<img> tag.
<img src="/path/to/images/foo.png" />
<img alt="Foo" src="/path/to/images/foo.png" />
=head2 input_tag
%= input_tag 'first_name'
%= input_tag first_name => 'Default name'
%= input_tag 'employed', type => 'checkbox'
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" />
=head2 javascript
%= javascript '/script.js'
%= javascript begin
var a = 'b';
% end
Generate portable C<script> tag for JavaScript asset.
<script src="/path/to/script.js" />
<script><![CDATA[
var a = 'b';
]]></script>
=head2 label_for
%= label_for first_name => 'First name'
%= label_for first_name => 'First name', class => 'user'
%= label_for first_name => begin
First name
% end
%= label_for first_name => (class => 'user') => begin
First name
% end
Generate C<label> tag.
<label for="first_name">First name</label>
<label class="user" for="first_name">First name</label>
<label for="first_name">
First name
</label>
<label class="user" for="first_name">
First name
</label>
=head2 link_to
%= link_to Home => 'index'
%= link_to Home => 'index' => {format => 'txt'} => (class => 'menu')
%= link_to index => {format => 'txt'} => (class => 'menu') => begin
Home
% end
%= link_to Contact => 'mailto:sri@example.com'
<%= link_to index => begin %>Home<% end %>
<%= link_to '/file.txt' => begin %>File<% end %>
<%= link_to 'http://mojolicio.us' => begin %>Mojolicious<% end %>
<%= link_to url_for->query(foo => 'bar')->to_abs => begin %>Retry<% end %>
Generate portable C<a> tag with L<Mojolicious::Controller/"url_for">, defaults
to using the capitalized link target as content.
<a href="/path/to/index">Home</a>
<a class="menu" href="/path/to/index.txt">Home</a>
<a class="menu" href="/path/to/index.txt">
Home
</a>
<a href="mailto:sri@example.com">Contact</a>
<a href="/path/to/index">Home</a>
<a href="/path/to/file.txt">File</a>
<a href="http://mojolicio.us">Mojolicious</a>
<a href="http://127.0.0.1:3000/current/path?foo=bar">Retry</a>
=head2 month_field
%= month_field 'vacation'
%= month_field vacation => '2012-12'
%= month_field vacation => '2012-12', id => 'foo'
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" />
=head2 number_field
%= number_field 'age'
%= number_field age => 25
%= number_field age => 25, id => 'foo', min => 0, max => 200
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" />
=head2 password_field
%= password_field 'pass'
%= password_field 'pass', id => 'foo'
Generate C<input> tag of type C<password>.
<input name="pass" type="password" />
<input id="foo" name="pass" type="password" />
=head2 radio_button
%= radio_button country => 'germany'
%= radio_button country => 'germany', id => 'foo'
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" />
=head2 range_field
%= range_field 'age'
%= range_field age => 25
%= range_field age => 25, id => 'foo', min => 0, max => 200
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" />
=head2 search_field
%= search_field 'q'
%= search_field q => 'perl'
%= search_field q => 'perl', id => 'foo'
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" />
=head2 select_field
%= select_field country => [qw(de en)]
%= select_field country => [[Germany => 'de'], 'en'], id => 'eu'
%= select_field country => [[Germany => 'de', disabled => 'disabled'], 'en']
%= select_field country => [c(EU => [[Germany => 'de'], 'en'], id => 'eu')]
%= select_field country => [c(EU => [qw(de en)]), c(Asia => [qw(cn jp)])]
Generate C<select> and C<option> tags from array references and C<optgroup>
tags from L<Mojo::Collection> objects. Previous input values will
automatically get picked up and shown as default.
<select name="country">
<option value="de">de</option>
<option value="en">en</option>
</select>
<select id="eu" name="country">
<option value="de">Germany</option>
<option value="en">en</option>
</select>
<select name="country">
<option disabled="disabled" value="de">Germany</option>
<option value="en">en</option>
</select>
<select name="country">
<optgroup id="eu" label="EU">
<option value="de">Germany</option>
<option value="en">en</option>
</optgroup>
</select>
<select name="country">
<optgroup label="EU">
<option value="de">de</option>
<option value="en">en</option>
</optgroup>
<optgroup label="Asia">
<option value="cn">cn</option>
<option value="jp">jp</option>
</optgroup>
</select>
=head2 stylesheet
%= stylesheet '/foo.css'
%= stylesheet begin
body {color: #000}
% end
Generate portable C<style> or C<link> tag for CSS asset.
<link href="/path/to/foo.css" rel="stylesheet" />
<style><![CDATA[
body {color: #000}
]]></style>
=head2 submit_button
%= submit_button
%= submit_button 'Ok!', id => 'foo'
Generate C<input> tag of type C<submit>.
<input type="submit" value="Ok" />
<input id="foo" type="submit" value="Ok!" />
=head2 t
%=t div => 'test & 123'
Alias for L</"tag">.
<div>test &amp; 123</div>
=head2 tag
%= tag 'div'
%= tag 'div', id => 'foo'
%= tag div => 'test & 123'
%= tag div => (id => 'foo') => 'test & 123'
%= tag div => (data => {my_id => 1, Name => 'test'}) => 'test & 123'
%= tag div => begin
test & 123
% end
<%= tag div => (id => 'foo') => begin %>test & 123<% end %>
HTML/XML tag generator.
<div />
<div id="foo" />
<div>test &amp; 123</div>
<div id="foo">test &amp; 123</div>
<div data-my-id="1" data-name="test">test &amp; 123</div>
<div>
test & 123
</div>
<div id="foo">test & 123</div>
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(div => '<p>This will be escaped</p>');
my $output = $c->tag(div => sub { '<p>This will not be escaped</p>' });
Results are automatically wrapped in L<Mojo::ByteStream> objects to prevent
accidental double escaping in C<ep> templates.
=head2 tag_with_error
%= tag_with_error 'input', class => 'foo'
Same as L</"tag">, but adds the class C<field-with-error>.
<input class="foo field-with-error" />
=head2 tel_field
%= tel_field 'work'
%= tel_field work => '123456789'
%= tel_field work => '123456789', id => 'foo'
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" />
=head2 text_area
%= text_area 'foo'
%= text_area 'foo', cols => 40
%= text_area foo => 'Default!', cols => 40
%= text_area foo => (cols => 40) => begin
Default!
% end
Generate C<textarea> tag. Previous input values will automatically get picked
up and shown as default.
<textarea name="foo"></textarea>
<textarea cols="40" name="foo"></textarea>
<textarea cols="40" name="foo">Default!</textarea>
<textarea cols="40" name="foo">
Default!
</textarea>
=head2 text_field
%= text_field 'first_name'
%= text_field first_name => 'Default name'
%= text_field first_name => 'Default name', class => 'user'
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" />
=head2 time_field
%= time_field 'start'
%= time_field start => '23:59:59'
%= time_field start => '23:59:59', id => 'foo'
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" />
=head2 url_field
%= url_field 'address'
%= url_field address => 'http://mojolicio.us'
%= url_field address => 'http://mojolicio.us', id => 'foo'
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" />
=head2 week_field
%= week_field 'vacation'
%= week_field vacation => '2012-W17'
%= week_field vacation => '2012-W17', id => 'foo'
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" />
=head1 METHODS
L<Mojolicious::Plugin::TagHelpers> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.
=head2 register
$plugin->register(Mojolicious->new);
Register helpers in L<Mojolicious> application.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGIN_TAGHELPERS
$fatpacked{"Mojolicious/Plugins.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_PLUGINS';
package Mojolicious::Plugins;
use Mojo::Base 'Mojo::EventEmitter';
use Mojo::Util 'camelize';
has namespaces => sub { ['Mojolicious::Plugin'] };
sub emit_hook {
my $self = shift;
for my $cb (@{$self->subscribers(shift)}) { $cb->(@_) }
return $self;
}
sub emit_chain {
my ($self, $name, @args) = @_;
my $wrapper;
for my $cb (reverse @{$self->subscribers($name)}) {
my $next = $wrapper;
$wrapper = sub { $cb->($next, @args) };
}
!$wrapper ? return : return $wrapper->();
}
sub emit_hook_reverse {
my $self = shift;
for my $cb (reverse @{$self->subscribers(shift)}) { $cb->(@_) }
return $self;
}
sub load_plugin {
my ($self, $name) = @_;
# Try all namespaces
my $class = $name =~ /^[a-z]/ ? camelize($name) : $name;
_load($_) and return $_->new for map {"${_}::$class"} @{$self->namespaces};
# Full module name
return $name->new if _load($name);
# Not found
die qq{Plugin "$name" missing, maybe you need to install it?\n};
}
sub register_plugin {
shift->load_plugin(shift)->register(shift, ref $_[0] ? $_[0] : {@_});
}
sub _load {
my $module = shift;
return $module->isa('Mojolicious::Plugin')
unless my $e = Mojo::Loader->new->load($module);
ref $e ? die $e : return undef;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugins - Plugin manager
=head1 SYNOPSIS
use Mojolicious::Plugins;
my $plugins = Mojolicious::Plugins->new;
push @{$plugins->namespaces}, 'MyApp::Plugin';
=head1 DESCRIPTION
L<Mojolicious::Plugins> is the plugin manager of L<Mojolicious>.
=head1 PLUGINS
The following plugins are included in the L<Mojolicious> distribution as
examples.
=over 2
=item L<Mojolicious::Plugin::Charset>
Change the application charset.
=item L<Mojolicious::Plugin::Config>
Perl-ish configuration files.
=item L<Mojolicious::Plugin::DefaultHelpers>
General purpose helper collection, loaded automatically.
=item L<Mojolicious::Plugin::EPLRenderer>
Renderer for plain embedded Perl templates, loaded automatically.
=item L<Mojolicious::Plugin::EPRenderer>
Renderer for more sophisticated embedded Perl templates, loaded automatically.
=item L<Mojolicious::Plugin::HeaderCondition>
Route condition for all kinds of headers, loaded automatically.
=item L<Mojolicious::Plugin::JSONConfig>
JSON configuration files.
=item L<Mojolicious::Plugin::Mount>
Mount whole L<Mojolicious> applications.
=item L<Mojolicious::Plugin::PODRenderer>
Renderer for turning POD into HTML and documentation browser for
L<Mojolicious::Guides>.
=item L<Mojolicious::Plugin::TagHelpers>
Template specific helper collection, loaded automatically.
=back
=head1 EVENTS
L<Mojolicious::Plugins> inherits all events from L<Mojo::EventEmitter>.
=head1 ATTRIBUTES
L<Mojolicious::Plugins> implements the following attributes.
=head2 namespaces
my $namespaces = $plugins->namespaces;
$plugins = $plugins->namespaces(['Mojolicious::Plugin']);
Namespaces to load plugins from, defaults to L<Mojolicious::Plugin>.
# Add another namespace to load plugins from
push @{$plugins->namespaces}, 'MyApp::Plugin';
=head1 METHODS
L<Mojolicious::Plugins> inherits all methods from L<Mojo::EventEmitter> and
implements the following new ones.
=head2 emit_chain
$plugins->emit_chain('foo');
$plugins->emit_chain(foo => 123);
Emit events as chained hooks.
=head2 emit_hook
$plugins = $plugins->emit_hook('foo');
$plugins = $plugins->emit_hook(foo => 123);
Emit events as hooks.
=head2 emit_hook_reverse
$plugins = $plugins->emit_hook_reverse('foo');
$plugins = $plugins->emit_hook_reverse(foo => 123);
Emit events as hooks in reverse order.
=head2 load_plugin
my $plugin = $plugins->load_plugin('some_thing');
my $plugin = $plugins->load_plugin('SomeThing');
my $plugin = $plugins->load_plugin('MyApp::Plugin::SomeThing');
Load a plugin from the configured namespaces or by full module name.
=head2 register_plugin
$plugins->register_plugin('some_thing', Mojolicious->new);
$plugins->register_plugin('some_thing', Mojolicious->new, foo => 23);
$plugins->register_plugin('some_thing', Mojolicious->new, {foo => 23});
$plugins->register_plugin('SomeThing', Mojolicious->new);
$plugins->register_plugin('SomeThing', Mojolicious->new, foo => 23);
$plugins->register_plugin('SomeThing', Mojolicious->new, {foo => 23});
$plugins->register_plugin('MyApp::Plugin::SomeThing', Mojolicious->new);
$plugins->register_plugin(
'MyApp::Plugin::SomeThing', Mojolicious->new, foo => 23);
$plugins->register_plugin(
'MyApp::Plugin::SomeThing', Mojolicious->new, {foo => 23});
Load a plugin from the configured namespaces or by full module name and run
C<register>, optional arguments are passed through.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_PLUGINS
$fatpacked{"Mojolicious/Renderer.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_RENDERER';
package Mojolicious::Renderer;
use Mojo::Base -base;
use File::Spec::Functions 'catfile';
use Mojo::Cache;
use Mojo::JSON 'encode_json';
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util qw(decamelize encode md5_sum monkey_patch slurp);
has cache => sub { Mojo::Cache->new };
has classes => sub { ['main'] };
has default_format => 'html';
has 'default_handler';
has encoding => 'UTF-8';
has handlers => sub {
{
data => sub { ${$_[2]} = $_[3]{data} },
text => sub { ${$_[2]} = $_[3]{text} },
json => sub { ${$_[2]} = encode_json($_[3]{json}) }
};
};
has helpers => sub { {} };
has paths => sub { [] };
# Bundled templates
my $HOME = Mojo::Home->new;
$HOME->parse(
$HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/templates'));
my %TEMPLATES = map { $_ => slurp $HOME->rel_file($_) } @{$HOME->list_files};
my $LOADER = Mojo::Loader->new;
sub DESTROY { Mojo::Util::_teardown($_) for @{shift->{namespaces}} }
sub accepts {
my ($self, $c) = (shift, shift);
# List representations
my $req = $c->req;
my @exts = @{$c->app->types->detect($req->headers->accept, $req->is_xhr)};
if (!@exts && (my $format = $c->stash->{format} || $req->param('format'))) {
push @exts, $format;
}
return \@exts unless @_;
# Find best representation
for my $ext (@exts) {
return $ext if grep { $ext eq $_ } @_;
}
return @exts ? undef : shift;
}
sub add_handler { shift->_add(handlers => @_) }
sub add_helper { shift->_add(helpers => @_) }
sub get_data_template {
my ($self, $options) = @_;
# Find template
return undef unless my $template = $self->template_name($options);
return $LOADER->data($self->{index}{$template}, $template);
}
sub get_helper {
my ($self, $name) = @_;
if (my $h = $self->{proxy}{$name} || $self->helpers->{$name}) { return $h }
my $found;
my $class = 'Mojolicious::Renderer::Helpers::' . md5_sum "$name:$self";
my $re = $name eq '' ? qr/^(([^.]+))/ : qr/^(\Q$name\E\.([^.]+))/;
for my $key (keys %{$self->helpers}) {
$key =~ $re ? ($found, my $method) = (1, $2) : next;
my $sub = $self->get_helper($1);
monkey_patch $class, $method => sub { ${shift()}->$sub(@_) };
}
$found ? push @{$self->{namespaces}}, $class : return undef;
return $self->{proxy}{$name} = sub { bless \(my $dummy = shift), $class };
}
sub render {
my ($self, $c, $args) = @_;
# Localize "extends" and "layout" to allow argument overrides
my $stash = $c->stash;
local $stash->{layout} = $stash->{layout} if exists $stash->{layout};
local $stash->{extends} = $stash->{extends} if exists $stash->{extends};
# Rendering to string
local @{$stash}{keys %$args} if my $ts = delete $args->{'mojo.to_string'};
delete @{$stash}{qw(layout extends)} if $ts;
# Merge stash and arguments
@$stash{keys %$args} = values %$args;
my $options = {
encoding => $self->encoding,
handler => $stash->{handler},
template => delete $stash->{template},
variant => $stash->{variant}
};
my $inline = $options->{inline} = delete $stash->{inline};
$options->{handler} //= $self->default_handler if defined $inline;
$options->{format} = $stash->{format} || $self->default_format;
# Data
my $output;
if (defined(my $data = delete $stash->{data})) {
$self->handlers->{data}->($self, $c, \$output, {data => $data});
return $output, $options->{format};
}
# JSON
elsif (exists $stash->{json}) {
my $json = delete $stash->{json};
$self->handlers->{json}->($self, $c, \$output, {json => $json});
return $output, 'json';
}
# Text
elsif (defined(my $text = delete $stash->{text})) {
$self->handlers->{text}->($self, $c, \$output, {text => $text});
}
# Template or templateless handler
else {
$options->{template} ||= $self->template_for($c);
return unless $self->_render_template($c, \$output, $options);
}
# Extends
my $content = $stash->{'mojo.content'} ||= {};
local $content->{content} = $output if $stash->{extends} || $stash->{layout};
while ((my $extends = $self->_extends($stash)) && !defined $inline) {
@$options{qw(handler template)} = ($stash->{handler}, $extends);
$options->{format} = $stash->{format} || $self->default_format;
$self->_render_template($c, \$output, $options);
$content->{content} = $output
if $content->{content} !~ /\S/ && $output =~ /\S/;
}
# Encoding
$output = encode $options->{encoding}, $output
if !$ts && $options->{encoding} && $output;
return $output, $options->{format};
}
sub template_for {
my ($self, $c) = @_;
# Normal default template
my $stash = $c->stash;
my ($controller, $action) = @$stash{qw(controller action)};
return join '/', split('-', decamelize($controller)), $action
if $controller && $action;
# Try the route name if we don't have controller and action
return undef unless my $route = $c->match->endpoint;
return $route->name;
}
sub template_handler {
my ($self, $options) = @_;
return undef unless my $file = $self->template_name($options);
return $self->default_handler
unless my $handlers = $self->{templates}{$file};
return $handlers->[0];
}
sub template_name {
my ($self, $options) = @_;
return undef unless my $template = $options->{template};
return undef unless my $format = $options->{format};
$template .= ".$format";
$self->_warmup unless $self->{templates};
# Variants
my $handler = $options->{handler};
if (defined(my $variant = $options->{variant})) {
$variant = "$template+$variant";
my $handlers = $self->{templates}{$variant} // [];
$template = $variant
if @$handlers && !defined $handler || grep { $_ eq $handler } @$handlers;
}
return defined $handler ? "$template.$handler" : $template;
}
sub template_path {
my $self = shift;
# Nameless
return undef unless my $name = $self->template_name(shift);
# Search all paths
for my $path (@{$self->paths}) {
my $file = catfile($path, split '/', $name);
return $file if -r $file;
}
return undef;
}
sub _add {
my ($self, $attr, $name, $cb) = @_;
$self->$attr->{$name} = $cb;
delete $self->{proxy};
return $self;
}
sub _bundled { $TEMPLATES{"@{[pop]}.html.ep"} }
sub _extends {
my ($self, $stash) = @_;
my $layout = delete $stash->{layout};
$stash->{extends} ||= join('/', 'layouts', $layout) if $layout;
return delete $stash->{extends};
}
sub _render_template {
my ($self, $c, $output, $options) = @_;
# Find handler and render
my $handler = $options->{handler} ||= $self->template_handler($options);
return undef unless $handler;
if (my $renderer = $self->handlers->{$handler}) {
return 1 if $renderer->($self, $c, $output, $options);
}
# No handler
else { $c->app->log->error(qq{No handler for "$handler" available.}) }
return undef;
}
sub _warmup {
my $self = shift;
my ($index, $templates) = @$self{qw(index templates)} = ({}, {});
# Handlers for templates
s/\.(\w+)$// and push @{$templates->{$_}}, $1
for map { sort @{Mojo::Home->new($_)->list_files} } @{$self->paths};
# Handlers and classes for DATA templates
for my $class (reverse @{$self->classes}) {
$index->{$_} = $class for my @keys = sort keys %{$LOADER->data($class)};
s/\.(\w+)$// and unshift @{$templates->{$_}}, $1 for reverse @keys;
}
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Renderer - Generate dynamic content
=head1 SYNOPSIS
use Mojolicious::Renderer;
my $renderer = Mojolicious::Renderer->new;
push @{$renderer->classes}, 'MyApp::Controller::Foo';
push @{$renderer->paths}, '/home/sri/templates';
=head1 DESCRIPTION
L<Mojolicious::Renderer> is the standard L<Mojolicious> renderer.
See L<Mojolicious::Guides::Rendering> for more.
=head1 ATTRIBUTES
L<Mojolicious::Renderer> implements the following attributes.
=head2 cache
my $cache = $renderer->cache;
$renderer = $renderer->cache(Mojo::Cache->new);
Renderer cache, defaults to a L<Mojo::Cache> object.
=head2 classes
my $classes = $renderer->classes;
$renderer = $renderer->classes(['main']);
Classes to use for finding templates in C<DATA> sections, first one has the
highest precedence, defaults to C<main>.
# Add another class with templates in DATA section
push @{$renderer->classes}, 'Mojolicious::Plugin::Fun';
=head2 default_format
my $default = $renderer->default_format;
$renderer = $renderer->default_format('html');
The default format to render if C<format> is not set in the stash.
=head2 default_handler
my $default = $renderer->default_handler;
$renderer = $renderer->default_handler('ep');
The default template handler to use for rendering in cases where auto
detection doesn't work, like for C<inline> templates.
=head2 encoding
my $encoding = $renderer->encoding;
$renderer = $renderer->encoding('koi8-r');
Will encode generated content if set, defaults to C<UTF-8>. Note that many
renderers such as L<Mojolicious::Plugin::EPRenderer> also use this value to
determine if template files should be decoded before processing.
=head2 handlers
my $handlers = $renderer->handlers;
$renderer = $renderer->handlers({epl => sub {...}});
Registered handlers, by default only C<data>, C<text> and C<json> are already
defined.
=head2 helpers
my $helpers = $renderer->helpers;
$renderer = $renderer->helpers({url_for => sub {...}});
Registered helpers.
=head2 paths
my $paths = $renderer->paths;
$renderer = $renderer->paths(['/home/sri/templates']);
Directories to look for templates in, first one has the highest precedence.
# Add another "templates" directory
push @{$renderer->paths}, '/home/sri/templates';
=head1 METHODS
L<Mojolicious::Renderer> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 accepts
my $all = $renderer->accepts(Mojolicious::Controller->new);
my $best = $renderer->accepts(Mojolicious::Controller->new, 'html', 'json');
Select best possible representation for L<Mojolicious::Controller> object from
C<Accept> request header, C<format> stash value or C<format> C<GET>/C<POST>
parameter, defaults to returning the first extension if no preference could be
detected. Since browsers often don't really know what they actually want,
unspecific C<Accept> request headers with more than one MIME type will be
ignored, unless the C<X-Requested-With> header is set to the value
C<XMLHttpRequest>.
=head2 add_handler
$renderer = $renderer->add_handler(epl => sub {...});
Register a new handler.
=head2 add_helper
$renderer = $renderer->add_helper(url_for => sub {...});
Register a new helper.
=head2 get_data_template
my $template = $renderer->get_data_template({
template => 'foo/bar',
format => 'html',
handler => 'epl'
});
Get a C<DATA> section template by name, usually used by handlers.
=head2 get_helper
my $helper = $renderer->get_helper('url_for');
Get a helper by full name, generate a helper dynamically for a prefix or
return C<undef> if no helper or prefix could be found. Generated helpers
return a proxy object containing the current controller object and on which
nested helpers can be called.
=head2 render
my ($output, $format) = $renderer->render(Mojolicious::Controller->new, {
template => 'foo/bar',
foo => 'bar'
});
Render output through one of the renderers. See
L<Mojolicious::Controller/"render"> for a more user-friendly interface.
=head2 template_for
my $name = $renderer->template_for(Mojolicious::Controller->new);
Generate default template name for L<Mojolicious::Controller> object.
=head2 template_handler
my $handler = $renderer->template_handler({
template => 'foo/bar',
format => 'html'
});
Detect handler based on an options hash reference with C<template> and
C<format>.
=head2 template_name
my $template = $renderer->template_name({
template => 'foo/bar',
format => 'html',
handler => 'epl'
});
Builds a template name based on an options hash reference with C<template>,
C<format> and C<handler>, usually used by handlers.
=head2 template_path
my $path = $renderer->template_path({
template => 'foo/bar',
format => 'html',
handler => 'epl'
});
Builds a full template path based on an options hash reference with
C<template>, C<format> and C<handler>, usually used by handlers.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_RENDERER
$fatpacked{"Mojolicious/Routes.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_ROUTES';
package Mojolicious::Routes;
use Mojo::Base 'Mojolicious::Routes::Route';
use List::Util 'first';
use Mojo::Cache;
use Mojo::Loader;
use Mojo::Util 'camelize';
use Mojolicious::Routes::Match;
use Scalar::Util 'weaken';
has base_classes => sub { [qw(Mojolicious::Controller Mojo)] };
has cache => sub { Mojo::Cache->new };
has [qw(conditions shortcuts)] => sub { {} };
has hidden => sub { [qw(attr has new tap)] };
has namespaces => sub { [] };
sub add_condition { shift->_add(conditions => @_) }
sub add_shortcut { shift->_add(shortcuts => @_) }
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;
}
sub continue {
my ($self, $c) = @_;
my $match = $c->match;
my $stack = $match->stack;
my $current = $match->current;
return $self->auto_render($c) unless my $field = $stack->[$current];
# Merge captures into stash
my $stash = $c->stash;
@{$stash->{'mojo.captures'} //= {}}{keys %$field} = values %$field;
@$stash{keys %$field} = values %$field;
my $continue;
my $last = !$stack->[++$current];
if (my $cb = $field->{cb}) { $continue = $self->_callback($c, $cb, $last) }
else { $continue = $self->_controller($c, $field, $last) }
$match->current($current);
$self->continue($c) if $last || $continue;
}
sub dispatch {
my ($self, $c) = @_;
$self->match($c);
@{$c->match->stack} ? $self->continue($c) : return undef;
return 1;
}
sub hide { push @{shift->hidden}, @_ }
sub is_hidden {
my ($self, $method) = @_;
my $h = $self->{hiding} ||= {map { $_ => 1 } @{$self->hidden}};
return !!($h->{$method} || index($method, '_') == 0 || $method !~ /[a-z]/);
}
sub lookup {
my ($self, $name) = @_;
my $reverse = $self->{reverse} ||= {};
return $reverse->{$name} if exists $reverse->{$name};
return undef unless my $route = $self->find($name);
return $reverse->{$name} = $route;
}
sub match {
my ($self, $c) = @_;
# Path (partial path gets priority)
my $req = $c->req;
my $path = $c->stash->{path};
if (defined $path) { $path = "/$path" if $path !~ m!^/! }
else { $path = $req->url->path->to_route }
# Method (HEAD will be treated as GET)
my $method = uc $req->method;
$method = 'GET' if $method eq 'HEAD';
# Check cache
my $ws = $c->tx->is_websocket ? 1 : 0;
my $match = Mojolicious::Routes::Match->new(root => $self);
$c->match($match);
my $cache = $self->cache;
if (my $result = $cache->get("$method:$path:$ws")) {
return $match->endpoint($result->{endpoint})->stack($result->{stack});
}
# Check routes
$match->match($c => {method => $method, path => $path, websocket => $ws});
return unless my $route = $match->endpoint;
$cache->set(
"$method:$path:$ws" => {endpoint => $route, stack => $match->stack});
}
sub route {
shift->add_child(Mojolicious::Routes::Route->new(@_))->children->[-1];
}
sub _action { shift->plugins->emit_chain(around_action => @_) }
sub _add {
my ($self, $attr, $name, $cb) = @_;
$self->$attr->{$name} = $cb;
return $self;
}
sub _callback {
my ($self, $c, $cb, $last) = @_;
$c->stash->{'mojo.routed'}++ if $last;
my $app = $c->app;
$app->log->debug('Routing to a callback.');
return _action($app, $c, $cb, $last);
}
sub _class {
my ($self, $c, $field) = @_;
# Application instance
return $field->{app} if ref $field->{app};
# Application class
my @classes;
my $class = $field->{controller} ? camelize($field->{controller}) : '';
if ($field->{app}) { push @classes, $field->{app} }
# Specific namespace
elsif (defined(my $ns = $field->{namespace})) {
if ($class) { push @classes, $ns ? "${ns}::$class" : $class }
elsif ($ns) { push @classes, $ns }
}
# All namespaces
elsif ($class) { push @classes, "${_}::$class" for @{$self->namespaces} }
# Try to load all classes
my $log = $c->app->log;
for my $class (@classes) {
# Failed
next unless defined(my $found = $self->_load($class));
return !$log->debug(qq{Class "$class" is not a controller.}) unless $found;
# Success
my $new = $class->new(%$c);
weaken $new->{$_} for qw(app tx);
return $new;
}
# Nothing found
$log->debug(qq{Controller "$classes[-1]" does not exist.}) if @classes;
return @classes ? undef : 0;
}
sub _controller {
my ($self, $old, $field, $last) = @_;
# Load and instantiate controller/application
my $new;
unless ($new = $self->_class($old, $field)) { return !!defined $new }
# Application
my $class = ref $new;
my $app = $old->app;
my $log = $app->log;
if (my $sub = $new->can('handler')) {
$log->debug(qq{Routing to application "$class".});
# Try to connect routes
if (my $sub = $new->can('routes')) {
my $r = $new->$sub;
weaken $r->parent($old->match->endpoint)->{parent} unless $r->parent;
}
$new->$sub($old);
$old->stash->{'mojo.routed'}++;
}
# Action
elsif (my $method = $field->{action}) {
if (!$self->is_hidden($method)) {
$log->debug(qq{Routing to controller "$class" and action "$method".});
if (my $sub = $new->can($method)) {
$old->stash->{'mojo.routed'}++ if $last;
return 1 if _action($app, $new, $sub, $last);
}
else { $log->debug('Action not found in controller.') }
}
else { $log->debug(qq{Action "$method" is not allowed.}) }
}
return undef;
}
sub _load {
my ($self, $app) = @_;
# Load unless already loaded
return 1 if $self->{loaded}{$app};
if (my $e = Mojo::Loader->new->load($app)) { ref $e ? die $e : return undef }
# Check base classes
return 0 unless first { $app->isa($_) } @{$self->base_classes};
return ++$self->{loaded}{$app};
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Routes - Always find your destination with routes!
=head1 SYNOPSIS
use Mojolicious::Routes;
# Simple route
my $r = Mojolicious::Routes->new;
$r->route('/')->to(controller => 'blog', action => 'welcome');
# More advanced routes
my $blog = $r->under('/blog');
$blog->get('/list')->to('blog#list');
$blog->get('/:id' => [id => qr/\d+/])->to('blog#show', id => 23);
$blog->patch(sub { shift->render(text => 'Go away!', status => 405) });
=head1 DESCRIPTION
L<Mojolicious::Routes> is the core of the L<Mojolicious> web framework.
See L<Mojolicious::Guides::Routing> for more.
=head1 ATTRIBUTES
L<Mojolicious::Routes> inherits all attributes from
L<Mojolicious::Routes::Route> and implements the following new ones.
=head2 base_classes
my $classes = $r->base_classes;
$r = $r->base_classes(['MyApp::Controller']);
Base classes used to identify controllers, defaults to
L<Mojolicious::Controller> and L<Mojo>.
=head2 cache
my $cache = $r->cache;
$r = $r->cache(Mojo::Cache->new);
Routing cache, defaults to a L<Mojo::Cache> object.
=head2 conditions
my $conditions = $r->conditions;
$r = $r->conditions({foo => sub {...}});
Contains all available conditions.
=head2 hidden
my $hidden = $r->hidden;
$r = $r->hidden([qw(attr has new)]);
Controller attributes and methods that are hidden from router, defaults to
C<attr>, C<has>, C<new> and C<tap>.
=head2 namespaces
my $namespaces = $r->namespaces;
$r = $r->namespaces(['Foo::Bar::Controller']);
Namespaces to load controllers from.
# Add another namespace to load controllers from
push @{$r->namespaces}, 'MyApp::MyController';
=head2 shortcuts
my $shortcuts = $r->shortcuts;
$r = $r->shortcuts({foo => sub {...}});
Contains all available shortcuts.
=head1 METHODS
L<Mojolicious::Routes> inherits all methods from
L<Mojolicious::Routes::Route> and implements the following new ones.
=head2 add_condition
$r = $r->add_condition(foo => sub {...});
Add a new condition.
=head2 add_shortcut
$r = $r->add_shortcut(foo => sub {...});
Add a new shortcut.
=head2 auto_render
$r->auto_render(Mojolicious::Controller->new);
Automatic rendering.
=head2 continue
$r->continue(Mojolicious::Controller->new);
Continue dispatch chain and emit the hook L<Mojolicious/"around_action"> for
every action.
=head2 dispatch
my $bool = $r->dispatch(Mojolicious::Controller->new);
Match routes with L</"match"> and dispatch with L</"continue">.
=head2 hide
$r = $r->hide(qw(foo bar));
Hide controller attributes and methods from router.
=head2 is_hidden
my $bool = $r->is_hidden('foo');
Check if controller attribute or method is hidden from router.
=head2 lookup
my $route = $r->lookup('foo');
Find route by name with L<Mojolicious::Routes::Route/"find"> and cache all
results for future lookups.
=head2 match
$r->match(Mojolicious::Controller->new);
Match routes with L<Mojolicious::Routes::Match>.
=head2 route
my $route = $r->route;
my $route = $r->route('/:action');
my $route = $r->route('/:action', action => qr/\w+/);
my $route = $r->route(format => 0);
Low-level generator for routes matching all HTTP request methods, returns a
L<Mojolicious::Routes::Route> object.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_ROUTES
$fatpacked{"Mojolicious/Routes/Match.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_ROUTES_MATCH';
package Mojolicious::Routes::Match;
use Mojo::Base -base;
use Mojo::Util;
has current => 0;
has [qw(endpoint root)];
has stack => sub { [] };
sub match { $_[0]->_match($_[0]->root, $_[1], $_[2]) }
sub path_for {
my ($self, $name, %values) = (shift, Mojo::Util::_options(@_));
# Current route
my $route;
if (!$name || $name eq 'current') {
return {} unless $route = $self->endpoint;
}
# Find endpoint
else { return {path => $name} unless $route = $self->root->lookup($name) }
# Merge values (clear format)
my $captures = $self->stack->[-1] || {};
%values = (%$captures, format => undef, %values);
my $pattern = $route->pattern;
$values{format}
//= defined $captures->{format}
? $captures->{format}
: $pattern->defaults->{format}
if $pattern->constraints->{format};
my $path = $route->render(\%values);
return {path => $path, websocket => $route->has_websocket};
}
sub _match {
my ($self, $r, $c, $options) = @_;
# Pattern
my $path = $options->{path};
my $partial = $r->partial;
my $detect = (my $endpoint = $r->is_endpoint) && !$partial;
return undef
unless my $captures = $r->pattern->match_partial(\$path, $detect);
local $options->{path} = $path;
local @{$self->{captures} ||= {}}{keys %$captures} = values %$captures;
$captures = $self->{captures};
# Method
my $methods = $r->via;
return undef if $methods && !grep { $_ eq $options->{method} } @$methods;
# Conditions
if (my $over = $r->over) {
my $conditions = $self->{conditions} ||= $self->root->conditions;
for (my $i = 0; $i < @$over; $i += 2) {
return undef unless my $condition = $conditions->{$over->[$i]};
return undef if !$condition->($r, $c, $captures, $over->[$i + 1]);
}
}
# WebSocket
return undef if $r->is_websocket && !$options->{websocket};
# Partial
my $empty = !length $path || $path eq '/';
if ($partial) {
$captures->{path} = $path;
$self->endpoint($r);
$empty = 1;
}
# Endpoint (or intermediate destination)
if (($endpoint && $empty) || $r->inline) {
push @{$self->stack}, {%$captures};
if ($endpoint && $empty) {
my $format = $captures->{format};
if ($format) { $_->{format} = $format for @{$self->stack} }
return !!$self->endpoint($r);
}
delete @$captures{qw(app cb)};
}
# Match children
my @snapshot = $r->parent ? ([@{$self->stack}], $captures) : ([], {});
for my $child (@{$r->children}) {
return 1 if $self->_match($child, $c, $options);
$self->stack([@{$snapshot[0]}])->{captures} = $snapshot[1];
}
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Routes::Match - Find routes
=head1 SYNOPSIS
use Mojolicious::Controller;
use Mojolicious::Routes;
use Mojolicious::Routes::Match;
# Routes
my $r = Mojolicious::Routes->new;
$r->get('/:controller/:action');
$r->put('/:controller/:action');
# Match
my $c = Mojolicious::Controller->new;
my $match = Mojolicious::Routes::Match->new(root => $r);
$match->match($c => {method => 'PUT', path => '/foo/bar'});
say $match->stack->[0]{controller};
say $match->stack->[0]{action};
# Render
say $match->path_for->{path};
say $match->path_for(action => 'baz')->{path};
=head1 DESCRIPTION
L<Mojolicious::Routes::Match> finds routes in L<Mojolicious::Routes>
structures.
=head1 ATTRIBUTES
L<Mojolicious::Routes::Match> implements the following attributes.
=head2 current
my $current = $match->current;
$match = $match->current(2);
Current position on the L</"stack">, defaults to C<0>.
=head2 endpoint
my $route = $match->endpoint;
$match = $match->endpoint(Mojolicious::Routes::Route->new);
The route endpoint that matched, usually a L<Mojolicious::Routes::Route>
object.
=head2 root
my $root = $match->root;
$match = $match->root(Mojolicious::Routes->new);
The root of the route structure, usually a L<Mojolicious::Routes> object.
=head2 stack
my $stack = $match->stack;
$match = $match->stack([{action => 'foo'}, {action => 'bar'}]);
Captured parameters with nesting history.
=head1 METHODS
L<Mojolicious::Routes::Match> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 match
$match->match(Mojolicious::Controller->new, {method => 'GET', path => '/'});
Match controller and options against L</"root"> to find appropriate
L</"endpoint">.
=head2 path_for
my $info = $match->path_for;
my $info = $match->path_for(foo => 'bar');
my $info = $match->path_for({foo => 'bar'});
my $info = $match->path_for('named');
my $info = $match->path_for('named', foo => 'bar');
my $info = $match->path_for('named', {foo => 'bar'});
Render matching route with parameters into path.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_ROUTES_MATCH
$fatpacked{"Mojolicious/Routes/Pattern.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_ROUTES_PATTERN';
package Mojolicious::Routes::Pattern;
use Mojo::Base -base;
has [qw(constraints defaults)] => sub { {} };
has [qw(format_regex pattern regex)];
has placeholder_start => ':';
has [qw(placeholders tree)] => sub { [] };
has quote_end => ')';
has quote_start => '(';
has relaxed_start => '#';
has wildcard_start => '*';
sub match {
my ($self, $path, $detect) = @_;
my $captures = $self->match_partial(\$path, $detect);
return !$path || $path eq '/' ? $captures : undef;
}
sub match_partial {
my ($self, $pathref, $detect) = @_;
# Compile on demand
$self->_compile unless $self->{regex};
$self->_compile_format if $detect && !$self->{format_regex};
# Match
return undef unless my @captures = $$pathref =~ $self->regex;
$$pathref = ${^POSTMATCH};
# Merge captures
my $captures = {%{$self->defaults}};
for my $placeholder (@{$self->placeholders}) {
last unless @captures;
my $capture = shift @captures;
$captures->{$placeholder} = $capture if defined $capture;
}
# Format
return $captures unless $detect && (my $regex = $self->format_regex);
return undef unless $$pathref =~ $regex;
$captures->{format} = $1 if defined $1;
$$pathref = '';
return $captures;
}
sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my $self = shift;
my $pattern = @_ % 2 ? (shift // '/') : '/';
$pattern =~ s!^/*|/+!/!g;
return $self->constraints({@_}) if $pattern eq '/';
$pattern =~ s!/$!!;
return $self->constraints({@_})->_tokenize($pattern);
}
sub render {
my ($self, $values, $endpoint) = @_;
# Placeholders can only be optional without a format
my $optional = !(my $format = $values->{format});
my $str = '';
for my $token (reverse @{$self->tree}) {
my ($op, $value) = @$token;
my $fragment = '';
# Text
if ($op eq 'text') { ($fragment, $optional) = ($value, 0) }
# Slash
elsif ($op eq 'slash') { $fragment = '/' unless $optional }
# Placeholder
else {
my $default = $self->defaults->{$value};
$fragment = $values->{$value} // $default // '';
if (!defined $default || ($default ne $fragment)) { $optional = 0 }
elsif ($optional) { $fragment = '' }
}
$str = "$fragment$str";
}
# Format can be optional
return $endpoint && $format ? "$str.$format" : $str;
}
sub _compile {
my $self = shift;
my $placeholders = $self->placeholders;
my $constraints = $self->constraints;
my $defaults = $self->defaults;
my $block = my $regex = '';
my $optional = 1;
for my $token (reverse @{$self->tree}) {
my ($op, $value) = @$token;
my $fragment = '';
# Text
if ($op eq 'text') { ($fragment, $optional) = (quotemeta $value, 0) }
# Slash
elsif ($op eq 'slash') {
$regex = ($optional ? "(?:/$block)?" : "/$block") . $regex;
($block, $optional) = ('', 1);
next;
}
# Placeholder
else {
unshift @$placeholders, $value;
# Placeholder
if ($op eq 'placeholder') { $fragment = '([^/\.]+)' }
# Relaxed
elsif ($op eq 'relaxed') { $fragment = '([^/]+)' }
# Wildcard
else { $fragment = '(.+)' }
# Custom regex
if (my $c = $constraints->{$value}) { $fragment = _compile_req($c) }
# Optional placeholder
exists $defaults->{$value} ? ($fragment .= '?') : ($optional = 0);
}
$block = "$fragment$block";
}
# Not rooted with a slash
$regex = "$block$regex" if $block;
$self->regex(qr/^$regex/ps);
}
sub _compile_format {
my $self = shift;
# Default regex
my $format = $self->constraints->{format};
return $self->format_regex(qr!^/?(?:\.([^/]+))?$!) unless defined $format;
# No regex
return undef unless $format;
# Compile custom regex
my $regex = '\.' . _compile_req($format);
$regex = "(?:$regex)?" if $self->defaults->{format};
$self->format_regex(qr!^/?$regex$!);
}
sub _compile_req {
my $req = shift;
return "($req)" if ref $req ne 'ARRAY';
return '(' . join('|', map {quotemeta} reverse sort @$req) . ')';
}
sub _tokenize {
my ($self, $pattern) = @_;
my $quote_end = $self->quote_end;
my $quote_start = $self->quote_start;
my $placeholder = $self->placeholder_start;
my $relaxed = $self->relaxed_start;
my $wildcard = $self->wildcard_start;
my (@tree, $inside, $quoted);
for my $char (split '', $pattern) {
# Quote start
if ($char eq $quote_start) {
push @tree, ['placeholder', ''];
($inside, $quoted) = (1, 1);
}
# Placeholder start
elsif ($char eq $placeholder) {
push @tree, ['placeholder', ''] unless $inside++;
}
# Relaxed or wildcard start (upgrade when quoted)
elsif ($char eq $relaxed || $char eq $wildcard) {
push @tree, ['placeholder', ''] unless $quoted;
$tree[-1][0] = $char eq $relaxed ? 'relaxed' : 'wildcard';
$inside = 1;
}
# Quote end
elsif ($char eq $quote_end) { ($inside, $quoted) = (0, 0) }
# Slash (first slash is text for optimizations)
elsif ($char eq '/') {
push @tree, @tree ? ['slash'] : ['text', '/'];
$inside = 0;
}
# Placeholder, relaxed or wildcard
elsif ($inside) { $tree[-1][-1] .= $char }
# Text (optimize text followed by slash followed by text)
elsif ($tree[-1][0] eq 'text') { $tree[-1][-1] .= $char }
elsif ($tree[-2] && $tree[-2][0] eq 'text' && $tree[-1][0] eq 'slash') {
pop @tree && ($tree[-1][-1] .= "/$char");
}
else { push @tree, ['text', $char] }
}
return $self->pattern($pattern)->tree(\@tree);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Routes::Pattern - Routes pattern engine
=head1 SYNOPSIS
use Mojolicious::Routes::Pattern;
# Create pattern
my $pattern = Mojolicious::Routes::Pattern->new('/test/:name');
# Match routes
my $captures = $pattern->match('/test/sebastian');
say $captures->{name};
=head1 DESCRIPTION
L<Mojolicious::Routes::Pattern> is the core of L<Mojolicious::Routes>.
=head1 ATTRIBUTES
L<Mojolicious::Routes::Pattern> implements the following attributes.
=head2 constraints
my $constraints = $pattern->constraints;
$pattern = $pattern->constraints({foo => qr/\w+/});
Regular expression constraints.
=head2 defaults
my $defaults = $pattern->defaults;
$pattern = $pattern->defaults({foo => 'bar'});
Default parameters.
=head2 format_regex
my $regex = $pattern->format_regex;
$pattern = $pattern->format_regex($regex);
Compiled regular expression for format matching.
=head2 pattern
my $raw = $pattern->pattern;
$pattern = $pattern->pattern('/(foo)/(bar)');
Raw unparsed pattern.
=head2 placeholder_start
my $start = $pattern->placeholder_start;
$pattern = $pattern->placeholder_start(':');
Character indicating a placeholder, defaults to C<:>.
=head2 placeholders
my $placeholders = $pattern->placeholders;
$pattern = $pattern->placeholders(['foo', 'bar']);
Placeholder names.
=head2 quote_end
my $end = $pattern->quote_end;
$pattern = $pattern->quote_end(']');
Character indicating the end of a quoted placeholder, defaults to C<)>.
=head2 quote_start
my $start = $pattern->quote_start;
$pattern = $pattern->quote_start('[');
Character indicating the start of a quoted placeholder, defaults to C<(>.
=head2 regex
my $regex = $pattern->regex;
$pattern = $pattern->regex($regex);
Pattern in compiled regular expression form.
=head2 relaxed_start
my $start = $pattern->relaxed_start;
$pattern = $pattern->relaxed_start('*');
Character indicating a relaxed placeholder, defaults to C<#>.
=head2 tree
my $tree = $pattern->tree;
$pattern = $pattern->tree([['text', '/foo']]);
Pattern in parsed form. Note that this structure should only be used very
carefully since it is very dynamic.
=head2 wildcard_start
my $start = $pattern->wildcard_start;
$pattern = $pattern->wildcard_start('*');
Character indicating the start of a wildcard placeholder, defaults to C<*>.
=head1 METHODS
L<Mojolicious::Routes::Pattern> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 match
my $captures = $pattern->match('/foo/bar');
my $captures = $pattern->match('/foo/bar', 1);
Match pattern against entire path, format detection is disabled by default.
=head2 match_partial
my $captures = $pattern->match_partial(\$path);
my $captures = $pattern->match_partial(\$path, 1);
Match pattern against path and remove matching parts, format detection is
disabled by default.
=head2 new
my $pattern = Mojolicious::Routes::Pattern->new('/:action');
my $pattern
= Mojolicious::Routes::Pattern->new('/:action', action => qr/\w+/);
my $pattern = Mojolicious::Routes::Pattern->new(format => 0);
Construct a new L<Mojolicious::Routes::Pattern> object and L</"parse"> pattern
if necessary.
=head2 parse
$pattern = $pattern->parse('/:action');
$pattern = $pattern->parse('/:action', action => qr/\w+/);
$pattern = $pattern->parse(format => 0);
Parse pattern.
=head2 render
my $path = $pattern->render({action => 'foo'});
my $path = $pattern->render({action => 'foo'}, 1);
Render pattern into a path with parameters, format rendering is disabled by
default.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_ROUTES_PATTERN
$fatpacked{"Mojolicious/Routes/Route.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_ROUTES_ROUTE';
package Mojolicious::Routes::Route;
use Mojo::Base -base;
use Carp ();
use Mojo::Util;
use Mojolicious::Routes::Pattern;
use Scalar::Util ();
has [qw(inline parent partial)];
has 'children' => sub { [] };
has pattern => sub { Mojolicious::Routes::Pattern->new };
sub AUTOLOAD {
my $self = shift;
my ($package, $method) = our $AUTOLOAD =~ /^(.+)::(.+)$/;
Carp::croak "Undefined subroutine &${package}::$method called"
unless Scalar::Util::blessed $self && $self->isa(__PACKAGE__);
# Call shortcut with current route
Carp::croak qq{Can't locate object method "$method" via package "$package"}
unless my $shortcut = $self->root->shortcuts->{$method};
return $self->$shortcut(@_);
}
sub add_child {
my ($self, $route) = @_;
Scalar::Util::weaken $route->remove->parent($self)->{parent};
push @{$self->children}, $route;
return $self;
}
sub any { shift->_generate_route(ref $_[0] eq 'ARRAY' ? shift : [], @_) }
sub bridge { shift->route(@_)->inline(1) }
sub delete { shift->_generate_route(DELETE => @_) }
sub detour { shift->partial(1)->to(@_) }
sub find {
my ($self, $name) = @_;
my @children = (@{$self->children});
my $candidate;
while (my $child = shift @children) {
# Match
$candidate = $child->has_custom_name ? return $child : $child
if $child->name eq $name;
# Search children too
push @children, @{$child->children};
}
return $candidate;
}
sub get { shift->_generate_route(GET => @_) }
# DEPRECATED in Tiger Face!
sub has_conditions {
Mojo::Util::deprecated
'Mojolicious::Routes::Route::has_conditions is DEPRECATED';
my $self = shift;
return 1 if @{$self->over || []};
return undef unless my $parent = $self->parent;
return $parent->has_conditions;
}
sub has_custom_name { !!shift->{custom} }
sub has_websocket {
my $self = shift;
return $self->{has_websocket} if exists $self->{has_websocket};
return $self->{has_websocket} = grep { $_->is_websocket } @{$self->_chain};
}
sub is_endpoint { $_[0]->inline ? undef : !@{$_[0]->children} }
sub is_websocket { !!shift->{websocket} }
sub name {
my $self = shift;
return $self->{name} unless @_;
@$self{qw(name custom)} = (shift, 1);
return $self;
}
sub new { shift->SUPER::new->parse(@_) }
sub options { shift->_generate_route(OPTIONS => @_) }
sub over {
my $self = shift;
# Routes with conditions can't be cached
return $self->{over} unless @_;
my $conditions = ref $_[0] eq 'ARRAY' ? $_[0] : [@_];
return $self unless @$conditions;
$self->{over} = $conditions;
$self->root->cache->max_keys(0);
return $self;
}
sub parse {
my $self = shift;
$self->{name} = $self->pattern->parse(@_)->pattern // '';
$self->{name} =~ s/\W+//g;
return $self;
}
sub patch { shift->_generate_route(PATCH => @_) }
sub post { shift->_generate_route(POST => @_) }
sub put { shift->_generate_route(PUT => @_) }
sub remove {
my $self = shift;
return $self unless my $parent = $self->parent;
@{$parent->children} = grep { $_ ne $self } @{$parent->children};
return $self->parent(undef);
}
sub render {
my ($self, $values) = @_;
my $path = join '',
map { $_->pattern->render($values, !@{$_->children} && !$_->partial) }
@{$self->_chain};
return $path || '/';
}
sub root { shift->_chain->[0] }
sub route {
my $self = shift;
my $route = $self->add_child($self->new(@_))->children->[-1];
my $format = $self->pattern->constraints->{format};
$route->pattern->constraints->{format} //= 0 if defined $format && !$format;
return $route;
}
sub to {
my $self = shift;
my $pattern = $self->pattern;
return $pattern->defaults unless @_;
my ($shortcut, %defaults) = Mojo::Util::_options(@_);
if ($shortcut) {
# Application
if (ref $shortcut || $shortcut =~ /^[\w:]+$/) {
$defaults{app} = $shortcut;
}
# Controller and action
elsif ($shortcut =~ /^([\w\-:]+)?\#(\w+)?$/) {
$defaults{controller} = $1 if defined $1;
$defaults{action} = $2 if defined $2;
}
}
@{$pattern->defaults}{keys %defaults} = values %defaults;
return $self;
}
sub to_string {
join '', map { $_->pattern->pattern // '' } @{shift->_chain};
}
sub under { shift->_generate_route(under => @_) }
sub via {
my $self = shift;
return $self->{via} unless @_;
my $methods = [map uc($_), @{ref $_[0] ? $_[0] : [@_]}];
$self->{via} = $methods if @$methods;
return $self;
}
sub websocket {
my $route = shift->get(@_);
$route->{websocket} = 1;
return $route;
}
sub _chain {
my @chain = (my $parent = shift);
unshift @chain, $parent while $parent = $parent->parent;
return \@chain;
}
sub _generate_route {
my ($self, $methods, @args) = @_;
my (@conditions, @constraints, %defaults, $name, $pattern);
while (defined(my $arg = shift @args)) {
# First scalar is the pattern
if (!ref $arg && !$pattern) { $pattern = $arg }
# Scalar
elsif (!ref $arg && @args) { push @conditions, $arg, shift @args }
# Last scalar is the route name
elsif (!ref $arg) { $name = $arg }
# Callback
elsif (ref $arg eq 'CODE') { $defaults{cb} = $arg }
# Constraints
elsif (ref $arg eq 'ARRAY') { push @constraints, @$arg }
# Defaults
elsif (ref $arg eq 'HASH') { %defaults = (%defaults, %$arg) }
}
my $route
= $self->route($pattern, @constraints)->over(\@conditions)->to(\%defaults);
$methods eq 'under' ? $route->inline(1) : $route->via($methods);
return defined $name ? $route->name($name) : $route;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Routes::Route - Route
=head1 SYNOPSIS
use Mojolicious::Routes::Route;
my $r = Mojolicious::Routes::Route->new;
=head1 DESCRIPTION
L<Mojolicious::Routes::Route> is the route container used by
L<Mojolicious::Routes>.
=head1 ATTRIBUTES
L<Mojolicious::Routes::Route> implements the following attributes.
=head2 children
my $children = $r->children;
$r = $r->children([Mojolicious::Routes::Route->new]);
The children of this route, used for nesting routes.
=head2 inline
my $bool = $r->inline;
$r = $r->inline($bool);
Allow L</"under"> semantics for this route.
=head2 parent
my $parent = $r->parent;
$r = $r->parent(Mojolicious::Routes::Route->new);
The parent of this route, usually a L<Mojolicious::Routes::Route> object.
=head2 partial
my $bool = $r->partial;
$r = $r->partial($bool);
Route has no specific end, remaining characters will be captured in C<path>.
=head2 pattern
my $pattern = $r->pattern;
$r = $r->pattern(Mojolicious::Routes::Pattern->new);
Pattern for this route, defaults to a L<Mojolicious::Routes::Pattern> object.
=head1 METHODS
L<Mojolicious::Routes::Route> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 add_child
$r = $r->add_child(Mojolicious::Routes::Route->new);
Add a new child to this route, it will be automatically removed from its
current parent if necessary.
# Reattach route
$r->add_child($r->find('foo'));
=head2 any
my $route = $r->any('/:foo');
my $route = $r->any('/:foo' => sub {...});
my $route = $r->any('/:foo' => {foo => 'bar'} => sub {...});
my $route = $r->any('/:foo' => [foo => qr/\w+/] => sub {...});
my $route = $r->any([qw(GET POST)] => '/:foo' => sub {...});
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
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');
my $route = $r->delete('/:foo' => sub {...});
my $route = $r->delete('/:foo' => {foo => 'bar'} => sub {...});
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
variations.
$r->delete('/user')->to('user#remove');
=head2 detour
$r = $r->detour(action => 'foo');
$r = $r->detour('controller#action');
$r = $r->detour(Mojolicious->new, foo => 'bar');
$r = $r->detour('MyApp', {foo => 'bar'});
Set default parameters for this route and allow partial matching to simplify
application embedding, takes the same arguments as L</"to">.
=head2 find
my $route = $r->find('foo');
Find child route by name, custom names have precedence over automatically
generated ones.
$r->find('show_user')->to(foo => 'bar');
=head2 get
my $route = $r->get('/:foo');
my $route = $r->get('/:foo' => sub {...});
my $route = $r->get('/:foo' => {foo => 'bar'} => sub {...});
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.
$r->get('/user')->to('user#show');
=head2 has_custom_name
my $bool = $r->has_custom_name;
Check if this route has a custom name.
=head2 has_websocket
my $bool = $r->has_websocket;
Check if this route has a WebSocket ancestor and cache the result for future
checks.
=head2 is_endpoint
my $bool = $r->is_endpoint;
Check if this route qualifies as an endpoint.
=head2 is_websocket
my $bool = $r->is_websocket;
Check if this route is a WebSocket.
=head2 name
my $name = $r->name;
$r = $r->name('foo');
The name of this route, defaults to an automatically generated name based on
the route pattern. Note that the name C<current> is reserved for referring to
the current route.
$r->get('/user')->to('user#show')->name('show_user');
=head2 new
my $r = Mojolicious::Routes::Route->new;
my $r = Mojolicious::Routes::Route->new('/:controller/:action');
Construct a new L<Mojolicious::Routes::Route> object and L</"parse"> pattern
if necessary.
=head2 options
my $route = $r->options('/:foo');
my $route = $r->options('/:foo' => sub {...});
my $route = $r->options('/:foo' => {foo => 'bar'} => sub {...});
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
variations.
$r->options('/user')->to('user#overview');
=head2 over
my $over = $r->over;
$r = $r->over(foo => 1);
$r = $r->over(foo => 1, bar => {baz => 'yada'});
$r = $r->over([foo => 1, bar => {baz => 'yada'}]);
Activate conditions for this route. Note that this automatically disables the
routing cache, since conditions are too complex for caching.
$r->get('/foo')->over(host => qr/mojolicio\.us/)->to('foo#bar');
=head2 parse
$r = $r->parse('/:action');
$r = $r->parse('/:action', action => qr/\w+/);
$r = $r->parse(format => 0);
Parse pattern.
=head2 patch
my $route = $r->patch('/:foo');
my $route = $r->patch('/:foo' => sub {...});
my $route = $r->patch('/:foo' => {foo => 'bar'} => sub {...});
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.
$r->patch('/user')->to('user#update');
=head2 post
my $route = $r->post('/:foo');
my $route = $r->post('/:foo' => sub {...});
my $route = $r->post('/:foo' => {foo => 'bar'} => sub {...});
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.
$r->post('/user')->to('user#create');
=head2 put
my $route = $r->put('/:foo');
my $route = $r->put('/:foo' => sub {...});
my $route = $r->put('/:foo' => {foo => 'bar'} => sub {...});
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.
$r->put('/user')->to('user#replace');
=head2 remove
$r = $r->remove;
Remove route from parent.
# Remove route completely
$r->find('foo')->remove;
# Reattach route to new parent
$r->route('/foo')->add_child($r->find('bar')->remove);
=head2 render
my $path = $r->render({foo => 'bar'});
Render route with parameters into a path.
=head2 root
my $root = $r->root;
The L<Mojolicious::Routes> object this route is a descendant of.
=head2 route
my $route = $r->route;
my $route = $r->route('/:action');
my $route = $r->route('/:action', action => qr/\w+/);
my $route = $r->route(format => 0);
Low-level generator for routes matching all HTTP request methods, returns a
L<Mojolicious::Routes::Route> object.
=head2 to
my $defaults = $r->to;
$r = $r->to(action => 'foo');
$r = $r->to({action => 'foo'});
$r = $r->to('controller#action');
$r = $r->to('controller#action', foo => 'bar');
$r = $r->to('controller#action', {foo => 'bar'});
$r = $r->to(Mojolicious->new);
$r = $r->to(Mojolicious->new, foo => 'bar');
$r = $r->to(Mojolicious->new, {foo => 'bar'});
$r = $r->to('MyApp');
$r = $r->to('MyApp', foo => 'bar');
$r = $r->to('MyApp', {foo => 'bar'});
Set default parameters for this route.
=head2 to_string
my $str = $r->to_string;
Stringify the whole route.
=head2 under
my $route = $r->under(sub {...});
my $route = $r->under('/:foo' => sub {...});
my $route = $r->under('/:foo' => {foo => 'bar'});
my $route = $r->under('/:foo' => [foo => qr/\w+/]);
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
more argument variations.
my $auth = $r->under('/user')->to('user#auth');
$auth->get('/show')->to('#show');
$auth->post('/create')->to('#create');
=head2 via
my $methods = $r->via;
$r = $r->via('GET');
$r = $r->via(qw(GET POST));
$r = $r->via([qw(GET POST)]);
Restrict HTTP methods this route is allowed to handle, defaults to no
restrictions.
$r->route('/foo')->via(qw(GET POST))->to('foo#bar');
=head2 websocket
my $route = $r->websocket('/:foo');
my $route = $r->websocket('/:foo' => sub {...});
my $route = $r->websocket('/:foo' => {foo => 'bar'} => sub {...});
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
variations.
$r->websocket('/echo')->to('example#echo');
=head1 AUTOLOAD
In addition to the L</"ATTRIBUTES"> and L</"METHODS"> above you can also call
shortcuts provided by L</"root"> on L<Mojolicious::Routes::Route> objects.
# Add a "firefox" shortcut
$r->root->add_shortcut(firefox => sub {
my ($r, $path) = @_;
$r->get($path, agent => qr/Firefox/);
});
# Use "firefox" shortcut to generate routes
$r->firefox('/welcome')->to('firefox#welcome');
$r->firefox('/bye')->to('firefox#bye');
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_ROUTES_ROUTE
$fatpacked{"Mojolicious/Sessions.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_SESSIONS';
package Mojolicious::Sessions;
use Mojo::Base -base;
use Mojo::JSON;
use Mojo::Util qw(b64_decode b64_encode);
has [qw(cookie_domain secure)];
has cookie_name => 'mojolicious';
has cookie_path => '/';
has default_expiration => 3600;
has deserialize => sub { \&Mojo::JSON::j };
has serialize => sub { \&Mojo::JSON::encode_json };
sub load {
my ($self, $c) = @_;
return unless $c->req->headers->cookie;
return unless my $value = $c->signed_cookie($self->cookie_name);
$value =~ y/-/=/;
return unless my $session = $self->deserialize->(b64_decode $value);
# "expiration" value is inherited
my $expiration = $session->{expiration} // $self->default_expiration;
return if !(my $expires = delete $session->{expires}) && $expiration;
return if defined $expires && $expires <= time;
my $stash = $c->stash;
return unless $stash->{'mojo.active_session'} = keys %$session;
$stash->{'mojo.session'} = $session;
$session->{flash} = delete $session->{new_flash} if $session->{new_flash};
}
sub store {
my ($self, $c) = @_;
# Make sure session was active
my $stash = $c->stash;
return unless my $session = $stash->{'mojo.session'};
return unless keys %$session || $stash->{'mojo.active_session'};
# Don't reset flash for static files
my $old = delete $session->{flash};
$session->{new_flash} = $old if $stash->{'mojo.static'};
delete $session->{new_flash} unless keys %{$session->{new_flash}};
# Generate "expires" value from "expiration" if necessary
my $expiration = $session->{expiration} // $self->default_expiration;
my $default = delete $session->{expires};
$session->{expires} = $default || time + $expiration
if $expiration || $default;
my $value = b64_encode($self->serialize->($session), '');
$value =~ y/=/-/;
my $options = {
domain => $self->cookie_domain,
expires => $session->{expires},
httponly => 1,
path => $self->cookie_path,
secure => $self->secure
};
$c->signed_cookie($self->cookie_name, $value, $options);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Sessions - Signed cookie based session manager
=head1 SYNOPSIS
use Mojolicious::Sessions;
my $sessions = Mojolicious::Sessions->new;
$sessions->cookie_name('myapp');
$sessions->default_expiration(86400);
=head1 DESCRIPTION
L<Mojolicious::Sessions> manages simple signed cookie based sessions for
L<Mojolicious>. All data gets serialized with L<Mojo::JSON> and stored Base64
encoded on the client-side, but is protected from unwanted changes with a
HMAC-SHA1 signature.
=head1 ATTRIBUTES
L<Mojolicious::Sessions> implements the following attributes.
=head2 cookie_domain
my $domain = $sessions->cookie_domain;
$sessions = $sessions->cookie_domain('.example.com');
Domain for session cookies, not defined by default.
=head2 cookie_name
my $name = $sessions->cookie_name;
$sessions = $sessions->cookie_name('session');
Name for session cookies, defaults to C<mojolicious>.
=head2 cookie_path
my $path = $sessions->cookie_path;
$sessions = $sessions->cookie_path('/foo');
Path for session cookies, defaults to C</>.
=head2 default_expiration
my $time = $sessions->default_expiration;
$sessions = $sessions->default_expiration(3600);
Default time for sessions to expire in seconds from now, defaults to C<3600>.
The expiration timeout gets refreshed for every request. Setting the value to
C<0> will allow sessions to persist until the browser window is closed, this
can have security implications though. For more control you can also use the
C<expiration> and C<expires> session values.
# Expiration date in seconds from now (persists between requests)
$c->session(expiration => 604800);
# Expiration date as absolute epoch time (only valid for one request)
$c->session(expires => time + 604800);
# Delete whole session by setting an expiration date in the past
$c->session(expires => 1);
=head2 deserialize
my $cb = $sessions->deserialize;
$sessions = $sessions->deserialize(sub {...});
A callback used to deserialize sessions, defaults to L<Mojo::JSON/"j">.
$sessions->deserialize(sub {
my $bytes = shift;
return {};
});
=head2 secure
my $bool = $sessions->secure;
$sessions = $sessions->secure($bool);
Set the secure flag on all session cookies, so that browsers send them only
over HTTPS connections.
=head2 serialize
my $cb = $sessions->serialize;
$sessions = $sessions->serialize(sub {...});
A callback used to serialize sessions, defaults to
L<Mojo::JSON/"encode_json">.
$sessions->serialize(sub {
my $hash = shift;
return '';
});
=head1 METHODS
L<Mojolicious::Sessions> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 load
$sessions->load(Mojolicious::Controller->new);
Load session data from signed cookie.
=head2 store
$sessions->store(Mojolicious::Controller->new);
Store session data in signed cookie.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_SESSIONS
$fatpacked{"Mojolicious/Static.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_STATIC';
package Mojolicious::Static;
use Mojo::Base -base;
use File::Spec::Functions 'catfile';
use Mojo::Asset::File;
use Mojo::Asset::Memory;
use Mojo::Date;
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util 'md5_sum';
has classes => sub { ['main'] };
has paths => sub { [] };
# Bundled files
my $HOME = Mojo::Home->new;
my $PUBLIC = $HOME->parse($HOME->mojo_lib_dir)->rel_dir('Mojolicious/public');
my $LOADER = Mojo::Loader->new;
sub dispatch {
my ($self, $c) = @_;
# Method (GET or HEAD)
my $req = $c->req;
my $method = $req->method;
return undef unless $method eq 'GET' || $method eq 'HEAD';
# Canonical path
my $stash = $c->stash;
my $path = $req->url->path;
$path = $stash->{path} ? $path->new($stash->{path}) : $path->clone;
return undef unless my @parts = @{$path->canonicalize->parts};
# Serve static file and prevent directory traversal
return undef if $parts[0] eq '..' || !$self->serve($c, join('/', @parts));
$stash->{'mojo.static'}++;
return !!$c->rendered;
}
sub file {
my ($self, $rel) = @_;
# Search all paths
for my $path (@{$self->paths}) {
next unless my $asset = $self->_get_file(catfile $path, split('/', $rel));
return $asset;
}
# Search DATA
if (my $asset = $self->_get_data_file($rel)) { return $asset }
# Search bundled files
return $self->_get_file(catfile($PUBLIC, split('/', $rel)));
}
sub is_fresh {
my ($self, $c, $options) = @_;
my $res_headers = $c->res->headers;
my ($last, $etag) = @$options{qw(last_modified etag)};
$res_headers->last_modified(Mojo::Date->new($last)) if $last;
$res_headers->etag($etag = qq{"$etag"}) if $etag;
# Unconditional
my $req_headers = $c->req->headers;
my $match = $req_headers->if_none_match;
return undef unless (my $since = $req_headers->if_modified_since) || $match;
# If-None-Match
return undef if $match && ($etag // $res_headers->etag // '') ne $match;
# If-Modified-Since
return !!$match unless ($last //= $res_headers->last_modified) && $since;
return _epoch($last) <= (_epoch($since) // 0);
}
sub serve {
my ($self, $c, $rel) = @_;
return undef unless my $asset = $self->file($rel);
my $headers = $c->res->headers;
return !!$self->serve_asset($c, $asset) if $headers->content_type;
# Content-Type
my $types = $c->app->types;
my $type = $rel =~ /\.(\w+)$/ ? $types->type($1) : undef;
$headers->content_type($type || $types->type('txt'));
return !!$self->serve_asset($c, $asset);
}
sub serve_asset {
my ($self, $c, $asset) = @_;
# Last-Modified and ETag
my $res = $c->res;
$res->code(200)->headers->accept_ranges('bytes');
my $mtime = $asset->mtime;
my $options = {etag => md5_sum($mtime), last_modified => $mtime};
return $res->code(304) if $self->is_fresh($c, $options);
# Range
return $res->content->asset($asset)
unless my $range = $c->req->headers->range;
# Not satisfiable
return $res->code(416) unless my $size = $asset->size;
return $res->code(416) unless $range =~ m/^bytes=(\d+)?-(\d+)?/;
my ($start, $end) = ($1 // 0, defined $2 && $2 < $size ? $2 : $size - 1);
return $res->code(416) if $start > $end;
# Satisfiable
$res->code(206)->headers->content_length($end - $start + 1)
->content_range("bytes $start-$end/$size");
return $res->content->asset($asset->start_range($start)->end_range($end));
}
sub _epoch { Mojo::Date->new(shift)->epoch }
sub _get_data_file {
my ($self, $rel) = @_;
# Protect files without extensions and templates with two extensions
return undef if $rel !~ /\.\w+$/ || $rel =~ /\.\w+\.\w+$/;
$self->_warmup unless $self->{index};
# Find file
return undef
unless defined(my $data = $LOADER->data($self->{index}{$rel}, $rel));
return Mojo::Asset::Memory->new->add_chunk($data);
}
sub _get_file {
my ($self, $path) = @_;
no warnings 'newline';
return -f $path && -r $path ? Mojo::Asset::File->new(path => $path) : undef;
}
sub _warmup {
my $self = shift;
my $index = $self->{index} = {};
for my $class (reverse @{$self->classes}) {
$index->{$_} = $class for keys %{$LOADER->data($class)};
}
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Static - Serve static files
=head1 SYNOPSIS
use Mojolicious::Static;
my $static = Mojolicious::Static->new;
push @{$static->classes}, 'MyApp::Controller::Foo';
push @{$static->paths}, '/home/sri/public';
=head1 DESCRIPTION
L<Mojolicious::Static> is a static file server with C<Range>,
C<If-Modified-Since> and C<If-None-Match> support based on
L<RFC 7232|http://tools.ietf.org/html/rfc7232> and
L<RFC 7233|http://tools.ietf.org/html/rfc7233>.
=head1 ATTRIBUTES
L<Mojolicious::Static> implements the following attributes.
=head2 classes
my $classes = $static->classes;
$static = $static->classes(['main']);
Classes to use for finding files in C<DATA> sections, first one has the
highest precedence, defaults to C<main>.
# Add another class with static files in DATA section
push @{$static->classes}, 'Mojolicious::Plugin::Fun';
=head2 paths
my $paths = $static->paths;
$static = $static->paths(['/home/sri/public']);
Directories to serve static files from, first one has the highest precedence.
# Add another "public" directory
push @{$static->paths}, '/home/sri/public';
=head1 METHODS
L<Mojolicious::Static> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 dispatch
my $bool = $static->dispatch(Mojolicious::Controller->new);
Serve static file for L<Mojolicious::Controller> object.
=head2 file
my $asset = $static->file('images/logo.png');
my $asset = $static->file('../lib/MyApp.pm');
Build L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object for a file,
relative to L</"paths"> or from L</"classes">. Note that this method does not
protect from traversing to parent directories.
my $content = $static->file('foo/bar.html')->slurp;
=head2 is_fresh
my $bool = $static->is_fresh(Mojolicious::Controller->new, {etag => 'abc'});
Check freshness of request by comparing the C<If-None-Match> and
C<If-Modified-Since> request headers to the C<ETag> and C<Last-Modified>
response headers.
These options are currently available:
=over 2
=item etag
etag => 'abc'
Add C<ETag> header before comparing.
=item last_modified
last_modified => $epoch
Add C<Last-Modified> header before comparing.
=back
=head2 serve
my $bool = $static->serve(Mojolicious::Controller->new, 'images/logo.png');
my $bool = $static->serve(Mojolicious::Controller->new, '../lib/MyApp.pm');
Serve a specific file, relative to L</"paths"> or from L</"classes">. Note
that this method does not protect from traversing to parent directories.
=head2 serve_asset
$static->serve_asset(Mojolicious::Controller->new, Mojo::Asset::File->new);
Serve a L<Mojo::Asset::File> or L<Mojo::Asset::Memory> object with C<Range>,
C<If-Modified-Since> and C<If-None-Match> support.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_STATIC
$fatpacked{"Mojolicious/Types.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_TYPES';
package Mojolicious::Types;
use Mojo::Base -base;
has types => sub {
{
appcache => ['text/cache-manifest'],
atom => ['application/atom+xml'],
bin => ['application/octet-stream'],
css => ['text/css'],
gif => ['image/gif'],
gz => ['application/x-gzip'],
htm => ['text/html'],
html => ['text/html;charset=UTF-8'],
ico => ['image/x-icon'],
jpeg => ['image/jpeg'],
jpg => ['image/jpeg'],
js => ['application/javascript'],
json => ['application/json'],
mp3 => ['audio/mpeg'],
mp4 => ['video/mp4'],
ogg => ['audio/ogg'],
ogv => ['video/ogg'],
pdf => ['application/pdf'],
png => ['image/png'],
rss => ['application/rss+xml'],
svg => ['image/svg+xml'],
txt => ['text/plain;charset=UTF-8'],
webm => ['video/webm'],
woff => ['application/font-woff'],
xml => ['application/xml', 'text/xml'],
zip => ['application/zip']
};
};
sub detect {
my ($self, $accept, $prioritize) = @_;
# Extract and prioritize MIME types
my %types;
/^\s*([^,; ]+)(?:\s*\;\s*q\s*=\s*(\d+(?:\.\d+)?))?\s*$/i
and $types{lc $1} = $2 // 1
for split ',', $accept // '';
my @detected = sort { $types{$b} <=> $types{$a} } sort keys %types;
return [] if !$prioritize && @detected > 1;
# Detect extensions from MIME types
my %reverse;
my $types = $self->types;
for my $ext (sort keys %$types) {
my @types = @{$types->{$ext}};
push @{$reverse{$_}}, $ext for map { s/\;.*$//; lc $_ } @types;
}
return [map { @{$reverse{$_} // []} } @detected];
}
sub type {
my ($self, $ext, $type) = @_;
return $self->types->{lc $ext}[0] unless $type;
$self->types->{lc $ext} = ref $type ? $type : [$type];
return $self;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Types - MIME types
=head1 SYNOPSIS
use Mojolicious::Types;
my $types = Mojolicious::Types->new;
$types->type(foo => 'text/foo');
say $types->type('foo');
=head1 DESCRIPTION
L<Mojolicious::Types> manages MIME types for L<Mojolicious>.
appcache -> text/cache-manifest
atom -> application/atom+xml
bin -> application/octet-stream
css -> text/css
gif -> image/gif
gz -> application/x-gzip
htm -> text/html
html -> text/html;charset=UTF-8
ico -> image/x-icon
jpeg -> image/jpeg
jpg -> image/jpeg
js -> application/javascript
json -> application/json
mp3 -> audio/mpeg
mp4 -> video/mp4
ogg -> audio/ogg
ogv -> video/ogg
pdf -> application/pdf
png -> image/png
rss -> application/rss+xml
svg -> image/svg+xml
txt -> text/plain;charset=UTF-8
webm -> video/webm
woff -> application/font-woff
xml -> application/xml,text/xml
zip -> application/zip
The most common ones are already defined.
=head1 ATTRIBUTES
L<Mojolicious::Types> implements the following attributes.
=head2 types
my $map = $types->types;
$types = $types->types({png => ['image/png']});
List of MIME types.
=head1 METHODS
L<Mojolicious::Types> inherits all methods from L<Mojo::Base> and implements
the following new ones.
=head2 detect
my $exts = $types->detect('application/json;q=9');
my $exts = $types->detect('text/html, application/json;q=9', 1);
Detect file extensions from C<Accept> header value, prioritization of
unspecific values that contain more than one MIME type is disabled by default.
# List detected extensions prioritized
say for @{$types->detect('application/json, text/xml;q=0.1', 1)};
=head2 type
my $type = $types->type('png');
$types = $types->type(png => 'image/png');
$types = $types->type(json => [qw(application/json text/x-json)]);
Get or set MIME types for file extension, alternatives are only used for
detection.
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_TYPES
$fatpacked{"Mojolicious/Validator.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_VALIDATOR';
package Mojolicious::Validator;
use Mojo::Base -base;
use Mojolicious::Validator::Validation;
has checks => sub {
{equal_to => \&_equal_to, in => \&_in, like => \&_like, size => \&_size};
};
sub add_check {
my ($self, $name, $cb) = @_;
$self->checks->{$name} = $cb;
return $self;
}
sub validation {
Mojolicious::Validator::Validation->new(validator => shift);
}
sub _equal_to {
my ($validation, $name, $value, $to) = @_;
return 1 unless defined(my $other = $validation->input->{$to});
return $value ne $other;
}
sub _in {
my ($validation, $name, $value) = (shift, shift, shift);
$value eq $_ && return undef for @_;
return 1;
}
sub _like { $_[2] !~ $_[3] }
sub _size {
my ($validation, $name, $value, $min, $max) = @_;
my $len = length $value;
return $len < $min || $len > $max;
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Validator - Validate parameter
=head1 SYNOPSIS
use Mojolicious::Validator;
my $validator = Mojolicious::Validator->new;
my $validation = $validator->validation;
$validation->input({foo => 'bar'});
$validation->required('foo')->like(qr/ar$/);
say $validation->param('foo');
=head1 DESCRIPTION
L<Mojolicious::Validator> validates parameters for L<Mojolicious>.
=head1 CHECKS
These validation checks are available by default.
=head2 equal_to
$validation->equal_to('foo');
Value needs to be equal to the value of another field.
=head2 in
$validation->in(qw(foo bar baz));
Value needs to match one of the values in the list.
=head2 like
$validation->like(qr/^[A-Z]/);
Value needs to match the regular expression.
=head2 size
$validation->size(2, 5);
Value length in characters needs to be between these two values.
=head1 ATTRIBUTES
L<Mojolicious::Validator> implements the following attributes.
=head2 checks
my $checks = $validator->checks;
$validator = $validator->checks({size => sub {...}});
Registered validation checks, by default only L</"equal_to">, L</"in">,
L</"like"> and L</"size"> are already defined.
=head1 METHODS
L<Mojolicious::Validator> inherits all methods from L<Mojo::Base> and
implements the following new ones.
=head2 add_check
$validator = $validator->add_check(size => sub {...});
Register a new validation check.
=head2 validation
my $validation = $validator->validation;
Build L<Mojolicious::Validator::Validation> object to perform validations.
my $validation = $validator->validation;
$validation->input({foo => 'bar'});
$validation->required('foo')->size(1, 5);
say $validation->param('foo');
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_VALIDATOR
$fatpacked{"Mojolicious/Validator/Validation.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'MOJOLICIOUS_VALIDATOR_VALIDATION';
package Mojolicious::Validator::Validation;
use Mojo::Base -base;
use Carp ();
use Scalar::Util ();
has [qw(csrf_token topic validator)];
has [qw(input output)] => sub { {} };
sub AUTOLOAD {
my $self = shift;
my ($package, $method) = our $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 => @_);
}
sub check {
my ($self, $check) = (shift, shift);
return $self unless $self->is_valid;
my $cb = $self->validator->checks->{$check};
my $name = $self->topic;
my $input = $self->input->{$name};
for my $value (ref $input eq 'ARRAY' ? @$input : $input) {
next unless my $result = $self->$cb($name, $value, @_);
return $self->error($name => [$check, $result, @_]);
}
return $self;
}
sub csrf_protect {
my $self = shift;
my $token = $self->input->{csrf_token};
$self->error(csrf_token => ['csrf_protect'])
unless $token && $token eq ($self->csrf_token // '');
return $self;
}
sub error {
my $self = shift;
return sort keys %{$self->{error}} unless defined(my $name = shift);
return $self->{error}{$name} unless @_;
$self->{error}{$name} = shift;
delete $self->output->{$name};
return $self;
}
sub every_param { shift->_param(@_) }
sub has_data { !!keys %{shift->input} }
sub has_error { $_[1] ? exists $_[0]{error}{$_[1]} : !!keys %{$_[0]{error}} }
sub is_valid { exists $_[0]->output->{$_[1] // $_[0]->topic} }
sub optional {
my ($self, $name) = @_;
my $input = $self->input->{$name};
my @input = ref $input eq 'ARRAY' ? @$input : $input;
$self->output->{$name} = $input
unless grep { !defined($_) || !length($_) } @input;
return $self->topic($name);
}
sub param {
my ($self, $name) = @_;
# Multiple names
return map { $self->param($_) } @$name if ref $name eq 'ARRAY';
# List names
return sort keys %{$self->output} unless defined $name;
return $self->_param($name)->[-1];
}
sub required {
my ($self, $name) = @_;
return $self if $self->optional($name)->is_valid;
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
=head1 NAME
Mojolicious::Validator::Validation - Perform validations
=head1 SYNOPSIS
use Mojolicious::Validator;
use Mojolicious::Validator::Validation;
my $validator = Mojolicious::Validator->new;
my $validation
= Mojolicious::Validator::Validation->new(validator => $validator);
$validation->input({foo => 'bar'});
$validation->required('foo')->in(qw(bar baz));
say $validation->param('foo');
=head1 DESCRIPTION
L<Mojolicious::Validator::Validation> performs L<Mojolicious::Validator>
validation checks.
=head1 ATTRIBUTES
L<Mojolicious::Validator::Validation> implements the following attributes.
=head2 csrf_token
my $token = $validation->csrf_token;
$validation = $validation->csrf_token('fa6a08...');
CSRF token.
=head2 input
my $input = $validation->input;
$validation = $validation->input({foo => 'bar', baz => [123, 'yada']});
Data to be validated.
=head2 output
my $output = $validation->output;
$validation = $validation->output({});
Validated data.
=head2 topic
my $topic = $validation->topic;
$validation = $validation->topic('foo');
Name of field currently being validated.
=head2 validator
my $validator = $validation->validator;
$validation = $validation->validator(Mojolicious::Validator->new);
L<Mojolicious::Validator> object this validation belongs to.
=head1 METHODS
L<Mojolicious::Validator::Validation> inherits all methods from L<Mojo::Base>
and implements the following new ones.
=head2 check
$validation = $validation->check('size', 2, 7);
Perform validation check on all values of the current L</"topic">, no more
checks will be performed on them after the first one failed.
=head2 csrf_protect
$validation = $validation->csrf_protect;
Validate C<csrf_token> and protect from cross-site request forgery.
=head2 error
my @names = $validation->error;
my $err = $validation->error('foo');
$validation = $validation->error(foo => ['custom_check']);
Get or set details for failed validation check, at any given time there can
only be one per field.
my ($check, $result, @args) = @{$validation->error('foo')};
=head2 every_param
my $values = $validation->every_param('foo');
Similar to L</"param">, but returns all values sharing the same name as an
array reference.
# Get first value
my $first = $validation->every_param('foo')->[0];
=head2 has_data
my $bool = $validation->has_data;
Check if L</"input"> is available for validation.
=head2 has_error
my $bool = $validation->has_error;
my $bool = $validation->has_error('foo');
Check if validation resulted in errors, defaults to checking all fields.
=head2 is_valid
my $bool = $validation->is_valid;
my $bool = $validation->is_valid('foo');
Check if validation was successful and field has a value, defaults to checking
the current L</"topic">.
=head2 optional
$validation = $validation->optional('foo');
Change validation L</"topic">.
=head2 param
my @names = $validation->param;
my $value = $validation->param('foo');
my ($foo, $bar) = $validation->param(['foo', 'bar']);
Access validated parameters. If there are multiple values sharing the same
name, and you want to access more than just the last one, you can use
L</"every_param">.
=head2 required
$validation = $validation->required('foo');
Change validation L</"topic"> and make sure a value is present and not an
empty string.
=head1 AUTOLOAD
In addition to the L</"ATTRIBUTES"> and L</"METHODS"> above, you can also call
validation checks provided by L</"validator"> on
L<Mojolicious::Validator::Validation> objects, similar to L</"check">.
# Call validation checks
$validation->required('foo')->size(2, 5)->like(qr/^[A-Z]/);
$validation->optional('bar')->equal_to('foo');
$validation->optional('baz')->in(qw(test 123));
# Longer version
$validation->required('foo')->check('size', 2,5)->check('like', qr/^[A-Z]/);
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
MOJOLICIOUS_VALIDATOR_VALIDATION
$fatpacked{"Test/Mojo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'TEST_MOJO';
package Test::Mojo;
use Mojo::Base -base;
# "Amy: He knows when you are sleeping.
# Professor: He knows when you're on the can.
# Leela: He'll hunt you down and blast your ass from here to Pakistan.
# Zoidberg: Oh.
# Hermes: You'd better not breathe, you'd better not move.
# Bender: You're better off dead, I'm telling you, dude.
# Fry: Santa Claus is gunning you down!"
use Mojo::IOLoop;
use Mojo::JSON 'j';
use Mojo::JSON::Pointer;
use Mojo::Server;
use Mojo::UserAgent;
use Mojo::Util qw(decode encode);
use Test::More ();
has [qw(message success tx)];
has ua => sub { Mojo::UserAgent->new->ioloop(Mojo::IOLoop->singleton) };
# Silent or loud tests
$ENV{MOJO_LOG_LEVEL} ||= $ENV{HARNESS_IS_VERBOSE} ? 'debug' : 'fatal';
sub app {
my ($self, $app) = @_;
return $self->ua->server->app unless $app;
$self->ua->server->app($app);
return $self;
}
sub content_is {
my ($self, $value, $desc) = @_;
$desc ||= 'exact match for content';
return $self->_test('is', $self->tx->res->text, $value, $desc);
}
sub content_isnt {
my ($self, $value, $desc) = @_;
$desc ||= 'no match for content';
return $self->_test('isnt', $self->tx->res->text, $value, $desc);
}
sub content_like {
my ($self, $regex, $desc) = @_;
$desc ||= 'content is similar';
return $self->_test('like', $self->tx->res->text, $regex, $desc);
}
sub content_unlike {
my ($self, $regex, $desc) = @_;
$desc ||= 'content is not similar';
return $self->_test('unlike', $self->tx->res->text, $regex, $desc);
}
sub content_type_is {
my ($self, $type, $desc) = @_;
$desc ||= "Content-Type: $type";
return $self->_test('is', $self->tx->res->headers->content_type, $type,
$desc);
}
sub content_type_isnt {
my ($self, $type, $desc) = @_;
$desc ||= "not Content-Type: $type";
return $self->_test('isnt', $self->tx->res->headers->content_type, $type,
$desc);
}
sub content_type_like {
my ($self, $regex, $desc) = @_;
$desc ||= 'Content-Type is similar';
return $self->_test('like', $self->tx->res->headers->content_type, $regex,
$desc);
}
sub content_type_unlike {
my ($self, $regex, $desc) = @_;
$desc ||= 'Content-Type is not similar';
return $self->_test('unlike', $self->tx->res->headers->content_type,
$regex, $desc);
}
sub delete_ok { shift->_build_ok(DELETE => @_) }
sub element_exists {
my ($self, $selector, $desc) = @_;
$desc ||= encode 'UTF-8', qq{element for selector "$selector" exists};
return $self->_test('ok', $self->tx->res->dom->at($selector), $desc);
}
sub element_exists_not {
my ($self, $selector, $desc) = @_;
$desc ||= encode 'UTF-8', qq{no element for selector "$selector"};
return $self->_test('ok', !$self->tx->res->dom->at($selector), $desc);
}
sub finish_ok {
my $self = shift;
$self->tx->finish(@_);
Mojo::IOLoop->one_tick while !$self->{finished};
return $self->_test('ok', 1, 'closed WebSocket');
}
sub finished_ok {
my ($self, $code) = @_;
Mojo::IOLoop->one_tick while !$self->{finished};
Test::More::diag "WebSocket closed with status $self->{finished}[0]"
unless my $ok = grep { $self->{finished}[0] == $_ } $code, 1006;
return $self->_test('ok', $ok, "WebSocket closed with status $code");
}
sub get_ok { shift->_build_ok(GET => @_) }
sub head_ok { shift->_build_ok(HEAD => @_) }
sub header_is {
my ($self, $name, $value, $desc) = @_;
$desc ||= "$name: " . ($value // '');
return $self->_test('is', $self->tx->res->headers->header($name), $value,
$desc);
}
sub header_isnt {
my ($self, $name, $value, $desc) = @_;
$desc ||= "not $name: " . ($value // '');
return $self->_test('isnt', $self->tx->res->headers->header($name), $value,
$desc);
}
sub header_like {
my ($self, $name, $regex, $desc) = @_;
$desc ||= "$name is similar";
return $self->_test('like', $self->tx->res->headers->header($name), $regex,
$desc);
}
sub header_unlike {
my ($self, $name, $regex, $desc) = @_;
$desc ||= "$name is not similar";
return $self->_test('unlike', $self->tx->res->headers->header($name),
$regex, $desc);
}
sub json_has {
my ($self, $p, $desc) = @_;
$desc ||= encode 'UTF-8', qq{has value for JSON Pointer "$p"};
return $self->_test('ok',
!!Mojo::JSON::Pointer->new($self->tx->res->json)->contains($p), $desc);
}
sub json_hasnt {
my ($self, $p, $desc) = @_;
$desc ||= encode 'UTF-8', qq{has no value for JSON Pointer "$p"};
return $self->_test('ok',
!Mojo::JSON::Pointer->new($self->tx->res->json)->contains($p), $desc);
}
sub json_is {
my $self = shift;
my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
my $desc = encode 'UTF-8', shift || qq{exact match for JSON Pointer "$p"};
return $self->_test('is_deeply', $self->tx->res->json($p), $data, $desc);
}
sub json_like {
my ($self, $p, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{similar match for JSON Pointer "$p"};
return $self->_test('like', $self->tx->res->json($p), $regex, $desc);
}
sub json_message_has {
my ($self, $p, $desc) = @_;
$desc ||= encode 'UTF-8', qq{has value for JSON Pointer "$p"};
return $self->_test('ok', $self->_json(contains => $p), $desc);
}
sub json_message_hasnt {
my ($self, $p, $desc) = @_;
$desc ||= encode 'UTF-8', qq{has no value for JSON Pointer "$p"};
return $self->_test('ok', !$self->_json(contains => $p), $desc);
}
sub json_message_is {
my $self = shift;
my ($p, $data) = @_ > 1 ? (shift, shift) : ('', shift);
my $desc = encode 'UTF-8', shift || qq{exact match for JSON Pointer "$p"};
return $self->_test('is_deeply', $self->_json(get => $p), $data, $desc);
}
sub json_message_like {
my ($self, $p, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{similar match for JSON Pointer "$p"};
return $self->_test('like', $self->_json(get => $p), $regex, $desc);
}
sub json_message_unlike {
my ($self, $p, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{no similar match for JSON Pointer "$p"};
return $self->_test('unlike', $self->_json(get => $p), $regex, $desc);
}
sub json_unlike {
my ($self, $p, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{no similar match for JSON Pointer "$p"};
return $self->_test('unlike', $self->tx->res->json($p), $regex, $desc);
}
sub message_is {
my ($self, $value, $desc) = @_;
return $self->_message('is', $value, $desc || 'exact match for message');
}
sub message_isnt {
my ($self, $value, $desc) = @_;
return $self->_message('isnt', $value, $desc || 'no match for message');
}
sub message_like {
my ($self, $regex, $desc) = @_;
return $self->_message('like', $regex, $desc || 'message is similar');
}
sub message_ok {
my ($self, $desc) = @_;
return $self->_test('ok', !!$self->_wait, $desc || 'message received');
}
sub message_unlike {
my ($self, $regex, $desc) = @_;
return $self->_message('unlike', $regex, $desc || 'message is not similar');
}
sub new {
my $self = shift->SUPER::new;
return $self unless my $app = shift;
return $self->app(ref $app ? $app : Mojo::Server->new->build_app($app));
}
sub options_ok { shift->_build_ok(OPTIONS => @_) }
sub or {
my ($self, $cb) = @_;
$self->$cb unless $self->success;
return $self;
}
sub patch_ok { shift->_build_ok(PATCH => @_) }
sub post_ok { shift->_build_ok(POST => @_) }
sub put_ok { shift->_build_ok(PUT => @_) }
sub request_ok { shift->_request_ok($_[0], $_[0]->req->url->to_string) }
sub reset_session {
my $self = shift;
if (my $jar = $self->ua->cookie_jar) { $jar->empty }
return $self->tx(undef);
}
sub send_ok {
my ($self, $msg, $desc) = @_;
$self->tx->send($msg => sub { Mojo::IOLoop->stop });
Mojo::IOLoop->start;
return $self->_test('ok', 1, $desc || 'send message');
}
sub status_is {
my ($self, $status, $desc) = @_;
$desc ||= "$status " . $self->tx->res->default_message($status);
return $self->_test('is', $self->tx->res->code, $status, $desc);
}
sub status_isnt {
my ($self, $status, $desc) = @_;
$desc ||= "not $status " . $self->tx->res->default_message($status);
return $self->_test('isnt', $self->tx->res->code, $status, $desc);
}
sub text_is {
my ($self, $selector, $value, $desc) = @_;
$desc ||= encode 'UTF-8', qq{exact match for selector "$selector"};
return $self->_test('is', $self->_text($selector), $value, $desc);
}
sub text_isnt {
my ($self, $selector, $value, $desc) = @_;
$desc ||= encode 'UTF-8', qq{no match for selector "$selector"};
return $self->_test('isnt', $self->_text($selector), $value, $desc);
}
sub text_like {
my ($self, $selector, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{similar match for selector "$selector"};
return $self->_test('like', $self->_text($selector), $regex, $desc);
}
sub text_unlike {
my ($self, $selector, $regex, $desc) = @_;
$desc ||= encode 'UTF-8', qq{no similar match for selector "$selector"};
return $self->_test('unlike', $self->_text($selector), $regex, $desc);
}
sub websocket_ok {
my $self = shift;
return $self->_request_ok($self->ua->build_websocket_tx(@_), $_[0]);
}
sub _build_ok {
my ($self, $method, $url) = (shift, shift, shift);
local $Test::Builder::Level = $Test::Builder::Level + 1;
return $self->_request_ok($self->ua->build_tx($method, $url, @_), $url);
}
sub _json {
my ($self, $method, $p) = @_;
return Mojo::JSON::Pointer->new(j(@{$self->message // []}[1]))->$method($p);
}
sub _message {
my ($self, $name, $value, $desc) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($type, $msg) = @{$self->message // []};
# Type check
if (ref $value eq 'HASH') {
my $expect = exists $value->{text} ? 'text' : 'binary';
$value = $value->{$expect};
$msg = '' unless $type eq $expect;
}
# Decode text frame if there is no type check
else { $msg = decode 'UTF-8', $msg if $type eq 'text' }
return $self->_test($name, $msg // '', $value, $desc);
}
sub _request_ok {
my ($self, $tx, $url) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
# Establish WebSocket connection
if ($tx->req->is_handshake) {
@$self{qw(finished messages)} = (undef, []);
$self->ua->start(
$tx => sub {
my ($ua, $tx) = @_;
$self->tx($tx);
$tx->on(finish => sub { shift; $self->{finished} = [@_] });
$tx->on(binary => sub { push @{$self->{messages}}, [binary => pop] });
$tx->on(text => sub { push @{$self->{messages}}, [text => pop] });
Mojo::IOLoop->stop;
}
);
Mojo::IOLoop->start;
my $desc = encode 'UTF-8', "WebSocket $url";
return $self->_test('ok', $self->tx->is_websocket, $desc);
}
# Perform request
$self->tx($self->ua->start($tx));
my $err = $self->tx->error;
Test::More::diag $err->{message}
if !(my $ok = !$err->{message} || $err->{code}) && $err;
my $desc = encode 'UTF-8', "@{[uc $tx->req->method]} $url";
return $self->_test('ok', $ok, $desc);
}
sub _test {
my ($self, $name, @args) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 2;
return $self->success(!!Test::More->can($name)->(@args));
}
sub _text {
return '' unless my $e = shift->tx->res->dom->at(shift);
return $e->text;
}
sub _wait {
my $self = shift;
Mojo::IOLoop->one_tick while !$self->{finished} && !@{$self->{messages}};
return $self->message(shift @{$self->{messages}})->message;
}
1;
=encoding utf8
=head1 NAME
Test::Mojo - Testing Mojo!
=head1 SYNOPSIS
use Test::More;
use Test::Mojo;
my $t = Test::Mojo->new('MyApp');
# HTML/XML
$t->get_ok('/welcome')->status_is(200)->text_is('div#message' => 'Hello!');
# JSON
$t->post_ok('/search.json' => form => {q => 'Perl'})
->status_is(200)
->header_is('Server' => 'Mojolicious (Perl)')
->header_isnt('X-Bender' => 'Bite my shiny metal ass!')
->json_is('/results/4/title' => 'Perl rocks!')
->json_like('/results/7/title' => qr/Perl/);
# WebSocket
$t->websocket_ok('/echo')
->send_ok('hello')
->message_ok
->message_is('echo: hello')
->finish_ok;
done_testing();
=head1 DESCRIPTION
L<Test::Mojo> is a collection of testing helpers for everyone developing
L<Mojo> and L<Mojolicious> applications, it is usually used together with
L<Test::More>.
=head1 ATTRIBUTES
L<Test::Mojo> implements the following attributes.
=head2 message
my $msg = $t->message;
$t = $t->message([text => $bytes]);
Current WebSocket message represented as an array reference containing the
frame type and payload.
# More specific tests
use Mojo::JSON 'decode_json';
my $hash = decode_json $t->message->[1];
is ref $hash, 'HASH', 'right reference';
is $hash->{foo}, 'bar', 'right value';
# Test custom message
$t->message([binary => $bytes])
->json_message_has('/foo/bar')
->json_message_hasnt('/bar')
->json_message_is('/foo/baz' => {yada => [1, 2, 3]});
=head2 success
my $bool = $t->success;
$t = $t->success($bool);
True if the last test was successful.
# Build custom tests
my $location_is = sub {
my ($t, $value, $desc) = @_;
$desc ||= "Location: $value";
local $Test::Builder::Level = $Test::Builder::Level + 1;
return $t->success(is($t->tx->res->headers->location, $value, $desc));
};
$t->get_ok('/')
->status_is(302)
->$location_is('http://mojolicio.us')
->or(sub { diag 'Must have been Joel!' });
=head2 tx
my $tx = $t->tx;
$t = $t->tx(Mojo::Transaction::HTTP->new);
Current transaction, usually a L<Mojo::Transaction::HTTP> object.
# More specific tests
is $t->tx->res->json->{foo}, 'bar', 'right value';
ok $t->tx->res->content->is_multipart, 'multipart content';
# Test custom transactions
$t->tx($t->tx->previous)->status_is(302)->header_like(Location => qr/foo/);
=head2 ua
my $ua = $t->ua;
$t = $t->ua(Mojo::UserAgent->new);
User agent used for testing, defaults to a L<Mojo::UserAgent> object.
# Allow redirects
$t->ua->max_redirects(10);
# Use absolute URL for request with Basic authentication
my $url = $t->ua->server->url->userinfo('sri:secr3t')->path('/secrets.json');
$t->post_ok($url => json => {limit => 10})
->status_is(200)
->json_is('/1/content', 'Mojo rocks!');
# Customize all transactions (including followed redirects)
$t->ua->on(start => sub {
my ($ua, $tx) = @_;
$tx->req->headers->accept_language('en-US');
});
=head1 METHODS
L<Test::Mojo> inherits all methods from L<Mojo::Base> and implements the
following new ones.
=head2 app
my $app = $t->app;
$t = $t->app(MyApp->new);
Access application with L<Mojo::UserAgent::Server/"app">.
# Change log level
$t->app->log->level('fatal');
# Test application directly
is $t->app->defaults->{foo}, 'bar', 'right value';
ok $t->app->routes->find('echo')->is_websocket, 'WebSocket route';
my $c = $t->app->build_controller;
ok $c->render(template => 'foo'), 'rendering was successful';
is $c->res->status, 200, 'right status';
is $c->res->body, 'Foo!', 'right content';
# Change application behavior
$t->app->hook(before_dispatch => sub {
my $c = shift;
$c->render(text => 'This request did not reach the router.')
if $c->req->url->path->contains('/user');
});
# Extract additional information
my $stash;
$t->app->hook(after_dispatch => sub { $stash = shift->stash });
=head2 content_is
$t = $t->content_is('working!');
$t = $t->content_is('working!', 'right content');
Check response content for exact match after retrieving it from
L<Mojo::Message/"text">.
=head2 content_isnt
$t = $t->content_isnt('working!');
$t = $t->content_isnt('working!', 'different content');
Opposite of L</"content_is">.
=head2 content_like
$t = $t->content_like(qr/working!/);
$t = $t->content_like(qr/working!/, 'right content');
Check response content for similar match after retrieving it from
L<Mojo::Message/"text">.
=head2 content_unlike
$t = $t->content_unlike(qr/working!/);
$t = $t->content_unlike(qr/working!/, 'different content');
Opposite of L</"content_like">.
=head2 content_type_is
$t = $t->content_type_is('text/html');
$t = $t->content_type_is('text/html', 'right content type');
Check response C<Content-Type> header for exact match.
=head2 content_type_isnt
$t = $t->content_type_isnt('text/html');
$t = $t->content_type_isnt('text/html', 'different content type');
Opposite of L</"content_type_is">.
=head2 content_type_like
$t = $t->content_type_like(qr/text/);
$t = $t->content_type_like(qr/text/, 'right content type');
Check response C<Content-Type> header for similar match.
=head2 content_type_unlike
$t = $t->content_type_unlike(qr/text/);
$t = $t->content_type_unlike(qr/text/, 'different content type');
Opposite of L</"content_type_like">.
=head2 delete_ok
$t = $t->delete_ok('/foo');
$t = $t->delete_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->delete_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->delete_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<DELETE> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"delete">, except for the callback.
=head2 element_exists
$t = $t->element_exists('div.foo[x=y]');
$t = $t->element_exists('html head title', 'has a title');
Checks for existence of the CSS selectors first matching HTML/XML element with
L<Mojo::DOM/"at">.
=head2 element_exists_not
$t = $t->element_exists_not('div.foo[x=y]');
$t = $t->element_exists_not('html head title', 'has no title');
Opposite of L</"element_exists">.
=head2 finish_ok
$t = $t->finish_ok;
$t = $t->finish_ok(1000);
$t = $t->finish_ok(1003 => 'Cannot accept data!');
Close WebSocket connection gracefully.
=head2 finished_ok
$t = $t->finished_ok(1000);
Wait for WebSocket connection to be closed gracefully and check status.
=head2 get_ok
$t = $t->get_ok('/foo');
$t = $t->get_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->get_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->get_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<GET> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"get">, except for the callback.
# Run tests against remote host
$t->get_ok('http://mojolicio.us/perldoc')->status_is(200);
# Run additional tests on the transaction
$t->get_ok('/foo')->status_is(200);
is $t->tx->res->dom->at('input')->{value}, 'whatever', 'right value';
=head2 head_ok
$t = $t->head_ok('/foo');
$t = $t->head_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->head_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->head_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<HEAD> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"head">, except for the callback.
=head2 header_is
$t = $t->header_is(ETag => '"abc321"');
$t = $t->header_is(ETag => '"abc321"', 'right header');
Check response header for exact match.
=head2 header_isnt
$t = $t->header_isnt(Etag => '"abc321"');
$t = $t->header_isnt(ETag => '"abc321"', 'different header');
Opposite of L</"header_is">.
=head2 header_like
$t = $t->header_like(ETag => qr/abc/);
$t = $t->header_like(ETag => qr/abc/, 'right header');
Check response header for similar match.
=head2 header_unlike
$t = $t->header_unlike(ETag => qr/abc/);
$t = $t->header_unlike(ETag => qr/abc/, 'different header');
Opposite of L</"header_like">.
=head2 json_has
$t = $t->json_has('/foo');
$t = $t->json_has('/minibar', 'has a minibar');
Check if JSON response contains a value that can be identified using the given
JSON Pointer with L<Mojo::JSON::Pointer>.
=head2 json_hasnt
$t = $t->json_hasnt('/foo');
$t = $t->json_hasnt('/minibar', 'no minibar');
Opposite of L</"json_has">.
=head2 json_is
$t = $t->json_is({foo => [1, 2, 3]});
$t = $t->json_is('/foo' => [1, 2, 3]);
$t = $t->json_is('/foo/1' => 2, 'right value');
Check the value extracted from JSON response using the given JSON Pointer with
L<Mojo::JSON::Pointer>, which defaults to the root value if it is omitted.
=head2 json_like
$t = $t->json_like('/foo/1' => qr/^\d+$/);
$t = $t->json_like('/foo/1' => qr/^\d+$/, 'right value');
Check the value extracted from JSON response using the given JSON Pointer with
L<Mojo::JSON::Pointer> for similar match.
=head2 json_message_has
$t = $t->json_message_has('/foo');
$t = $t->json_message_has('/minibar', 'has a minibar');
Check if JSON WebSocket message contains a value that can be identified using
the given JSON Pointer with L<Mojo::JSON::Pointer>.
=head2 json_message_hasnt
$t = $t->json_message_hasnt('/foo');
$t = $t->json_message_hasnt('/minibar', 'no minibar');
Opposite of L</"json_message_has">.
=head2 json_message_is
$t = $t->json_message_is({foo => [1, 2, 3]});
$t = $t->json_message_is('/foo' => [1, 2, 3]);
$t = $t->json_message_is('/foo/1' => 2, 'right value');
Check the value extracted from JSON WebSocket message using the given JSON
Pointer with L<Mojo::JSON::Pointer>, which defaults to the root value if it is
omitted.
=head2 json_message_like
$t = $t->json_message_like('/foo/1' => qr/^\d+$/);
$t = $t->json_message_like('/foo/1' => qr/^\d+$/, 'right value');
Check the value extracted from JSON WebSocket message using the given JSON
Pointer with L<Mojo::JSON::Pointer> for similar match.
=head2 json_message_unlike
$t = $t->json_message_unlike('/foo/1' => qr/^\d+$/);
$t = $t->json_message_unlike('/foo/1' => qr/^\d+$/, 'different value');
Opposite of L</"json_message_like">.
=head2 json_unlike
$t = $t->json_unlike('/foo/1' => qr/^\d+$/);
$t = $t->json_unlike('/foo/1' => qr/^\d+$/, 'different value');
Opposite of L</"json_like">.
=head2 message_is
$t = $t->message_is({binary => $bytes});
$t = $t->message_is({text => $bytes});
$t = $t->message_is('working!');
$t = $t->message_is('working!', 'right message');
Check WebSocket message for exact match.
=head2 message_isnt
$t = $t->message_isnt({binary => $bytes});
$t = $t->message_isnt({text => $bytes});
$t = $t->message_isnt('working!');
$t = $t->message_isnt('working!', 'different message');
Opposite of L</"message_is">.
=head2 message_like
$t = $t->message_like({binary => qr/$bytes/});
$t = $t->message_like({text => qr/$bytes/});
$t = $t->message_like(qr/working!/);
$t = $t->message_like(qr/working!/, 'right message');
Check WebSocket message for similar match.
=head2 message_ok
$t = $t->message_ok;
$t = $t->message_ok('got a message');
Wait for next WebSocket message to arrive.
# Wait for message and perform multiple tests on it
$t->websocket_ok('/time')
->message_ok
->message_like(qr/\d+/)
->message_unlike(qr/\w+/)
->finish_ok;
=head2 message_unlike
$t = $t->message_unlike({binary => qr/$bytes/});
$t = $t->message_unlike({text => qr/$bytes/});
$t = $t->message_unlike(qr/working!/);
$t = $t->message_unlike(qr/working!/, 'different message');
Opposite of L</"message_like">.
=head2 new
my $t = Test::Mojo->new;
my $t = Test::Mojo->new('MyApp');
my $t = Test::Mojo->new(MyApp->new);
Construct a new L<Test::Mojo> object.
=head2 options_ok
$t = $t->options_ok('/foo');
$t = $t->options_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->options_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->options_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<OPTIONS> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"options">, except for the callback.
=head2 or
$t = $t->or(sub {...});
Invoke callback if the value of L</"success"> is false.
# Diagnostics
$t->get_ok('/bad')->or(sub { diag 'Must have been Glen!' })
->status_is(200)->or(sub { diag $t->tx->res->dom->at('title')->text });
=head2 patch_ok
$t = $t->patch_ok('/foo');
$t = $t->patch_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->patch_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->patch_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<PATCH> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"patch">, except for the callback.
=head2 post_ok
$t = $t->post_ok('/foo');
$t = $t->post_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->post_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->post_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<POST> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"post">, except for the callback.
# Test file upload
$t->post_ok('/upload' => form => {foo => {content => 'bar'}})
->status_is(200);
# Test JSON API
$t->post_ok('/hello.json' => json => {hello => 'world'})
->status_is(200)
->json_is({bye => 'world'});
=head2 put_ok
$t = $t->put_ok('/foo');
$t = $t->put_ok('/foo' => {Accept => '*/*'} => 'Hi!');
$t = $t->put_ok('/foo' => {Accept => '*/*'} => form => {a => 'b'});
$t = $t->put_ok('/foo' => {Accept => '*/*'} => json => {a => 'b'});
Perform a C<PUT> request and check for transport errors, takes the same
arguments as L<Mojo::UserAgent/"put">, except for the callback.
=head2 request_ok
$t = $t->request_ok(Mojo::Transaction::HTTP->new);
Perform request and check for transport errors.
# Request with custom method
my $tx = $t->ua->build_tx(FOO => '/test.json' => json => {foo => 1});
$t->request_ok($tx)->status_is(200)->json_is({success => 1});
# Custom WebSocket handshake
my $tx = $t->ua->build_websocket_tx('/foo');
$tx->req->headers->remove('User-Agent');
$t->request_ok($tx)->message_ok->message_is('bar')->finish_ok;
=head2 reset_session
$t = $t->reset_session;
Reset user agent session.
=head2 send_ok
$t = $t->send_ok({binary => $bytes});
$t = $t->send_ok({text => $bytes});
$t = $t->send_ok({json => {test => [1, 2, 3]}});
$t = $t->send_ok([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]);
$t = $t->send_ok($chars);
$t = $t->send_ok($chars, 'sent successfully');
Send message or frame via WebSocket.
# Send JSON object as "Text" message
$t->websocket_ok('/echo.json')
->send_ok({json => {test => 'I ♥ Mojolicious!'}})
->message_ok
->json_message_is('/test' => 'I ♥ Mojolicious!')
->finish_ok;
=head2 status_is
$t = $t->status_is(200);
$t = $t->status_is(200, 'right status');
Check response status for exact match.
=head2 status_isnt
$t = $t->status_isnt(200);
$t = $t->status_isnt(200, 'different status');
Opposite of L</"status_is">.
=head2 text_is
$t = $t->text_is('div.foo[x=y]' => 'Hello!');
$t = $t->text_is('html head title' => 'Hello!', 'right title');
Checks text content of the CSS selectors first matching HTML/XML element for
exact match with L<Mojo::DOM/"at">.
=head2 text_isnt
$t = $t->text_isnt('div.foo[x=y]' => 'Hello!');
$t = $t->text_isnt('html head title' => 'Hello!', 'different title');
Opposite of L</"text_is">.
=head2 text_like
$t = $t->text_like('div.foo[x=y]' => qr/Hello/);
$t = $t->text_like('html head title' => qr/Hello/, 'right title');
Checks text content of the CSS selectors first matching HTML/XML element for
similar match with L<Mojo::DOM/"at">.
=head2 text_unlike
$t = $t->text_unlike('div.foo[x=y]' => qr/Hello/);
$t = $t->text_unlike('html head title' => qr/Hello/, 'different title');
Opposite of L</"text_like">.
=head2 websocket_ok
$t = $t->websocket_ok('/echo');
$t = $t->websocket_ok('/echo' => {DNT => 1} => ['v1.proto']);
Open a WebSocket connection with transparent handshake, takes the same
arguments as L<Mojo::UserAgent/"websocket">, except for the callback.
# WebSocket with permessage-deflate compression
$t->websocket('/x' => {'Sec-WebSocket-Extensions' => 'permessage-deflate'})
->send_ok('y' x 50000)
->message_ok
->message_is('z' x 50000)
->finish_ok;
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
TEST_MOJO
$fatpacked{"ojo.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'OJO';
package ojo;
use Mojo::Base -strict;
use Benchmark qw(timeit timestr :hireswallclock);
use Mojo::ByteStream 'b';
use Mojo::Collection 'c';
use Mojo::DOM;
use Mojo::JSON 'j';
use Mojo::Util qw(dumper monkey_patch);
# Silent one-liners
$ENV{MOJO_LOG_LEVEL} ||= 'fatal';
sub import {
# Mojolicious::Lite
my $caller = caller;
eval "package $caller; use Mojolicious::Lite; 1" or die $@;
my $ua = $caller->app->ua;
$ua->server->app->hook(around_action => sub { local $_ = $_[1]; $_[0]->() });
$ua->max_redirects(10) unless defined $ENV{MOJO_MAX_REDIRECTS};
$ua->proxy->detect unless defined $ENV{MOJO_PROXY};
# The ojo DSL
monkey_patch $caller,
a => sub { $caller->can('any')->(@_) and return $ua->server->app },
b => \&b,
c => \&c,
d => sub { _request($ua, 'DELETE', @_) },
g => sub { _request($ua, 'GET', @_) },
h => sub { _request($ua, 'HEAD', @_) },
j => \&j,
n => sub (&@) { say STDERR timestr timeit($_[1] // 1, $_[0]) },
o => sub { _request($ua, 'OPTIONS', @_) },
p => sub { _request($ua, 'POST', @_) },
r => \&dumper,
t => sub { _request($ua, 'PATCH', @_) },
u => sub { _request($ua, 'PUT', @_) },
x => sub { Mojo::DOM->new(@_) };
}
sub _request {
my $ua = shift;
my $tx = $ua->start($ua->build_tx(@_));
my $err = $tx->error;
warn qq/Problem loading URL "@{[$tx->req->url]}": $err->{message}\n/
if $err && !$err->{code};
return $tx->res;
}
1;
=encoding utf8
=head1 NAME
ojo - Fun one-liners with Mojo!
=head1 SYNOPSIS
$ perl -Mojo -E 'say g("mojolicio.us")->dom->at("title")->text'
=head1 DESCRIPTION
A collection of automatically exported functions for fun Perl one-liners. Ten
redirects will be followed by default, you can change this behavior with the
C<MOJO_MAX_REDIRECTS> environment variable.
$ MOJO_MAX_REDIRECTS=0 perl -Mojo -E 'say g("example.com")->code'
Proxy detection is enabled by default, but you can disable it with the
C<MOJO_PROXY> environment variable.
$ MOJO_PROXY=0 perl -Mojo -E 'say g("example.com")->body'
Every L<ojo> one-liner is also a L<Mojolicious::Lite> application.
$ perl -Mojo -E 'get "/" => {inline => "%= time"}; app->start' get /
=head1 FUNCTIONS
L<ojo> implements the following functions, which are automatically exported.
=head2 a
my $app = a('/hello' => sub { $_->render(json => {hello => 'world'}) });
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
argument variations.
$ perl -Mojo -E 'a("/hello" => {text => "Hello Mojo!"})->start' daemon
=head2 b
my $stream = b('lalala');
Turn string into a L<Mojo::ByteStream> object.
$ perl -Mojo -E 'b(g("mojolicio.us")->body)->html_unescape->say'
=head2 c
my $collection = c(1, 2, 3);
Turn list into a L<Mojo::Collection> object.
=head2 d
my $res = d('example.com');
my $res = d('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<DELETE> request with L<Mojo::UserAgent/"delete"> and return
resulting L<Mojo::Message::Response> object.
=head2 g
my $res = g('example.com');
my $res = g('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<GET> request with L<Mojo::UserAgent/"get"> and return resulting
L<Mojo::Message::Response> object.
$ perl -Mojo -E 'say g("mojolicio.us")->dom("h1")->map("text")->join("\n")'
=head2 h
my $res = h('example.com');
my $res = h('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<HEAD> request with L<Mojo::UserAgent/"head"> and return resulting
L<Mojo::Message::Response> object.
=head2 j
my $bytes = j([1, 2, 3]);
my $bytes = j({foo => 'bar'});
my $value = j($bytes);
Encode Perl data structure or decode JSON with L<Mojo::JSON/"j">.
$ perl -Mojo -E 'b(j({hello => "world!"}))->spurt("hello.json")'
=head2 n
n {...};
n {...} 100;
Benchmark block and print the results to C<STDERR>, with an optional number of
iterations, which defaults to C<1>.
$ perl -Mojo -E 'n { say g("mojolicio.us")->code }'
=head2 o
my $res = o('example.com');
my $res = o('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<OPTIONS> request with L<Mojo::UserAgent/"options"> and return
resulting L<Mojo::Message::Response> object.
=head2 p
my $res = p('example.com');
my $res = p('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<POST> request with L<Mojo::UserAgent/"post"> and return resulting
L<Mojo::Message::Response> object.
=head2 r
my $perl = r({data => 'structure'});
Dump a Perl data structure with L<Mojo::Util/"dumper">.
perl -Mojo -E 'say r(g("example.com")->headers->to_hash)'
=head2 t
my $res = t('example.com');
my $res = t('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<PATCH> request with L<Mojo::UserAgent/"patch"> and return resulting
L<Mojo::Message::Response> object.
=head2 u
my $res = u('example.com');
my $res = u('http://example.com' => {Accept => '*/*'} => 'Hi!');
Perform C<PUT> request with L<Mojo::UserAgent/"put"> and return resulting
L<Mojo::Message::Response> object.
=head2 x
my $dom = x('<div>Hello!</div>');
Turn HTML/XML input into L<Mojo::DOM> object.
$ perl -Mojo -E 'say x(b("test.html")->slurp)->at("title")->text'
=head1 SEE ALSO
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>.
=cut
OJO
$fatpacked{"x86_64-linux-thread-multi/List/Util.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'X86_64-LINUX-THREAD-MULTI_LIST_UTIL';
# Copyright (c) 1997-2009 Graham Barr <gbarr@pobox.com>. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Maintained since 2013 by Paul Evans <leonerd@leonerd.org.uk>
package List::Util;
use strict;
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
all any first min max minstr maxstr none notall product reduce sum sum0 shuffle
pairmap pairgrep pairfirst pairs pairkeys pairvalues
);
our $VERSION = "1.41";
our $XS_VERSION = $VERSION;
$VERSION = eval $VERSION;
require XSLoader;
XSLoader::load('List::Util', $XS_VERSION);
sub import
{
my $pkg = caller;
# (RT88848) Touch the caller's $a and $b, to avoid the warning of
# Name "main::a" used only once: possible typo" warning
no strict 'refs';
${"${pkg}::a"} = ${"${pkg}::a"};
${"${pkg}::b"} = ${"${pkg}::b"};
goto &Exporter::import;
}
# For objects returned by pairs()
sub List::Util::_Pair::key { shift->[0] }
sub List::Util::_Pair::value { shift->[1] }
1;
__END__
=head1 NAME
List::Util - A selection of general-utility list subroutines
=head1 SYNOPSIS
use List::Util qw(first max maxstr min minstr reduce shuffle sum);
=head1 DESCRIPTION
C<List::Util> contains a selection of subroutines that people have expressed
would be nice to have in the perl core, but the usage would not really be high
enough to warrant the use of a keyword, and the size so small such that being
individual extensions would be wasteful.
By default C<List::Util> does not export any subroutines.
=cut
=head1 LIST-REDUCTION FUNCTIONS
The following set of functions all reduce a list down to a single value.
=cut
=head2 $result = reduce { BLOCK } @list
Reduces C<@list> by calling C<BLOCK> in a scalar context multiple times,
setting C<$a> and C<$b> each time. The first call will be with C<$a> and C<$b>
set to the first two elements of the list, subsequent calls will be done by
setting C<$a> to the result of the previous call and C<$b> to the next element
in the list.
Returns the result of the last call to the C<BLOCK>. If C<@list> is empty then
C<undef> is returned. If C<@list> only contains one element then that element
is returned and C<BLOCK> is not executed.
The following examples all demonstrate how C<reduce> could be used to implement
the other list-reduction functions in this module. (They are not in fact
implemented like this, but instead in a more efficient manner in individual C
functions).
$foo = reduce { defined($a) ? $a :
$code->(local $_ = $b) ? $b :
undef } undef, @list # first
$foo = reduce { $a > $b ? $a : $b } 1..10 # max
$foo = reduce { $a gt $b ? $a : $b } 'A'..'Z' # maxstr
$foo = reduce { $a < $b ? $a : $b } 1..10 # min
$foo = reduce { $a lt $b ? $a : $b } 'aa'..'zz' # minstr
$foo = reduce { $a + $b } 1 .. 10 # sum
$foo = reduce { $a . $b } @bar # concat
$foo = reduce { $a || $code->(local $_ = $b) } 0, @bar # any
$foo = reduce { $a && $code->(local $_ = $b) } 1, @bar # all
$foo = reduce { $a && !$code->(local $_ = $b) } 1, @bar # none
$foo = reduce { $a || !$code->(local $_ = $b) } 0, @bar # notall
# Note that these implementations do not fully short-circuit
If your algorithm requires that C<reduce> produce an identity value, then make
sure that you always pass that identity value as the first argument to prevent
C<undef> being returned
$foo = reduce { $a + $b } 0, @values; # sum with 0 identity value
The remaining list-reduction functions are all specialisations of this generic
idea.
=head2 any
my $bool = any { BLOCK } @list;
I<Since version 1.33.>
Similar to C<grep> in that it evaluates C<BLOCK> setting C<$_> to each element
of C<@list> in turn. C<any> returns true if any element makes the C<BLOCK>
return a true value. If C<BLOCK> never returns true or C<@list> was empty then
it returns false.
Many cases of using C<grep> in a conditional can be written using C<any>
instead, as it can short-circuit after the first true result.
if( any { length > 10 } @strings ) {
# at least one string has more than 10 characters
}
=head2 all
my $bool = all { BLOCK } @list;
I<Since version 1.33.>
Similar to L</any>, except that it requires all elements of the C<@list> to
make the C<BLOCK> return true. If any element returns false, then it returns
false. If the C<BLOCK> never returns false or the C<@list> was empty then it
returns true.
=head2 none
=head2 notall
my $bool = none { BLOCK } @list;
my $bool = notall { BLOCK } @list;
I<Since version 1.33.>
Similar to L</any> and L</all>, but with the return sense inverted. C<none>
returns true only if no value in the C<@list> causes the C<BLOCK> to return
true, and C<notall> returns true only if not all of the values do.
=head2 first
my $val = first { BLOCK } @list;
Similar to C<grep> in that it evaluates C<BLOCK> setting C<$_> to each element
of C<@list> in turn. C<first> returns the first element where the result from
C<BLOCK> is a true value. If C<BLOCK> never returns true or C<@list> was empty
then C<undef> is returned.
$foo = first { defined($_) } @list # first defined value in @list
$foo = first { $_ > $value } @list # first value in @list which
# is greater than $value
=head2 max
my $num = max @list;
Returns the entry in the list with the highest numerical value. If the list is
empty then C<undef> is returned.
$foo = max 1..10 # 10
$foo = max 3,9,12 # 12
$foo = max @bar, @baz # whatever
=head2 maxstr
my $str = maxstr @list;
Similar to L</max>, but treats all the entries in the list as strings and
returns the highest string as defined by the C<gt> operator. If the list is
empty then C<undef> is returned.
$foo = maxstr 'A'..'Z' # 'Z'
$foo = maxstr "hello","world" # "world"
$foo = maxstr @bar, @baz # whatever
=head2 min
my $num = min @list;
Similar to L</max> but returns the entry in the list with the lowest numerical
value. If the list is empty then C<undef> is returned.
$foo = min 1..10 # 1
$foo = min 3,9,12 # 3
$foo = min @bar, @baz # whatever
=head2 minstr
my $str = minstr @list;
Similar to L</min>, but treats all the entries in the list as strings and
returns the lowest string as defined by the C<lt> operator. If the list is
empty then C<undef> is returned.
$foo = minstr 'A'..'Z' # 'A'
$foo = minstr "hello","world" # "hello"
$foo = minstr @bar, @baz # whatever
=head2 product
my $num = product @list;
I<Since version 1.35.>
Returns the numerical product of all the elements in C<@list>. If C<@list> is
empty then C<1> is returned.
$foo = product 1..10 # 3628800
$foo = product 3,9,12 # 324
=head2 sum
my $num_or_undef = sum @list;
Returns the numerical sum of all the elements in C<@list>. For backwards
compatibility, if C<@list> is empty then C<undef> is returned.
$foo = sum 1..10 # 55
$foo = sum 3,9,12 # 24
$foo = sum @bar, @baz # whatever
=head2 sum0
my $num = sum0 @list;
I<Since version 1.26.>
Similar to L</sum>, except this returns 0 when given an empty list, rather
than C<undef>.
=cut
=head1 KEY/VALUE PAIR LIST FUNCTIONS
The following set of functions, all inspired by L<List::Pairwise>, consume an
even-sized list of pairs. The pairs may be key/value associations from a hash,
or just a list of values. The functions will all preserve the original ordering
of the pairs, and will not be confused by multiple pairs having the same "key"
value - nor even do they require that the first of each pair be a plain string.
=cut
=head2 pairgrep
my @kvlist = pairgrep { BLOCK } @kvlist;
my $count = pairgrep { BLOCK } @kvlist;
I<Since version 1.29.>
Similar to perl's C<grep> keyword, but interprets the given list as an
even-sized list of pairs. It invokes the C<BLOCK> multiple times, in scalar
context, with C<$a> and C<$b> set to successive pairs of values from the
C<@kvlist>.
Returns an even-sized list of those pairs for which the C<BLOCK> returned true
in list context, or the count of the B<number of pairs> in scalar context.
(Note, therefore, in scalar context that it returns a number half the size of
the count of items it would have returned in list context).
@subset = pairgrep { $a =~ m/^[[:upper:]]+$/ } @kvlist
As with C<grep> aliasing C<$_> to list elements, C<pairgrep> aliases C<$a> and
C<$b> to elements of the given list. Any modifications of it by the code block
will be visible to the caller.
=head2 pairfirst
my ( $key, $val ) = pairfirst { BLOCK } @kvlist;
my $found = pairfirst { BLOCK } @kvlist;
I<Since version 1.30.>
Similar to the L</first> function, but interprets the given list as an
even-sized list of pairs. It invokes the C<BLOCK> multiple times, in scalar
context, with C<$a> and C<$b> set to successive pairs of values from the
C<@kvlist>.
Returns the first pair of values from the list for which the C<BLOCK> returned
true in list context, or an empty list of no such pair was found. In scalar
context it returns a simple boolean value, rather than either the key or the
value found.
( $key, $value ) = pairfirst { $a =~ m/^[[:upper:]]+$/ } @kvlist
As with C<grep> aliasing C<$_> to list elements, C<pairfirst> aliases C<$a> and
C<$b> to elements of the given list. Any modifications of it by the code block
will be visible to the caller.
=head2 pairmap
my @list = pairmap { BLOCK } @kvlist;
my $count = pairmap { BLOCK } @kvlist;
I<Since version 1.29.>
Similar to perl's C<map> keyword, but interprets the given list as an
even-sized list of pairs. It invokes the C<BLOCK> multiple times, in list
context, with C<$a> and C<$b> set to successive pairs of values from the
C<@kvlist>.
Returns the concatenation of all the values returned by the C<BLOCK> in list
context, or the count of the number of items that would have been returned in
scalar context.
@result = pairmap { "The key $a has value $b" } @kvlist
As with C<map> aliasing C<$_> to list elements, C<pairmap> aliases C<$a> and
C<$b> to elements of the given list. Any modifications of it by the code block
will be visible to the caller.
See L</KNOWN BUGS> for a known-bug with C<pairmap>, and a workaround.
=head2 pairs
my @pairs = pairs @kvlist;
I<Since version 1.29.>
A convenient shortcut to operating on even-sized lists of pairs, this function
returns a list of ARRAY references, each containing two items from the given
list. It is a more efficient version of
@pairs = pairmap { [ $a, $b ] } @kvlist
It is most convenient to use in a C<foreach> loop, for example:
foreach my $pair ( pairs @KVLIST ) {
my ( $key, $value ) = @$pair;
...
}
Since version C<1.39> these ARRAY references are blessed objects, recognising
the two methods C<key> and C<value>. The following code is equivalent:
foreach my $pair ( pairs @KVLIST ) {
my $key = $pair->key;
my $value = $pair->value;
...
}
=head2 pairkeys
my @keys = pairkeys @kvlist;
I<Since version 1.29.>
A convenient shortcut to operating on even-sized lists of pairs, this function
returns a list of the the first values of each of the pairs in the given list.
It is a more efficient version of
@keys = pairmap { $a } @kvlist
=head2 pairvalues
my @values = pairvalues @kvlist;
I<Since version 1.29.>
A convenient shortcut to operating on even-sized lists of pairs, this function
returns a list of the the second values of each of the pairs in the given list.
It is a more efficient version of
@values = pairmap { $b } @kvlist
=cut
=head1 OTHER FUNCTIONS
=cut
=head2 shuffle
my @values = shuffle @values;
Returns the values of the input in a random order
@cards = shuffle 0..51 # 0..51 in a random order
=cut
=head1 KNOWN BUGS
=head2 RT #95409
L<https://rt.cpan.org/Ticket/Display.html?id=95409>
If the block of code given to L</pairmap> contains lexical variables that are
captured by a returned closure, and the closure is executed after the block
has been re-used for the next iteration, these lexicals will not see the
correct values. For example:
my @subs = pairmap {
my $var = "$a is $b";
sub { print "$var\n" };
} one => 1, two => 2, three => 3;
$_->() for @subs;
Will incorrectly print
three is 3
three is 3
three is 3
This is due to the performance optimisation of using C<MULTICALL> for the code
block, which means that fresh SVs do not get allocated for each call to the
block. Instead, the same SV is re-assigned for each iteration, and all the
closures will share the value seen on the final iteration.
To work around this bug, surround the code with a second set of braces. This
creates an inner block that defeats the C<MULTICALL> logic, and does get fresh
SVs allocated each time:
my @subs = pairmap {
{
my $var = "$a is $b";
sub { print "$var\n"; }
}
} one => 1, two => 2, three => 3;
This bug only affects closures that are generated by the block but used
afterwards. Lexical variables that are only used during the lifetime of the
block's execution will take their individual values for each invocation, as
normal.
=head1 SUGGESTED ADDITIONS
The following are additions that have been requested, but I have been reluctant
to add due to them being very simple to implement in perl
# How many elements are true
sub true { scalar grep { $_ } @_ }
# How many elements are false
sub false { scalar grep { !$_ } @_ }
=head1 SEE ALSO
L<Scalar::Util>, L<List::MoreUtils>
=head1 COPYRIGHT
Copyright (c) 1997-2007 Graham Barr <gbarr@pobox.com>. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
Recent additions and current maintenance by
Paul Evans, <leonerd@leonerd.org.uk>.
=cut
X86_64-LINUX-THREAD-MULTI_LIST_UTIL
$fatpacked{"x86_64-linux-thread-multi/List/Util/XS.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'X86_64-LINUX-THREAD-MULTI_LIST_UTIL_XS';
package List::Util::XS;
use strict;
use List::Util;
our $VERSION = "1.41"; # FIXUP
$VERSION = eval $VERSION; # FIXUP
1;
__END__
=head1 NAME
List::Util::XS - Indicate if List::Util was compiled with a C compiler
=head1 SYNOPSIS
use List::Util::XS 1.20;
=head1 DESCRIPTION
C<List::Util::XS> can be used as a dependency to ensure List::Util was
installed using a C compiler and that the XS version is installed.
During installation C<$List::Util::XS::VERSION> will be set to
C<undef> if the XS was not compiled.
Starting with release 1.23_03, Scalar-List-Util is B<always> using
the XS implementation, but for backwards compatibility, we still
ship the C<List::Util::XS> module which just loads C<List::Util>.
=head1 SEE ALSO
L<Scalar::Util>, L<List::Util>, L<List::MoreUtils>
=head1 COPYRIGHT
Copyright (c) 2008 Graham Barr <gbarr@pobox.com>. All rights reserved.
This program is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.
=cut
X86_64-LINUX-THREAD-MULTI_LIST_UTIL_XS
$fatpacked{"x86_64-linux-thread-multi/Scalar/Util.pm"} = '#line '.(1+__LINE__).' "'.__FILE__."\"\n".<<'X86_64-LINUX-THREAD-MULTI_SCALAR_UTIL';
# Copyright (c) 1997-2007 Graham Barr <gbarr@pobox.com>. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Maintained since 2013 by Paul Evans <leonerd@leonerd.org.uk>
package Scalar::Util;
use strict;
require Exporter;
require List::Util; # List::Util loads the XS
our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
blessed refaddr reftype weaken unweaken isweak
dualvar isdual isvstring looks_like_number openhandle readonly set_prototype
tainted
);
our $VERSION = "1.41";
$VERSION = eval $VERSION;
our @EXPORT_FAIL;
unless (defined &weaken) {
push @EXPORT_FAIL, qw(weaken);
}
unless (defined &isweak) {
push @EXPORT_FAIL, qw(isweak isvstring);
}
unless (defined &isvstring) {
push @EXPORT_FAIL, qw(isvstring);
}
sub export_fail {
if (grep { /^(?:weaken|isweak)$/ } @_ ) {
require Carp;
Carp::croak("Weak references are not implemented in the version of perl");
}
if (grep { /^isvstring$/ } @_ ) {
require Carp;
Carp::croak("Vstrings are not implemented in the version of perl");
}
@_;
}
# set_prototype has been moved to Sub::Util with a different interface
sub set
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment