3 # based on http://www.perlmonks.org/?node_id=582781 by Justin Hawkins
4 # and http://poe.perl.org/?POE_Cookbook/Web_Server_With_Forking
8 freeside-selfservice-xmlrpcd
15 use constant DEBUG => 1; # Enable much runtime information.
16 use constant MAX_PROCESSES => 10; # Total server process count.
17 use constant SERVER_PORT => 8080; # Server port.
18 use constant TESTING_CHURN => 0; # Randomly test process respawning.
20 use POE 1.2; # Base features.
21 use POE::Filter::HTTPD; # For serving HTTP content.
22 use POE::Wheel::ReadWrite; # For socket I/O.
23 use POE::Wheel::SocketFactory; # For serving socket connections.
25 use XMLRPC::Transport::HTTP; #SOAP::Transport::HTTP;
26 use XMLRPC::Lite; # for XMLRPC::Serializer
28 use FS::Daemon qw( daemonize1 drop_root logfile daemonize2 );
29 use FS::UID qw( adminsuidsetup forksuidsetup dbh );
31 use FS::ClientAPI qw( load_clientapi_modules );
32 use FS::ClientAPI_XMLRPC; #FS::SelfService::XMLRPC;
36 my $FREESIDE_LOG = "%%%FREESIDE_LOG%%%";
37 my $FREESIDE_LOCK = "%%%FREESIDE_LOCK%%%";
38 my $lock_file = "$FREESIDE_LOCK/selfservice-xmlrpcd.writelock";
42 #not utf-8 safe# base64 => [10, sub {$_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/}, 'as_base64'],
43 dateTime => [35, sub {$_[0] =~ /^\d{8}T\d\d:\d\d:\d\d$/}, 'as_dateTime'],
44 string => [40, sub {1}, 'as_string'],
51 my $user = shift or die &usage;
53 $FS::Daemon::NOSIG = 1;
54 $FS::Daemon::PID_NEWSTYLE = 1;
55 daemonize1('selfservice-xmlrpcd');
57 POE::Kernel->has_forked(); #daemonize forks...
61 adminsuidsetup($user);
63 load_clientapi_modules;
65 logfile("$FREESIDE_LOG/selfservice-xmlrpcd.log");
69 FS::ClientAPI::Signup::clear_cache();
71 my $conf = new FS::Conf;
73 die "not running; selfservice-xmlrpc conf option is off\n"
74 unless $conf->exists('selfservice-xmlrpc');
76 #parent doesn't need to hold a DB connection open
84 server_spawn(MAX_PROCESSES);
92 ### Spawn the main server. This will run as the parent process.
95 my ($max_processes) = @_;
99 _start => \&server_start,
100 _stop => \&server_stop,
101 do_fork => \&server_do_fork,
102 got_error => \&server_got_error,
103 got_sig_int => \&server_got_sig_int,
104 got_sig_child => \&server_got_sig_child,
105 got_connection => \&server_got_connection,
106 _child => sub { undef },
108 heap => { max_processes => MAX_PROCESSES },
112 ### The main server session has started. Set up the server socket and
113 ### bookkeeping information, then fork the initial child processes.
116 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
118 $heap->{server} = POE::Wheel::SocketFactory->new
119 ( BindPort => SERVER_PORT,
120 SuccessEvent => "got_connection",
121 FailureEvent => "got_error",
125 $kernel->sig( INT => "got_sig_int" );
126 $kernel->sig( TERM => "got_sig_int" ); #huh
128 $heap->{children} = {};
129 $heap->{is_a_child} = 0;
131 warn "Server $$ has begun listening on port ", SERVER_PORT, "\n";
133 $kernel->yield("do_fork");
136 ### The server session has shut down. If this process has any
137 ### children, signal them to shutdown too.
141 DEBUG and warn "Server $$ stopped.\n";
143 if ( my @children = keys %{ $heap->{children} } ) {
144 DEBUG and warn "Server $$ is signaling children to stop.\n";
145 kill INT => @children;
149 ### The server session has encountered an error. Shut it down.
151 sub server_got_error {
152 my ( $heap, $syscall, $errno, $error ) = @_[ HEAP, ARG0 .. ARG2 ];
153 warn( "Server $$ got $syscall error $errno: $error\n",
154 "Server $$ is shutting down.\n",
156 delete $heap->{server};
159 ### The server has a need to fork off more children. Only honor that
160 ### request form the parent, otherwise we would surely "forkbomb".
161 ### Fork off as many child processes as we need.
164 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
166 return if $heap->{is_a_child};
168 #my $current_children = keys %{ $heap->{children} };
169 #for ( $current_children + 2 .. $heap->{max_processes} ) {
170 while (scalar(keys %{$heap->{children}}) < $heap->{max_processes}) {
172 DEBUG and warn "Server $$ is attempting to fork.\n";
176 unless ( defined($pid) ) {
178 warn( "Server $$ fork failed: $!\n",
179 "Server $$ will retry fork shortly.\n",
181 $kernel->delay( do_fork => 1 );
185 # Parent. Add the child process to its list.
187 $heap->{children}->{$pid} = 1;
188 $kernel->sig_child($pid, "got_sig_child");
192 # Child. Clear the child process list.
193 $kernel->has_forked();
194 DEBUG and warn "Server $$ forked successfully.\n";
195 $heap->{is_a_child} = 1;
196 $heap->{children} = {};
198 #freeside db connection, etc.
199 forksuidsetup($user);
201 #why isn't this needed ala freeside-selfservice-server??
202 #FS::TicketSystem->init();
208 ### The server session received SIGINT. Don't handle the signal,
209 ### which in turn will trigger the process to exit gracefully.
211 sub server_got_sig_int {
212 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
213 DEBUG and warn "Server $$ received SIGINT/TERM.\n";
215 if ( my @children = keys %{ $heap->{children} } ) {
216 DEBUG and warn "Server $$ is signaling children to stop.\n";
217 kill INT => @children;
220 delete $heap->{server};
221 $kernel->sig_handled();
224 ### The server session received a SIGCHLD, indicating that some child
225 ### server has gone away. Remove the child's process ID from our
226 ### list, and trigger more fork() calls to spawn new children.
228 sub server_got_sig_child {
229 my ( $kernel, $heap, $child_pid ) = @_[ KERNEL, HEAP, ARG1 ];
231 return unless delete $heap->{children}->{$child_pid};
233 DEBUG and warn "Server $$ reaped child $child_pid.\n";
234 $kernel->yield("do_fork") if exists $_[HEAP]->{server};
237 ### The server session received a connection request. Spawn off a
238 ### client handler session to parse the request and respond to it.
240 sub server_got_connection {
241 my ( $heap, $socket, $peer_addr, $peer_port ) = @_[ HEAP, ARG0, ARG1, ARG2 ];
243 DEBUG and warn "Server $$ received a connection.\n";
245 POE::Session->create(
247 _start => \&client_start,
248 _stop => \&client_stop,
249 got_request => \&client_got_request,
250 got_flush => \&client_flushed_request,
251 got_error => \&client_got_error,
252 _parent => sub { 0 },
256 peer_addr => $peer_addr,
257 peer_port => $peer_port,
261 # Gracefully exit if testing process churn.
262 delete $heap->{server}
263 if TESTING_CHURN and $heap->{is_a_child} and ( rand() < 0.1 );
266 ### The client handler has started. Wrap its socket in a ReadWrite
267 ### wheel to begin interacting with it.
272 $heap->{client} = POE::Wheel::ReadWrite->new
273 ( Handle => $heap->{socket},
274 Filter => POE::Filter::HTTPD->new(),
275 InputEvent => "got_request",
276 ErrorEvent => "got_error",
277 FlushedEvent => "got_flush",
280 DEBUG and warn "Client handler $$/", $_[SESSION]->ID, " started.\n";
283 ### The client handler has stopped. Log that fact.
286 DEBUG and warn "Client handler $$/", $_[SESSION]->ID, " stopped.\n";
289 ### The client handler has received a request. If it's an
290 ### HTTP::Response object, it means some error has occurred while
291 ### parsing the request. Send that back and return immediately.
292 ### Otherwise parse and process the request, generating and sending an
293 ### HTTP::Response object in response.
295 sub client_got_request {
296 my ( $heap, $request ) = @_[ HEAP, ARG0 ];
298 forksuidsetup($user) unless dbh && dbh->ping;
300 my $serializer = new XMLRPC::Serializer(typelookup => \%typelookup);
302 #my $soap = SOAP::Transport::HTTP::Server
303 my $soap = XMLRPC::Transport::HTTP::Server
305 -> dispatch_to('FS::ClientAPI_XMLRPC')
306 -> serializer($serializer);
309 warn "Client handler $$/", $_[SESSION]->ID, " is handling a request.\n";
311 if ( $request->isa("HTTP::Response") ) {
312 $heap->{client}->put($request);
316 $soap->request($request);
319 $FS::UID::dbh->commit() if $FS::UID::dbh; #XXX handle commit error
321 my $response = $soap->response;
323 $heap->{client}->put($response);
326 ### The client handler received an error. Stop the ReadWrite wheel,
327 ### which also closes the socket.
329 sub client_got_error {
330 my ( $heap, $operation, $errnum, $errstr ) = @_[ HEAP, ARG0, ARG1, ARG2 ];
332 warn( "Client handler $$/", $_[SESSION]->ID,
333 " got $operation error $errnum: $errstr\n",
334 "Client handler $$/", $_[SESSION]->ID, " is shutting down.\n"
336 delete $heap->{client};
339 ### The client handler has flushed its response to the socket. We're
340 ### done with the client connection, so stop the ReadWrite wheel.
342 sub client_flushed_request {
345 warn( "Client handler $$/", $_[SESSION]->ID,
346 " flushed its response.\n",
347 "Client handler $$/", $_[SESSION]->ID, " is shutting down.\n"
349 delete $heap->{client};
353 die "Usage:\n\n freeside-selfservice-xmlrpcd user\n";