root/lang/perl/HTTP-Engine/trunk/lib/HTTP/Engine/Interface/Standalone.pm @ 18120

Revision 18120, 7.0 kB (checked in by tokuhirom, 5 years ago)

should be ignore case: Connection header.

Line 
1package HTTP::Engine::Interface::Standalone;
2use Moose;
3with 'HTTP::Engine::Role::Interface';
4
5use Socket qw(:all);
6use IO::Socket::INET ();
7use IO::Select       ();
8
9BEGIN {
10    if ( $ENV{SMART_COMMENTS} ) {
11        Class::MOP::load_class('Smart::Comments');
12        Smart::Comments->import;
13    }
14}
15
16has host => (
17    is      => 'ro',
18    isa     => 'Str',
19    default => '127.0.0.1',
20);
21
22has port => (
23    is      => 'ro',
24    isa     => 'Int',
25    default => 1978,
26);
27
28has keepalive => (
29    is      => 'ro',
30    isa     => 'Bool',
31    default => 0,
32);
33
34# fixme add preforking support using Parallel::Prefork
35has fork => (
36    is      => 'ro',
37    isa     => 'Bool',
38    default => 0,
39);
40
41has allowed => (
42    is      => 'rw',
43    isa     => 'HashRef',
44    default => sub { { '127.0.0.1' => '255.255.255.255' } },
45);
46
47has argv => (
48    is      => 'ro',
49    isa     => 'ArrayRef',
50    default => sub { [] },
51);
52
53sub run {
54    my ( $self ) = @_;
55
56    if ($self->keepalive && !$self->fork) {
57        Carp::croak "set fork=1 if you want to work with keepalive!";
58    }
59
60    $self->response_writer->keepalive( $self->keepalive );
61
62    my $host = $self->host;
63    my $port = $self->port;
64
65    # Setup address
66    my $addr = $host ? inet_aton($host) : INADDR_ANY;
67    if ($addr eq INADDR_ANY) {
68        require Sys::Hostname;
69        $host = lc Sys::Hostname::hostname();
70    } else {
71        $host = gethostbyaddr($addr, AF_INET) || inet_ntoa($addr);
72    }
73
74    # Setup socket
75    my $daemon = IO::Socket::INET->new(
76        Listen    => SOMAXCONN,
77        LocalAddr => inet_ntoa($addr),
78        LocalPort => $port,
79        Proto     => 'tcp',
80        ReuseAddr => 1,
81        Type      => SOCK_STREAM,
82    ) or die "Couldn't create daemon: $!";
83
84    my $restart = 0;
85    my $parent = $$;
86    my $pid    = undef;
87    local $SIG{CHLD} = 'IGNORE';
88
89    ### start server
90    while (my ($remote, $peername) = $daemon->accept) {
91        ### accept
92        # TODO (Catalyst): get while ( my $remote = $daemon->accept ) to work
93        delete $self->{_sigpipe};
94
95        next unless my($method, $uri, $protocol) = $self->_parse_request_line($remote);
96        unless (uc $method eq 'RESTART') {
97            # Fork
98            next if $self->fork && ($pid = fork);
99            $self->_handler($remote, $port, $method, $uri, $protocol, $peername);
100            $daemon->close if defined $pid;
101        } else {
102            if ($self->_can_restart($peername)) {
103                $restart = 1;
104                last;
105            }
106        }
107        exit if defined $pid;
108    } continue {
109        close $remote;
110    }
111    $daemon->close;
112
113    if ($restart) {
114        $SIG{CHLD} = 'DEFAULT';
115        wait;
116        exec $^X . ' "' . $0 . '" ' . join(' ', @{ $self->argv });
117    }
118
119    exit;
120}
121
122sub _handler {
123    my($self, $remote, $port, $method, $uri, $protocol, $peername) = @_;
124
125    # Ignore broken pipes as an HTTP server should
126    local $SIG{PIPE} = sub { $self->{_sigpipe} = 1; close $remote };
127
128    # We better be careful and just use 1.0
129    $protocol = '1.0';
130
131    my $peeraddr = $self->_peeraddr($peername);
132
133    my $select = IO::Select->new;
134    $select->add($remote);
135
136    $remote->autoflush(1);
137
138    while (1) {
139        # FIXME refactor an HTTP push parser
140
141        # Parse headers
142        # taken from HTTP::Message, which is unfortunately not really reusable
143        my $headers = do {
144            if ($protocol >= 1) {
145                my @hdr;
146                while ( length(my $line = $self->_get_line($remote)) ) {
147                    if ($line =~ s/^([^\s:]+)[ \t]*: ?(.*)//) {
148                        push(@hdr, $1, $2);
149                    }
150                    elsif (@hdr && $line =~ s/^([ \t].*)//) {
151                        $hdr[-1] .= "\n$1";
152                    } else {
153                        last;
154                    }
155                }
156                HTTP::Headers->new(@hdr);
157            } else {
158                HTTP::Headers->new;
159            }
160        };
161
162        # Pass flow control to HTTP::Engine
163        $self->handle_request(
164            request_args => {
165                uri            => URI::WithBase->new(
166                    do {
167                        my $u = URI->new($uri);
168                        $u->scheme('http');
169                        $u->host($headers->header('Host') || $self->host);
170                        $u->port($self->port);
171                        my $b = $u->clone;
172                        $b->path_query('/');
173                        ($u, $b);
174                    },
175                ),
176                headers        => $headers,
177                _connection => {
178                    input_handle  => $remote,
179                    output_handle => $remote,
180                    env           => {}, # no more env than what we provide
181                },
182                connection_info => {
183                    method         => $method,
184                    address        => $peeraddr,
185                    port           => $port,
186                    protocol       => "HTTP/$protocol",
187                    user           => undef,
188                    https_info     => undef,
189                },
190            },
191        );
192
193        my $connection = lc $headers->header("Connection");
194        ### connection: $connection
195
196        last
197          unless $self->keepalive
198          && index($connection, 'keep-alive') > -1
199          && index($connection, 'te') == -1          # opera stuff
200          && $select->can_read(5);
201
202        ### keep alive
203        last unless ($method, $uri, $protocol) = $self->_parse_request_line($remote, 1);
204    }
205
206    $self->request_builder->_io_read($remote, my $buf, 4096) if $select->can_read(0); # IE hack
207    close $remote;
208}
209
210sub _parse_request_line {
211    my($self, $handle, $is_keepalive) = @_;
212
213    # Parse request line
214    my $line = $self->_get_line($handle);
215    if ($is_keepalive && ($line eq '' || $line eq "\015")) {
216        $line = $self->_get_line($handle);
217    }
218    return ()
219      unless my($method, $uri, $protocol) =
220      $line =~ m/\A(\w+)\s+(\S+)(?:\s+HTTP\/(\d+(?:\.\d+)?))?\z/;
221    return ($method, $uri, $protocol);
222}
223
224sub _peeraddr {
225    my ($self, $peername) = @_;
226
227    my (undef, $iaddr) = sockaddr_in($peername);
228    return inet_ntoa($iaddr) || "127.0.0.1";
229}
230
231sub _get_line {
232    my($self, $handle) = @_;
233
234    # FIXME use bufferred but nonblocking IO? this is a lot of calls =(
235    my $line = '';
236    while ($self->request_builder->_io_read($handle, my $byte, 1)) {
237        last if $byte eq "\012";    # eol
238        $line .= $byte;
239    }
240
241    # strip \r, \n was already stripped
242    $line =~ s/\015$//s;
243
244    $line;
245}
246
247sub _can_restart {
248    my ($self, $peername) = @_;
249
250    my $peeraddr = _inet_addr($self->_peeraddr($peername));
251    my $allowed = $self->allowed;
252    for my $ip (keys %{ $allowed }) {
253        my $mask = $allowed->{$ip};
254        if (($peeraddr & _inet_addr($mask)) == _inet_addr($ip)) {
255            return 1
256        }
257    }
258    return 0;
259}
260
261sub _inet_addr { unpack "N*", inet_aton($_[0]) }
262
2631;
264__END__
265
266=for stopwords Standalone
267
268=head1 NAME
269
270HTTP::Engine::Interface::Standalone - Standalone HTTP Server
271
272=head1 AUTHOR
273
274Kazuhiro Osawa
275
Note: See TracBrowser for help on using the browser.