builder
=>
'NoEnv'
,
writer
=> {
response_line
=> 1,
before
=> {
finalize
=>
sub
{
my
(
$self
,
$req
,
$res
) =
@_
;
$res
->headers->date(
time
);
if
(
$req
->_connection->{keepalive_available}) {
$res
->headers->header(
Connection
=>
'keep-alive'
);
}
else
{
$res
->headers->header(
Connection
=>
'close'
);
}
}
}
}
;
BEGIN {
if
(
$ENV
{SMART_COMMENTS} ) {
Any::Moose::load_class(
'Smart::Comments'
);
Smart::Comments->
import
;
}
}
has
host
=> (
is
=>
'ro'
,
isa
=>
'Str'
,
default
=>
'127.0.0.1'
,
);
has
port
=> (
is
=>
'ro'
,
isa
=>
'Int'
,
default
=> 1978,
);
has
keepalive
=> (
is
=>
'ro'
,
isa
=>
'Bool'
,
default
=> 0,
);
has
keepalive_timeout
=> (
is
=>
'ro'
,
isa
=>
'Int'
,
default
=> 5,
);
has
fork
=> (
is
=>
'ro'
,
isa
=>
'Bool'
,
default
=> 0,
);
has
allowed
=> (
is
=>
'rw'
,
isa
=>
'HashRef'
,
default
=>
sub
{ {
'127.0.0.1'
=>
'255.255.255.255'
} },
);
has
argv
=> (
is
=>
'ro'
,
isa
=>
'ArrayRef'
,
default
=>
sub
{ [] },
);
sub
run {
my
(
$self
) =
@_
;
if
(
$self
->keepalive && !
$self
->
fork
) {
Carp::croak
"set fork=1 if you want to work with keepalive!"
;
}
my
$daemon
= IO::Socket::INET->new(
Listen
=> SOMAXCONN,
LocalAddr
=>
$self
->host,
LocalPort
=>
$self
->port,
Proto
=>
'tcp'
,
ReuseAddr
=> 1,
Type
=> SOCK_STREAM,
) or
die
"Couldn't create daemon: $!"
;
my
$restart
= 0;
my
$parent
= $$;
my
$pid
=
undef
;
local
$SIG
{CHLD} =
'IGNORE'
;
while
(
my
(
$remote
,
$peername
) =
$daemon
->
accept
) {
next
unless
my
(
$method
,
$uri
,
$protocol
) =
$self
->_parse_request_line(
$remote
);
unless
(
uc
$method
eq
'RESTART'
) {
next
if
$self
->
fork
&& (
$pid
=
fork
);
$self
->_handler(
$remote
,
$method
,
$uri
,
$protocol
,
$peername
);
if
(
defined
$pid
) {
$daemon
->
close
;
exit
();
}
}
else
{
if
(
$self
->_can_restart(
$peername
)) {
$restart
= 1;
last
;
}
}
}
continue
{
close
$remote
;
}
$daemon
->
close
;
if
(
$restart
) {
$SIG
{CHLD} =
'DEFAULT'
;
wait
;
exec
$^X, $0, @{
$self
->argv };
}
exit
;
}
sub
_handler {
my
(
$self
,
$remote
,
$method
,
$uri
,
$protocol
,
$peername
) =
@_
;
local
$SIG
{PIPE} =
sub
{
close
$remote
};
$protocol
=
'1.0'
;
my
$select
= IO::Select->new(
$remote
);
$remote
->autoflush(1);
while
(1) {
my
$headers
=
$self
->_parse_header(
$remote
,
$protocol
);
my
$connection
=
lc
$headers
->header(
"Connection"
);
my
$keepalive_available
=
$self
->keepalive
&&
index
(
$connection
,
'keep-alive'
) > -1
;
$self
->_handle_one(
$remote
,
$method
,
$uri
,
$protocol
,
$peername
,
$headers
,
$keepalive_available
);
if
(
$keepalive_available
) {
last
unless
$select
->can_read(
$self
->keepalive_timeout);
last
unless
(
$method
,
$uri
,
$protocol
) =
$self
->_parse_request_line(
$remote
, 1);
}
else
{
last
;
}
}
$remote
->
read
(
my
$buf
, 4096)
if
$select
->can_read(0);
$remote
->
close
();
}
sub
_parse_request_line {
my
(
$self
,
$handle
,
$is_keepalive
) =
@_
;
my
$line
=
$self
->_get_line(
$handle
);
if
(
$is_keepalive
&& (
$line
eq
''
||
$line
eq
"\015"
)) {
$line
=
$self
->_get_line(
$handle
);
}
return
()
unless
my
(
$method
,
$uri
,
$protocol
) =
$line
=~ m/\A(\w+)\s+(\S+)(?:\s+HTTP\/(\d+(?:\.\d+)?))?\z/;
return
(
$method
,
$uri
,
$protocol
);
}
sub
_peeraddr {
my
(
$self
,
$peername
) =
@_
;
my
(
undef
,
$iaddr
) = sockaddr_in(
$peername
);
return
inet_ntoa(
$iaddr
) ||
"127.0.0.1"
;
}
sub
_get_line {
my
(
$self
,
$handle
) =
@_
;
my
$line
=
''
;
while
(
$handle
->
read
(
my
$byte
, 1)) {
last
if
$byte
eq
"\012"
;
$line
.=
$byte
;
}
$line
=~ s/\015$//s;
$line
;
}
sub
_parse_header {
my
(
$self
,
$remote
,
$protocol
) =
@_
;
if
(
$protocol
>= 1 ) {
my
@hdr
;
while
(
length
(
my
$line
=
$self
->_get_line(
$remote
) ) ) {
if
(
$line
=~ s/^([^\s:]+)[ \t]*: ?(.*)// ) {
push
(
@hdr
, $1, $2 );
}
elsif
(
@hdr
&&
$line
=~ s/^([ \t].*)// ) {
$hdr
[-1] .=
"\n$1"
;
}
else
{
last
;
}
}
HTTP::Headers::Fast->new(
@hdr
);
}
else
{
HTTP::Headers::Fast->new;
}
}
sub
_handle_one {
my
(
$self
,
$remote
,
$method
,
$uri
,
$protocol
,
$peername
,
$headers
,
$keepalive_available
) =
@_
;
local
*STDOUT
=
$remote
;
$self
->handle_request(
uri
=> URI::WithBase->new(
do
{
my
$u
= URI->new(
$uri
);
$u
->scheme(
'http'
);
$u
->host(
$headers
->header(
'Host'
) ||
$self
->host);
$u
->port(
$self
->port);
$u
->path(
'/'
)
if
$uri
=~ m!^https?://!i;
my
$b
=
$u
->clone;
$b
->path_query(
'/'
);
(
$u
,
$b
);
},
),
headers
=>
$headers
,
_connection
=> {
input_handle
=>
$remote
,
output_handle
=>
$remote
,
env
=> {},
keepalive_available
=>
$keepalive_available
,
},
connection_info
=> {
method
=>
$method
,
address
=>
$self
->_peeraddr(
$peername
),
port
=>
$self
->port,
protocol
=>
"HTTP/$protocol"
,
user
=>
undef
,
_https_info
=>
undef
,
request_uri
=>
$uri
,
},
);
}
sub
_can_restart {
my
(
$self
,
$peername
) =
@_
;
my
$peeraddr
= _inet_addr(
$self
->_peeraddr(
$peername
));
my
$allowed
=
$self
->allowed;
for
my
$ip
(
keys
%{
$allowed
}) {
my
$mask
=
$allowed
->{
$ip
};
if
((
$peeraddr
& _inet_addr(
$mask
)) == _inet_addr(
$ip
)) {
return
1
}
}
return
0;
}
sub
_inet_addr {
unpack
"N*"
, inet_aton(
$_
[0]) }
__INTERFACE__