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

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

Interface::(?:POE|Standalone): bug fixed around $req->base.

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