Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

Danny

by Danny (Chaplain)
on Nov 03, 2014 at 19:05 UTC ( [id://1105934]=user: print w/replies, xml ) Need Help??

Below is a method I use to implement a fast web server using Perl with Nginx on a Linux system. I have ran three servers for several years with this and haven't had any issues. I'm posting it here for three reasons: 1) It may be of use to someone. 2) Someone will probably tell me a better way to do it. 3) It will help me remember how to do it if I need to reproduce it.

The perl s‎crip‎t below is the daemon that runs via systemd. It is started by the systemd service file (/usr/lib/systemd/system/myserver.service) that looks something like:

[Unit] Des‎crip‎tion=Myserver CGI Server After=nss-user-lookup.target network.target nss-lookup.target sockets. +target # possibly mount services Requires=myserver.socket [Service] Type=simple RuntimeDirectory=www-data RuntimeDirectoryPreserve=yes User=www-data Group=www-data ExecStart=/var/www/some/more/paths/Myserver.fcgi # Or wherever you wan +t to put it StandardInput=socket [Install] WantedBy=multi-user.target Also=myserver.socket

where /var/www/some/more/paths/Myserver.fcgi is the Perl s‎crip‎t below. "myserver" etc should be changed to whatever you want to call things. This service is run automatically by linking via /etc/systemd/system/multi-user.target.wants/. The myserver.socket service file looks something like:

[Unit] Des‎crip‎tion=Myserver Socket [Socket] SocketUser=www-data SocketGroup=www-data ListenStream=/run/www-data/Myserver.fcgi.socket [Install] WantedBy=sockets.target

On the Nginx side, the relevant parts of the main server config look like:

upstream myserver { server unix:/run/www-data/Myserver.fcgi.socket; keepalive 2; } server { # ... root /var/www/some_place; include /etc/nginx/fastcgi_params; location ~ ^/some_uniq_identifier/?$ { include myserver.conf; } # ... }

where myserver.conf looks like:

gzip off; fastcgi_pass myserver; # defined above with "upstream myserver" fastcgi_keep_conn on;
Perl s‎crip‎t, e.g. /var/www/some/more/paths/Myserver.fcgi
#!/usr/bin/perl -T

# This version uses FCGI and forks.  The children use CGI to process stdin.

use warnings;
use strict;
use POSIX ":sys_wait_h";
use FCGI;
use CGI ();
use DBI;
use lib ( $ENV{SOME_PATH} );
use Myserver;

delete $ENV{PATH};
my $DEBUG = 0;
my %FORKS;
my ($SOCKET, $PID_FILE, $debug_fh);
if($DEBUG) {
  $SOCKET = "/tmp/Myserver.debug.socket";
  $PID_FILE = "/tmp/Myserver.debug.pid";
  my $debug_log = "/tmp/Myserver.debug.log";
  open $debug_fh, ">", $debug_log or die "error: can't open file=$debug_log\n";
} else {
  $SOCKET = "/var/run/www-data/Myserver.fcgi.socket";
  $PID_FILE = "/var/run/www-data/Myserver.pid";
}
my $LISTEN_QUEUE = 50;
$SIG{CHLD} = 'IGNORE';
my %opts = ( note => "just an example" );
my $s = Myserver->new( \%opts );
init_Myserver( $s ); # If needed: set up database connections, constants etc
$s->writePidFile( $PID_FILE ); # Optional: If you want to keep a file with the daemon process id.

my $socket = FCGI::OpenSocket($SOCKET, $LISTEN_QUEUE);
my $req = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

my $count = 0;
while ($req->Accept() >= 0) {
  $count++;
  $DEBUG and printf $debug_fh "\n%s FCGI accepted request count=$count ppid=$$\n", scalar localtime;
  my $pid = fork;
  if( not defined $pid ) {
    $s->{CGI} = CGI->new;
    $s->error("Child failed to fork in Myserver.fcgi.  Please email me\@somewhere.com if you get this error.\n");
    next;
  }
  if($pid) {
    $req->Detach; # So next Accept doesn't disconnect current request that fork is handling
    $DEBUG and printf $debug_fh "%s Spawned child cpid=$pid\n", scalar localtime;
    $DEBUG and printf $debug_fh "%s Detached from request.  Waiting for next request.\n", scalar localtime;
    $DEBUG and $debug_fh->flush;
    checkForks();
    $FORKS{$pid} = time;
    next; # wait for next request
  }
  $s->{CGI} = CGI->new;
  $s->check_dbh or child_exit($req);
  $s->{CALL_COUNT} = $count; # Optional: This is for debugging. You can insert an attribute in your HTML to show the call count or do whatever you want with it.
  $s->init_Myserver_per_request; # Optional: Per request as opposed to the one time init_Myserver( $s ) above.
  $s->ProcessRequest(); # Do the work on the request.
  child_exit($req);
}

sub child_exit {
  my $req = shift;
  $req->Flush;
  $req->Finish;
  exit; # kill the child
}

# This is for cleaning up forks that may accumulate because of network issues,
# etc. Some debug statements can be added to check if this may be happening.
sub checkForks {
  # Kill forks older than a minute old
  my $t = time;
  foreach my $pid ( keys %FORKS ) {
    if(waitpid($pid, WNOHANG) != 0) {
      delete $FORKS{$pid};
      next;
    }
    $t - $FORKS{$pid} > 60 or next;
    # printf $old_child_fh "%s Child lived more than a minute; might want to investigate.\n", scalar localtime;
    kill 'KILL', $pid;
    delete $FORKS{$pid};
  }
}

# This is called when daemon is started, but not when URL requests are being
# made, so it's OK to die here if we can't connect to database.
sub init_Myserver { # one time init
  my $s = shift;
  $s->{VAR} = "some important variable";
  $s->{DBH} = $s->DBIconnect(); # If needed
  # etc
}

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (3)
As of 2025-06-16 21:50 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuliAnonymous Monks are no longer allowed to use Super Search, due to an excessive use of this resource by robots.