File Coverage

lib/HTTP/Server/Multiplex.pm
Criterion Covered Total %
statement 54 172 31.4
branch 0 72 0.0
condition 0 19 0.0
subroutine 18 31 58.0
pod 0 9 0.0
total 72 303 23.7


line stmt bran cond sub pod time code
1             # Copyrights 2008 by Mark Overmeer.
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 1.05.
5 1     1   53535 use strict;
  1         2  
  1         45  
6 1     1   5 use warnings;
  1         2  
  1         44  
7              
8             package HTTP::Server::Multiplex;
9 1     1   19 use vars '$VERSION';
  1         2  
  1         53  
10             $VERSION = '0.11';
11              
12              
13 1     1   444 use HTTP::Server::VirtualHost;
  1         3  
  1         30  
14 1     1   403 use HTTP::Server::VirtualHost::LocalHost;
  1         3  
  1         20  
15 1     1   425 use HTTP::Server::Connection;
  1         3  
  1         39  
16              
17 1     1   8 use IO::Multiplex ();
  1         40  
  1         15  
18 1     1   2005 use IO::Socket::INET ();
  1         12587  
  1         33  
19 1     1   1097 use Sys::Hostname qw(hostname);
  1         1368  
  1         155  
20 1     1   7 use POSIX qw(setsid);
  1         2  
  1         12  
21 1     1   68 use English qw(-no_match_vars);
  1         3  
  1         12  
22 1         6 use POSIX qw(setuid setgid sigprocmask
23 1     1   537 SIGINT SIG_BLOCK SIG_UNBLOCK);
  1         3  
24 1     1   127 use Fcntl;
  1         3  
  1         495  
25 1     1   11 use File::Spec ();
  1         1  
  1         19  
26 1     1   6 use Socket qw(inet_aton AF_INET);
  1         2  
  1         64  
27              
28 1     1   6 use Log::Report 'httpd-multiplex', syntax => 'SHORT';
  1         1  
  1         11  
29              
30             ###
31              
32              
33             my $singleton;
34             sub new(@)
35 0     0 0   { my $class = shift;
36 0 0         my $args = @_==1 ? shift @_ : {@_};
37              
38 0 0         error __x"you can only create one {pkg} object per program"
39             if $singleton++; # only one IO::Multiplexer
40              
41 0           (bless {}, $class)->init($args);
42             }
43              
44 0 0   0     sub _to_list($) { ref $_[0] eq 'ARRAY' ? @{$_[0]} : $_[0] }
  0            
45             sub init($)
46 0     0 0   { my ($self, $args) = @_;
47              
48 0           my $mux = $self->{HSM_mux} = IO::Multiplex->new;
49 0           $mux->set_callback_object($self);
50              
51 0           foreach my $conn (_to_list delete $args->{connection})
52 0           { trace "setting up connection";
53 0           $self->_configNetwork($mux, $conn);
54             }
55              
56 0           $self->{HSM_vhosts} = {};
57 0           foreach my $vhost (_to_list delete $args->{vhosts})
58 0           { trace "setting up virtual host";
59 0           $self->addVirtualHost($vhost);
60             }
61              
62 0           trace "setting up daemon";
63 0           $self->_configDaemon(delete $args->{daemon});
64              
65 0 0         error __x"Unknown option for ::Multiplex::new(): {names}"
66             , names => [keys %$args]
67             if keys %$args;
68 0           $self;
69             }
70              
71              
72             sub _configNetwork($$)
73 0     0     { my ($self, $mux, $config) = @_;
74 0           my $socket;
75              
76 0 0         if(UNIVERSAL::isa($config, 'IO::Socket'))
    0          
77 0           { $socket = $config;
78             }
79             elsif(not UNIVERSAL::isa($config, 'HASH'))
80 0           { error __x"connection configuration not a socket not HASH";
81             }
82             else
83 0   0       { my $host = $config->{host} || '0.0.0.0';
84 0   0       my $port = $config->{port} || 80;
85 0           $socket = IO::Socket::INET->new
86             ( LocalAddr => $host
87             , Listen => 5
88             , LocalPort => $port
89             , Reuse => 1 # to be able to restart without loss of service
90             # not yet implemented
91             );
92              
93 0 0         defined $socket
94             or fault __x"unable to create socket for {host} port {port}"
95             , host => $host, port => $port;
96              
97 0           trace 'created server socket '.$socket->sockhost.':'.$port;
98             }
99              
100 0           $mux->listen($socket);
101             }
102              
103              
104             sub _configDaemon($)
105 0     0     { my ($self, $config) = @_;
106 0           my @daemon_headers;
107              
108             my $id;
109 0 0         if(exists $config->{server_id})
110 0           { $id = $config->{server_id};
111             }
112             else
113 1     1   976 { no strict; no warnings;
  1     1   2  
  1         38  
  1         5  
  1         2  
  1         1725  
114 0           $id = hostname . " ".__PACKAGE__." $VERSION, "
115             . "IO::Multiplex $IO::Multiplex::VERSION";
116             }
117 0 0         push @daemon_headers, Server => $id if defined $id;
118 0           HTTP::Server::Connection->setDefaultHeaders(@daemon_headers);
119              
120 0 0 0       $EUID!=0 || defined $config->{user}
121             or error __"running daemon as root is dangerous: specify other user";
122              
123 0   0       my $user = $config->{user} || $ENV{USER} || $EUID;
124 0 0         my $uid = $user =~ m/\D/ ? getpwnam($user) : $user;
125 0 0         defined $uid
126             or error __x"user {name} does not exist", name => $user;
127 0           $self->{HSM_uid} = $uid;
128              
129 0   0       my @groups = split ' ', ($config->{group} || $EGID);
130 0           my @gid;
131 0           foreach my $group (@groups)
132 0 0         { my $gid = $group =~ m/\D/ ? getgrnam($group) : $group;
133 0 0         defined $gid
134             or error __x"group {name} does not exist", name => $group;
135 0           push @gid, $gid;
136             }
137 0           $self->{HSM_gid} = join ' ', @gid;
138              
139 0           $self->{HSM_pidfn} = $config->{pid_file};
140 0           $self;
141             }
142              
143             sub _daemonize()
144 0     0     { my $self = shift;
145              
146 0           my ($uid, $gid) = @$self{'HSM_uid', 'HSM_gid'};
147 0 0         if($uid ne $EUID)
148 0 0         { setuid $uid
149             or fault __x"cannot switch to user-id {uid}", uid => $uid;
150 0           trace "switch to user $uid";
151             }
152 0 0         if($gid ne $EGID)
153 0 0         { setgid $gid
154             or fault __x"cannot switch to group-id {gid}", gid => $gid;
155 0           trace "switch to group $gid";
156             }
157              
158 0 0         $self->{HSM_detach}
159             or return $self;
160              
161 0           my $pidfile = $self->{HSM_pidfn};
162 0 0         if(defined $pidfile)
163 0 0         { sysopen PID, $pidfile, O_EXCL|O_CREAT|O_WRONLY|O_TRUNC
164             or fault __x"cannot write to pid_file {fn}", fn => $pidfile;
165             }
166              
167 0           trace "close standard error dispatcher";
168 0           dispatcher close => 'PERL'; # no die/warn output
169              
170 0           trace "closing standard file-handles";
171 0           open STDIN, '<', File::Spec->devnull;
172 0           open STDOUT, '>', File::Spec->devnull;
173 0           open STDERR, '>', File::Spec->devnull;
174              
175 0           trace "process into the background";
176 0           my $sigset = POSIX::SigSet->new(SIGINT);
177 0 0         sigprocmask SIG_BLOCK, $sigset
178             or fault "cannot block SIGINT for fork";
179              
180 0           my $pid = fork;
181 0 0         defined $pid
182             or fault "cannot fork into background";
183              
184 0 0         sigprocmask SIG_UNBLOCK, $sigset
185             or fault "cannot unblock SIGINT after fork";
186              
187 0 0         if($pid > 0)
188             { # Parent process
189 0 0         if($pidfile)
190 0           { print PID "$pid\n";
191 0 0         close PID or fault "cannot write pid-file {fn}", fn => $pidfile;
192             }
193              
194 0           return $self;
195             }
196              
197             # Child process
198 0 0         close PID if $pidfile;
199              
200 0           setsid;
201              
202 0           $self;
203             }
204              
205             #-------------
206              
207 0     0 0   sub mux() {shift->{HSM_mux}}
208              
209             #-------------
210              
211             sub run()
212 0     0 0   { my $self = shift;
213              
214 0 0         unless(keys %{$self->{HSM_vhosts}})
  0            
215 0           { trace "creating default vhost 'localhost' because no explicit vhost";
216 0           $self->addVirtualHost(HTTP::Server::VirtualHost::LocalHost->new);
217             }
218              
219 0           $self->_daemonize;
220              
221 0           info __x"http daemon start, user {uid} group {gid}"
222             , uid => $EUID, gid => $EGID;
223              
224 0           $self->mux->loop;
225             }
226              
227             #-------------
228              
229             sub addVirtualHost(@)
230 0     0 0   { my $self = shift;
231 0 0         my $config = @_==1 ? shift : {@_};
232 0           my $vhost;
233 0 0 0       if(UNIVERSAL::isa($config, 'HTTP::Server::VirtualHost'))
    0          
    0          
234 0           { $vhost = $config;
235             }
236             elsif(!ref $config && $config =~ m/\:\:/)
237 0           { eval "require $config";
238 0 0         die $@ if $@;
239 0           $vhost = $config->new;
240             }
241             elsif(not UNIVERSAL::isa($config, 'HASH'))
242 0           { error __x"virtual configuration not a valid object not HASH";
243             }
244             else
245 0           { $vhost = HTTP::Server::VirtualHost->new($config);
246             }
247              
248             $self->{HSM_vhosts}{$_} = $vhost
249 0           for $vhost->name, $vhost->aliases;
250 0           $vhost;
251             }
252              
253              
254             sub removeVirtualHost($)
255 0     0 0   { my ($self, $id) = @_;
256 0 0         my $vhost = UNIVERSAL::isa($id, 'HTTP::Server::VirtualHost') ? $id
257             : $self->virtualHost($id);
258 0 0         defined $vhost or return;
259              
260             delete $self->{HSM_vhosts}{$_}
261 0           for $vhost->name, $vhost->aliases;
262 0           $vhost;
263             }
264              
265              
266 0     0 0   sub virtualHost($) { $_[0]->{HSM_vhosts}{$_[1]} }
267              
268             #-------------------
269             #section Multiplexer
270              
271             sub mux_connection($$)
272 0     0 0   { my ($self, $mux, $fh) = @_;
273 0           my $client = HTTP::Server::Connection->new($mux, $fh, $self);
274 0           $mux->set_callback_object($client, $fh);
275             }
276              
277             sub dnslookup($$$)
278 0     0 0   { my ($self, $conn, $ip, $where) = @_;
279 0   0       my $host = $self->{HSM_cache}{$ip} ||=
280             # must be changed into async lookup!
281             gethostbyaddr inet_aton($ip), AF_INET;
282 0           $$where = $host;
283 0           info $conn->id." $ip is $host";
284             }
285              
286             #-------------------
287              
288              
289             1;