view release on metacpan or search on metacpan
lib/Any/Daemon/FCGI.pm view on Meta::CPAN
use parent 'IO::Socket::IP';
use warnings;
use strict;
use Log::Report 'any-daemon-http';
use Any::Daemon::FCGI::ClientConn ();
sub new(%)
{ my ($class, %args) = @_;
$args{Listen} ||= 5;
$args{Proto} ||= 'tcp';
$class->SUPER::new(%args);
}
#----------------
#----------------
sub accept(;$)
{ my $self = shift;
my $pkg = shift // 'Any::Daemon::FCGI::ClientConn';
$self->SUPER::accept($pkg);
}
1;
lib/Any/Daemon/FCGI/ClientConn.pm view on Meta::CPAN
( REQUEST_COMPLETE => 0
, CANT_MPX_CONN => 1
, OVERLOADED => 2
, UNKNOWN_ROLE => 3
);
my %server_role_id2name = reverse %server_role_name2id;
my %frame_id2name = reverse %frame_name2id;
sub new($%) { (bless {}, $_[0])->init($_[1]) }
sub init($)
{ my ($self, $args) = @_;
$self->{ADFC_requests} = {};
$self->{ADFC_max_conns} = $args->{max_childs} or panic;
$self->{ADFC_max_reqs} = $args->{max_childs};
$self->{ADFC_select} = my $select = IO::Select->new;
$self->{ADFC_socket} = my $socket = $args->{socket} or panic;
$self->{ADFC_stdin} = \my $stdin;
$self->{ADFC_keep_conn} = 0;
$select->add($socket);
$self;
}
#----------------
sub socket() { shift->{ADFC_socket} }
#----------------
sub _next_record()
{ my $self = shift;
my $leader = $self->_read_chunk(8);
length $leader==8 or return;
my ($version, $type_id, $req_id, $clen, $plen) = unpack 'CCnnC', $leader;
my $body = $self->_read_chunk($clen + $plen);
substr $body, -$plen, $plen, '' if $plen; # remove padding bytes
length $body==$clen or return;
($frame_id2name{$type_id} || 'UNKNOWN_TYPE', $req_id, \$body);
}
sub _reply_record($$$)
{ my ($self, $type, $req_id, $body) = @_;
my $type_id = $frame_name2id{$type} or panic $type;
my $empty = ! length $body; # write one empty frame
while(length $body || $empty)
{ my $chunk = substr $body, 0, MAX_FRAME_SEND, '';
my $size = length $chunk;
my $pad = (-$size) % 8; # advise to pad on 8 bytes
my $frame = pack "CCnnCxa${size}x${pad}"
, FCGI_VERSION, $type_id, $req_id, $size, $pad, $chunk;
lib/Any/Daemon/FCGI/ClientConn.pm view on Meta::CPAN
return unless $! == EAGAIN || $! == EINTR || $! == EWOULDBLOCK;
usleep 1000; # 1 ms
}
last if $empty;
}
}
sub get_request()
{ my $self = shift;
my $requests = $self->{ADFC_requests};
my $reqdata;
### At the moment, we will only support processing of whole requests
# and full replies: no chunking inside the server.
while(1)
{ my ($type, $req_id, $body) = $self->_next_record
or return;
lib/Any/Daemon/FCGI/ClientConn.pm view on Meta::CPAN
info __x"fcgi {id} request from {host}"
, id => $req_id
, host => $remote_host || $remote_ip;
$self->keep_connection
or $self->socket->shutdown(SHUT_RD);
$request;
}
sub send_response($;$)
{ my ($self, $response, $stderr) = @_;
#XXX Net::Async::FastCGI::Request demonstrates how to catch stdout and
#XXX stderr via ties. We don't use that here: cleanly work with
#XXX HTTP::Message objects... errors are logged locally.
my $req_id = $response->request->request_id;
# Simply "Status: " in front of the Response header will make the whole
# message HTTP::Response into a valid CGI response.
lib/Any/Daemon/FCGI/ClientConn.pm view on Meta::CPAN
}
$self->_fcgi_end_request(REQUEST_COMPLETE => $req_id);
$self->keep_connection
or $self->socket->shutdown(SHUT_WR);
$self;
}
sub keep_connection()
{ my $self = shift;
$self->{ADFC_keep_conn} || keys %{$self->{ADFC_requests}}
}
#### MANAGEMENT RECORDS
# have req_id==0
sub _management_record($$)
{ my ($self, $type, $body) = @_;
$type eq 'GET_VALUES' ? $self->_fcgi_get_values($body)
: $self->_fcgi_unknown($body);
}
# Request record FCGI_GET_VALUES may be used by the front-end server to
# collect back_end settings. In Apache, you have to configure it manually.
sub _fcgi_get_values($)
{ my $self = shift;
my %need = $self->_body2hash(shift);
# The maximum number of concurrent transport connections this
# application will accept.
$need{FCGI_MAX_CONNS} = $self->{ADFC_max_conns}
if exists $need{FCGI_MAX_CONNS};
# The maximum number of concurrent requests this application will accept.
$need{FCGI_MAX_REQS} = $self->{ADFC_max_reqs}
lib/Any/Daemon/FCGI/ClientConn.pm view on Meta::CPAN
# concurrent requests over each connection), "1" otherwise.
$need{FCGI_MPXS_CONNS} = 0
if exists $need{FCGI_MPXS_CONNS};
$self->_reply_record(GET_VALUES_RESULT => 0, $self->hash2body(\%need));
}
# Reply record FCGI_UNKNOWN_TYPE is designed for protocol upgrades: to
# respond to unknown record types.
sub _fcgi_unknown($)
{ my ($self, $body) = @_;
$self->_reply_record(UNKNOWN_TYPE => 0, '');
}
# Reply END_REQUEST is used for all ways to close a BEGIN_REQUEST session.
# It depends on the $status code which additionals fields were sent.
sub _fcgi_end_request($$;$)
{ my ($self, $status, $req_id, $rc) = @_;
my $body = pack "nCCCC", $rc || 0, $end_status2id{$status}
, RESERVED, RESERVED, RESERVED;
$self->_reply_record(END_REQUEST => $req_id, $body);
}
# Convert the FGCI request into a full HTTP::Request object
sub _body2hash($$)
{ my ($self, $body) = @_;
my %h;
while(length $$body)
{ my $name_len = $self->_take_encoded_nv($body);
my $value_len = $self->_take_encoded_nv($body);
my $name = substr $$body, 0, $name_len, '';
$h{$name} = substr $$body, 0, $value_len, '';
}
\%h;
}
sub _hash2body($)
{ my ($self, $h) = @_;
my @params;
foreach my $name (sort keys %$h)
{ my $name_len = length $name;
my $val_len = length $h->{$name};
push @params, pack "NNxa{$name_len}xa{$val_len}"
, $name_len, $val_len, $name, $h->{$name};
}
join '', @params;
}
# Numerical values are 1 or 4 bytes. Long when first bit == 1
sub _take_encoded_nv($)
{ my ($self, $body) = @_;
my $short = unpack 'C', substr $$body, 0, 1, '';
$short & 0x80 or return $short;
my $long = pack('C', $short & 0x7F) . substr($$body, 0, 3, '');
unpack 'N', $long;
}
sub _read_chunk($)
{ my ($self, $need) = @_;
my $stdin = $self->{ADFC_stdin};
return substr $$stdin, 0, $need, ''
if length $$stdin > $need;
my $select = $self->{ADFC_select};
while(length $$stdin < $need)
{ $select->can_read or next;
lib/Any/Daemon/FCGI/Request.pm view on Meta::CPAN
$VERSION = '0.30';
use base 'HTTP::Request';
use warnings;
use strict;
use Log::Report 'any-daemon-http';
sub new($)
{ my ($class, $args) = @_;
my $params = $args->{params} or panic;
my $role = $args->{role} or panic;
my @headers;
# Content-Type and Content-Length come specially
push @headers, 'Content-Type' => $params->{CONTENT_TYPE}
if exists $params->{CONTENT_TYPE};
lib/Any/Daemon/FCGI/Request.pm view on Meta::CPAN
$self->{ADFR_params} = $params;
$self->{ADFR_role} = $role;
$self->{ADFR_data} = $args->{data};
$self;
}
#----------------
sub request_id { shift->{ADFR_reqid} }
sub params() { shift->{ADFR_params} }
sub param($) { $_[0]->{ADFR_params}{$_[1]} }
sub role() { shift->{ADFR_role} }
sub data() { shift->{ADFR_data} }
1;
lib/Any/Daemon/HTTP.pm view on Meta::CPAN
use constant # default ports
{ PORT_HTTP => 80
, PORT_HTTPS => 443
};
# To support IPv6, replace ::INET by ::IP
@HTTP::Daemon::ClientConn::ISA = qw(IO::Socket::IP);
sub _to_list($) { ref $_[0] eq 'ARRAY' ? @{$_[0]} : defined $_[0] ? $_[0] : () }
sub init($)
{ my ($self, $args) = @_;
$self->SUPER::init($args);
my $listen = $args->{listen} || $args->{socket} || $args->{host};
my (@sockets, @hosts);
foreach my $conn (_to_list $listen)
{ my ($socket, @host) = $self->_create_socket($conn
, protocol => $args->{protocol}
);
lib/Any/Daemon/HTTP.pm view on Meta::CPAN
$self->addVirtualHost
( name => $host
, aliases => [@hosts, 'default']
, documents => $args->{documents}
, handler => $handler
) if $args->{documents} || $handler;
$self;
}
sub _create_socket($%)
{ my ($self, $listen, %args) = @_;
defined $listen or return;
return ($listen, $listen->sockhost.':'.$listen->sockport)
if blessed $listen && $listen->isa('IO::Socket');
my $port = $listen =~ s/\:([0-9]+)$// ? $1 : PORT_HTTP;
my $host = $listen;
my $proto = $self->{ADH_protocol}
= $args{protocol} || ($port==PORT_HTTPS ? 'HTTPS' : 'HTTP');
lib/Any/Daemon/HTTP.pm view on Meta::CPAN
, address => "$host:$port";
notice __x"got socket after {secs} seconds", secs => $elapse
if $elapse;
($socket, "$listen:$port", $socket->sockhost.':'.$socket->sockport);
}
#----------------
sub sockets() { @{shift->{ADH_sockets}} }
sub hosts() { @{shift->{ADH_hosts}} }
sub protocol() { shift->{ADH_protocol} }
#-------------
sub addVirtualHost(@)
{ my $self = shift;
my $config = @_ > 1 ? +{@_} : !defined $_[0] ? return : shift;
my $vhost;
if(blessed $config && $config->isa('Any::Daemon::HTTP::VirtualHost'))
{ $vhost = $config }
elsif(ref $config eq 'HASH')
{ $vhost = $self->{ADH_vhost_class}->new($config) }
else { error __x"virtual host configuration not a valid object nor HASH" }
info __x"adding virtual host {name}", name => $vhost->name;
$self->{ADH_vhosts}{$_} = $vhost
for $vhost->name, $vhost->aliases;
$vhost;
}
sub addProxy(@)
{ my $self = shift;
my $config = @_ > 1 ? +{@_} : !defined $_[0] ? return : shift;
my $proxy;
if(UNIVERSAL::isa($config, 'Any::Daemon::HTTP::Proxy'))
{ $proxy = $config }
elsif(UNIVERSAL::isa($config, 'HASH'))
{ $proxy = $self->{ADH_proxy_class}->new($config) }
else { error __x"proxy configuration not a valid object nor HASH" }
$proxy->forwardMap
or error __x"proxy {name} has no map, so needs inside vhost"
, name => $proxy->name;
info __x"adding proxy {name}", name => $proxy->name;
push @{$self->{ADH_proxies}}, $proxy;
}
sub removeVirtualHost($)
{ my ($self, $id) = @_;
my $vhost = blessed $id && $id->isa('Any::Daemon::HTTP::VirtualHost')
? $id : $self->virtualHost($id);
defined $vhost or return;
delete $self->{ADH_vhosts}{$_}
for $vhost->name, $vhost->aliases;
$vhost;
}
sub virtualHost($) { $_[0]->{ADH_vhosts}{$_[1]} }
sub proxies() { @{shift->{ADH_proxies}} }
sub findProxy($$$)
{ my ($self, $session, $req, $host) = @_;
my $uri = $req->uri->abs("http://$host");
foreach my $proxy ($self->proxies)
{ my $mapped = $proxy->forwardRewrite($session, $req, $uri) or next;
return ($proxy, $mapped);
}
();
}
#-------------------
sub _connection($$)
{ my ($self, $client, $args) = @_;
my $nr_req = 0;
my $max_req = $args->{max_req_per_conn} ||= 100;
my $start = time;
my $deadline = $start + ($args->{max_time_per_conn} ||= 120);
my $bonus = $args->{req_time_bonus} //= 2;
my $conn;
if($self->protocol eq 'FCGI')
lib/Any/Daemon/HTTP.pm view on Meta::CPAN
$resp->header(Connection => ($close ? 'close' : 'open'));
$conn->send_response($resp);
last if $close;
}
alarm 0;
$nr_req;
}
sub _clean_uri($)
{ my ($self, $uri) = @_;
my $path = $uri->path;
for($path)
{ 1 while s!/[^/.]+/+\.\.(/|\z)!/$1!
|| s!^/\.\.(/|\z)!/$1!
|| s!/{2,}!/! # //
|| s!/\.(/|\z)!/!; # /./
}
$uri->path($path);
}
sub run(%)
{ my ($self, %args) = @_;
my $new_child = $args{new_child} || 'newChild';
$args{new_connection} ||= 'newConnection';
my $vhosts = $self->{ADH_vhosts};
unless(keys %$vhosts)
{ my ($host, @aliases) = $self->hosts;
$self->addVirtualHost(name => $host, aliases => ['default', @aliases]);
}
lib/Any/Daemon/HTTP.pm view on Meta::CPAN
$self->psTitle("$title idle after $conn_count");
}
0;
};
info __x"start running the webserver";
$self->SUPER::run(%args);
}
sub newConnection($)
{ my ($self, $session) = @_;
return $self;
}
sub newChild($)
{ my ($self, $select) = @_;
return $self;
}
sub psTitle($)
{ my ($self, $string) = @_;
$0 = $string if $self->{ADH_show_ps};
}
# HTTP::Daemon methods used by ::ClientConn. We steal that parent role,
# but need to mimic the object a little. The names are not compatible
# with MarkOv's convention, so hidden for the users of this module
sub url() { shift->{ADH_host_base} }
sub product_tokens() {shift->{ADH_server}}
1;
__END__
lib/Any/Daemon/HTTP/Directory.pm view on Meta::CPAN
use File::Spec ();
use File::Basename qw/dirname/;
use POSIX::1003 qw/strftime :fd :fs/;
use HTTP::Status qw/:constants/;
use HTTP::Response ();
use Encode qw/encode/;
use MIME::Types ();
my $mimetypes = MIME::Types->new(only_complete => 1);
sub _filename_trans($$);
sub init($)
{ my ($self, $args) = @_;
$self->SUPER::init($args);
my $path = $self->path;
my $loc = $args->{location}
or error __x"directory definition requires location";
my $trans;
if(ref $loc eq 'CODE')
{ $trans = $loc;
lib/Any/Daemon/HTTP/Directory.pm view on Meta::CPAN
my $if = $args->{index_file};
my @if = ref $if eq 'ARRAY' ? @$if
: defined $if ? $if
: qw/index.html index.htm/;
$self->{ADHD_indexfns} = \@if;
$self;
}
#-----------------
sub location() {shift->{ADHD_location}}
sub charset() {shift->{ADHD_charset}}
#-----------------
#-----------------------
sub filename($) { $_[0]->{ADHD_fn}->($_[1]) }
sub _filename_trans($$)
{ my ($path, $loc) = @_;
return $loc if ref $loc eq 'CODE';
sub
{ my $x = shift;
$x =~ s!^\Q$path!$loc! or panic "path $x not inside $path";
$x;
};
}
sub _collect($$$$)
{ my ($self, $vhost, $session, $req, $uri) = @_;
my $item = $self->filename($uri);
# soft-fail when the item does not exists
-e $item or return;
return $self->_file_response($req, $item)
if -f _;
lib/Any/Daemon/HTTP/Directory.pm view on Meta::CPAN
{ -f $item.$if or next;
return $self->_file_response($req, $item.$if);
}
$self->{ADHD_dirlist}
or return HTTP::Response->new(HTTP_FORBIDDEN, "no directory lists");
$self->_list_response($req, $uri, $item);
}
sub _file_response($$)
{ my ($self, $req, $fn) = @_;
-f $fn
or return HTTP::Response->new(HTTP_NOT_FOUND);
open my $fh, '<:raw', $fn
or return HTTP::Response->new(HTTP_FORBIDDEN);
my ($dev, $inode, $mtime) = (stat $fh)[0,1,9];
my $etag = "$dev-$inode-$mtime";
lib/Any/Daemon/HTTP/Directory.pm view on Meta::CPAN
}
$head->content_type($ct);
$head->last_modified($mtime);
$head->header(ETag => $etag);
local $/;
HTTP::Response->new(HTTP_OK, undef, $head, <$fh>);
}
sub _list_response($$$)
{ my ($self, $req, $uri, $dir) = @_;
no warnings 'uninitialized';
my $list = $self->list($dir);
my $now = localtime;
my @rows;
push @rows, <<__UP if $dir ne '/';
<tr><td colspan="5"> </td><td><a href="../">(up)</a></td></tr>
lib/Any/Daemon/HTTP/Directory.pm view on Meta::CPAN
my %filetype =
( &S_IFSOCK => 's', &S_IFLNK => 'l', &S_IFREG => '-', &S_IFBLK => 'b'
, &S_IFDIR => 'd', &S_IFCHR => 'c', &S_IFIFO => 'p');
my @flags = ('---', '--x', '-w-', '-wx', 'r--', 'r-x', 'rw-', 'rwx');
my @stat_fields =
qw/dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks/;
sub list($@)
{ my ($self, $dirname, %opts) = @_;
opendir my $from_dir, $dirname
or return;
my $names = $opts{names} || qr/^[^.]/;
my $prefilter
= ref $names eq 'Regexp' ? sub { $_[0] =~ $names }
: ref $names eq 'CODE' ? $names
: panic "::Directory::list(names) must be regexp or code, not $names";
lib/Any/Daemon/HTTP/Proxy.pm view on Meta::CPAN
use warnings;
use strict;
use Log::Report 'any-daemon-http';
use LWP::UserAgent ();
use HTTP::Status qw(HTTP_TOO_MANY_REQUESTS);
use Time::HiRes qw(time);
sub init($)
{ my ($self, $args) = @_;
$self->SUPER::init($args);
$self->{ADHDP_ua} = $args->{user_agent}
|| LWP::UserAgent->new(keep_alive => 30);
$self->{ADHDP_via} = $args->{via};
if(my $fm = $args->{forward_map})
{ $self->{ADHDP_map} = $fm eq 'RELAY' ? sub {$_[3]} : $fm;
}
lib/Any/Daemon/HTTP/Proxy.pm view on Meta::CPAN
, $args->{change_response} || ()
);
$self->{ADHDP_prepare} = \@prepare;
$self->{ADHDP_postproc} = \@postproc;
$self;
}
#-----------------
sub userAgent() {shift->{ADHDP_ua}}
sub via() {shift->{ADHDP_via}}
sub forwardMap(){shift->{ADHDP_map}}
sub remoteProxy(@)
{ my $rem = shift->{ADHDP_proxy};
$rem ? $rem->(@_) : undef;
}
#-----------------
my $last_used_proxy = '';
sub _collect($$$$)
{ my ($self, $vhost, $session, $req, $rel_uri) = @_;
my $resp;
my $vhost_name = $vhost ? $vhost->name : '';
my $tohost = $req->header('Host') || $vhost_name;
#XXX MO: need to support https as well
my $uri = URI->new_abs($rel_uri, "http://$tohost");
# Via: RFC2616 section 14.45
lib/Any/Daemon/HTTP/Proxy.pm view on Meta::CPAN
, method => $req->method, uri => "$uri", status => $resp->code;
}
$self->$_($resp, $uri)
for @{$self->{ADHDP_postproc}};
$resp;
}
sub stripHeaders(@)
{ my $self = shift;
my @strip;
foreach my $field (@_ > 1 ? @_ : ref $_[0] eq 'ARRAY' ? @{$_[0]} : shift)
{ push @strip
, !ref $field ? sub {$_[0]->remove_header($field)}
: ref $field eq 'CODE' ? $field
: ref $field eq 'Regex' ? sub {
my @kill = grep $_ =~ $field, $_[0]->header_field_names;
$_[0]->remove_header($_) for @kill;
}
: panic "do not understand $field";
}
@strip or return;
sub { my $header = $_[1]->headers; $_->($header) for @strip };
}
sub addHeaders($@)
{ my $self = shift;
return if @_==1 && ref $_[0] eq 'CODE';
my @pairs = @_ > 1 ? @_ : defined $_[0] ? @{$_[0]} : ();
@pairs or return sub {};
sub { $_[1]->push_header(@pairs) };
}
sub proxify($$)
{ my ($self, $request, $uri) = @_;
$request->uri($uri);
$request->header(Host => $uri->authority);
}
sub forwardRewrite($$$)
{ my ($self, $session, $req, $uri) = @_;
$self->allow($session, $req, $uri) or return;
my $mapper = $self->forwardMap or return;
$mapper->(@_);
}
sub forwardRequest($$$)
{ my ($self, $session, $req, $uri) = @_;
$self->_collect(undef, $session, $req, $uri);
}
#----------------
1;
lib/Any/Daemon/HTTP/Session.pm view on Meta::CPAN
use strict;
use warnings;
use Log::Report 'any-daemon-http';
use Socket qw(inet_aton AF_INET AF_INET6 PF_INET PF_INET6);
sub new(%) {my $class = shift; (bless {}, $class)->init({@_})}
sub init($)
{ my ($self, $args) = @_;
$self->{ADHC_store} = $args->{store} || {};
$self;
}
#-----------------
sub client() {shift->{ADHC_client}}
sub get(@) {my $s = shift->{ADHC_store}; wantarray ? @{$s}{@_} : $s->{$_[0]}}
sub set($$) {$_[0]->{ADHC_store}{$_[1]} = $_[2]}
# should not be used
sub _store() {shift->{ADHC_store}}
1;
lib/Any/Daemon/HTTP/Source.pm view on Meta::CPAN
use warnings;
use strict;
use Log::Report 'any-daemon-http';
use Net::CIDR qw/cidrlookup/;
use List::Util qw/first/;
use HTTP::Status qw/HTTP_FORBIDDEN/;
sub _allow_cleanup($);
sub _allow_match($$$$);
sub new(@)
{ my $class = shift;
my $args = @_==1 ? shift : +{@_};
(bless {}, $class)->init($args);
}
sub init($)
{ my ($self, $args) = @_;
my $path = $self->{ADHS_path} = $args->{path} || '/';
$self->{ADHS_allow} = _allow_cleanup $args->{allow};
$self->{ADHS_deny} = _allow_cleanup $args->{deny};
$self->{ADHS_name} = $args->{name} || $path;
$self;
}
#-----------------
sub path() {shift->{ADHS_path}}
sub name() {shift->{ADHS_name}}
#-----------------
sub allow($$$$)
{ my ($self, $session, $req, $uri) = @_;
if(my $allow = $self->{ADHS_allow})
{ $self->_allow_match($session, $uri, $allow) or return 0;
}
if(my $deny = $self->{ADHS_deny})
{ $self->_allow_match($session, $uri, $deny) and return 0;
}
1;
}
sub _allow_match($$$$)
{ my ($self, $session, $uri, $rules) = @_;
my $peer = $session->get('peer');
first { $_->($peer->{ip}, $peer->{host}, $session, $uri) } @$rules;
}
sub _allow_cleanup($)
{ my $p = shift or return;
my @p;
foreach my $r (ref $p eq 'ARRAY' ? @$p : $p)
{ push @p
, ref $r eq 'CODE' ? $r
: index($r, ':') >= 0 ? sub {cidrlookup $_[0], $r} # IPv6
: $r !~ m/[a-zA-Z]/ ? sub {cidrlookup $_[0], $r} # IPv4
: substr($r,0,1) eq '.' ? sub {$_[1] =~ qr/(^|\.)\Q$r\E$/i} # Domain
: sub {lc($_[1]) eq lc($r)} # hostname
}
@p ? \@p : undef;
}
sub collect($$$$)
{ my ($self, $vhost, $session, $req, $uri) = @_;
$self->allow($session, $req, $uri)
or return HTTP::Response->new(HTTP_FORBIDDEN);
$self->_collect($vhost, $session, $req, $uri);
}
sub _collect($$$) { panic "must be extended" }
#-----------------------
#-----------------------
1;
lib/Any/Daemon/HTTP/UserDirs.pm view on Meta::CPAN
$VERSION = '0.30';
use parent 'Any::Daemon::HTTP::Directory';
use warnings;
use strict;
use Log::Report 'any-daemon-http';
sub init($)
{ my ($self, $args) = @_;
my $subdirs = $args->{user_subdirs} || 'public_html';
my %allow = map +($_ => 1), @{$args->{allow_users} || []};
my %deny = map +($_ => 1), @{$args->{deny_users} || []};
$args->{location} ||= $self->userdirRewrite($subdirs, \%allow, \%deny);
$self->SUPER::init($args);
$self;
}
#-----------------
sub userdirRewrite($$$)
{ my ($self, $udsub, $allow, $deny) = @_;
my %homes; # cache
sub { my $path = shift;
my ($user, $pathinfo) = $path =~ m!^/\~([^/]*)(.*)!;
return if keys %$allow && !$allow->{$user};
return if keys %$deny && $deny->{$user};
return if exists $homes{$user} && !defined $homes{$user};
my $d = $homes{$user} ||= (getpwnam $user)[7];
$d ? "$d/$udsub$pathinfo" : undef;
};
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
use Any::Daemon::HTTP::Proxy;
use HTTP::Status qw/:constants/;
use List::Util qw/first/;
use File::Spec ();
use POSIX::1003 qw(strftime);
use Scalar::Util qw(blessed);
use Digest::MD5 qw(md5_base64);
sub new(@)
{ my $class = shift;
my $args = @_==1 ? shift : {@_};
(bless {}, $class)->init($args);
}
sub init($)
{ my ($self, $args) = @_;
my $name = $self->{ADHV_name} = $args->{name};
defined $name
or error __x"virtual host {pkg} has no name", pkg => ref $self;
my $aliases = $args->{aliases} || 'AUTO';
$self->{ADHV_aliases}
= ref $aliases eq 'ARRAY' ? $aliases
: $aliases eq 'AUTO' ? [ $self->generateAliases($name) ]
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
my $dirs = $args->{directories} || $args->{directory} || [];
$self->addDirectory($_) for ref $dirs eq 'ARRAY' ? @$dirs : $dirs;
$self->{ADHV_proxies} = {};
my $proxies = $args->{proxies} || $args->{proxy} || [];
$self->addProxy($_) for ref $proxies eq 'ARRAY' ? @$proxies : $proxies;
$self;
}
sub _user_dirs($)
{ my ($self, $dirs) = @_;
$dirs or return undef;
return Any::Daemon::HTTP::UserDirs->new($dirs)
if ref $dirs eq 'HASH';
return $dirs
if $dirs->isa('Any::Daemon::HTTP::UserDirs');
error __x"vhost {name} user_dirs is not an ::UserDirs object"
, name => $self->name;
}
sub _auto_docs($)
{ my ($self, $docroot) = @_;
$docroot or return;
File::Spec->file_name_is_absolute($docroot)
or error __x"vhost {name} documents directory must be absolute"
, name => $self->name;
-d $docroot
or error __x"vhost {name} documents `{dir}' must point to dir"
, name => $self->name, dir => $docroot;
$docroot =~ s/\\$//; # strip trailing / if present
$self->addDirectory(path => '/', location => $docroot);
}
#---------------------
sub name() {shift->{ADHV_name}}
sub aliases() {@{shift->{ADHV_aliases}}}
sub generateAliases($)
{ my ($thing, $h) = @_;
my @a;
$h =~ m/^(([^.:]+)(?:[^:]*)?)(?:\:([0-9]+))?$/;
push @a, $1 if $3; # name with port
push @a, $2 if $1 ne $2; # hostname vs fqdn
push @a, "$2:$3" if $1 ne $2 && $3; # hostname with port
@a;
}
#---------------------
sub addHandler(@)
{ my $self = shift;
return if @_==1 && !defined $_[0];
my @pairs
= @_ > 1 ? @_
: ref $_[0] eq 'HASH' ? %{$_[0]}
: ( '/' => $_[0]);
my $h = $self->{ADHV_handlers} ||= {};
while(@pairs)
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
$h->{$k} = $v;
}
$h;
}
*addHandlers = \&addHandler;
sub findHandler(@)
{ my $self = shift;
my @path = @_>1 ? @_ : ref $_[0] ? $_[0]->path_segments : split('/', $_[0]);
my $h = $self->{ADHV_handlers} ||= {};
while(@path)
{ my $handler = $h->{join '/', @path};
return $handler if $handler;
pop @path;
}
if(my $handler = $h->{'/'})
{ return $handler;
}
sub { HTTP::Response->new(HTTP_NOT_FOUND) };
}
sub handleRequest($$$;$)
{ my ($self, $server, $session, $req, $uri) = @_;
$uri ||= $req->uri;
info __x"{host} request {uri}", host => $self->name, uri => $uri->as_string;
my $new_uri = $self->rewrite($uri);
if($new_uri ne $uri)
{ info __x"{vhost} rewrote {uri} into {new}", vhost => $self->name
, uri => $uri->as_string, new => $new_uri->as_string;
$uri = $new_uri;
}
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
my $has_etag = $req->headers->header('ETag');
return HTTP::Response->new(HTTP_NOT_MODIFIED, 'cached dynamic data')
if $has_etag && $has_etag eq $etag;
$resp->headers->header(ETag => $etag);
$resp;
}
#----------------------
sub rewrite($) { $_[0]->{ADHV_rewrite}->(@_) }
sub _rewrite_call($)
{ my ($self, $rew) = @_;
$rew or return sub { $_[1] };
return $rew if ref $rew eq 'CODE';
if(ref $rew eq 'HASH')
{ my %lookup = %$rew;
return sub {
my $uri = $_[1] or return undef;
exists $lookup{$uri->path} or return $uri;
URI->new_abs($lookup{$uri->path}, $uri)
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
error __x"rewrite rule method {name} in {vhost} does not exist"
, name => $rew, vhost => $self->name;
}
error __x"unknown rewrite rule type {ref} in {vhost}"
, ref => (ref $rew || $rew), vhost => $self->name;
}
sub redirect($;$)
{ my ($self, $uri, $code) = @_;
HTTP::Response->new($code//HTTP_TEMPORARY_REDIRECT, undef
, [ Location => "$uri" ]
);
}
sub mustRedirect($)
{ my ($self, $uri) = @_;
my $new_uri = $self->{ADHV_redirect}->($self, $uri);
$new_uri && $new_uri ne $uri or return;
info __x"{vhost} redirecting {uri} to {new}"
, vhost => $self->name, uri => $uri->path, new => "$new_uri";
$self->redirect($new_uri);
}
sub _redirect_call($)
{ my ($self, $red) = @_;
$red or return sub { $_[1] };
return $red if ref $red eq 'CODE';
if(ref $red eq 'HASH')
{ my %lookup = %$red;
return sub {
my $uri = $_[1] or return undef;
exists $lookup{$uri->path} or return undef;
URI->new_abs($lookup{$uri->path}, $uri);
lib/Any/Daemon/HTTP/VirtualHost.pm view on Meta::CPAN
error __x"redirect rule method {name} in {vhost} does not exist"
, name => $red, vhost => $self->name;
}
error __x"unknown redirect rule type {ref} in {vhost}"
, ref => (ref $red || $red), vhost => $self->name;
}
sub addSource($)
{ my ($self, $source) = @_;
$source or return;
my $sources = $self->{ADHV_sources};
my $path = $source->path;
if(my $old = exists $sources->{$path})
{ error __x"vhost {name} directory `{path}' defined twice, for `{old}' and `{new}' "
, name => $self->name, path => $path
, old => $old->name, new => $source->name;
}
info __x"add configuration `{name}' to {vhost} for {path}"
, name => $source->name, vhost => $self->name, path => $path;
$sources->{$path} = $source;
}
#------------------
sub filename($)
{ my ($self, $uri) = @_;
my $dir = $self->sourceFor($uri);
$dir ? $dir->filename($uri->path) : undef;
}
sub addDirectory(@)
{ my $self = shift;
my $dir = @_==1 && blessed $_[0] ? shift
: Any::Daemon::HTTP::Directory->new(@_);
$self->addSource($dir);
}
sub sourceFor(@)
{ my $self = shift;
my @path = @_>1 || index($_[0], '/')==-1 ? @_ : split('/', $_[0]);
return $self->{ADHV_udirs}
if substr($path[0], 0, 1) eq '~';
my $sources = $self->{ADHV_sources};
while(@path)
{ my $dir = $sources->{join '/', @path};
return $dir if $dir;
pop @path;
}
# return empty list, not undef, when not found
$sources->{'/'} ? $sources->{'/'} : ();
}
#-----------------------------
sub addProxy(@)
{ my $self = shift;
my $proxy = @_==1 && blessed $_[0] ? shift
: Any::Daemon::HTTP::Proxy->new(@_);
error __x"proxy {name} has a map, so cannot be added to a vhost"
, name => $proxy->name
if $proxy->forwardMap;
info __x"add proxy configuration to {vhost} for {path}"
, vhost => $self->name, path => $proxy->path;