Last active
February 12, 2018 14:47
-
-
Save dex4er/0e2ef2d1582307a14329 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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('<html>')->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 = ( | |
'&' => '&', | |
'<' => '<', | |
'>' => '>', | |
'"' => '"', | |
'\'' => ''' | |
); | |
# 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|->\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 & 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 & 123</div> | |
<div id="foo">test & 123</div> | |
<div data-my-id="1" data-name="test">test & 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