Ziirish's Home :: Blog

Ziirish's Pub

 
 

Certains ont peut-être déjà entendu parler de la technique dite "du collier de perles". Eh bien ce n'est pas du tout de cela que l'on va parler.

P.S.: je ne peux être tenu pour responsable des résultat de la recherche que vous pourriez effectuer suite à cette introduction.

Je vous ai déjà parlé à plusieurs reprises de ce petit serveur web venu du froid qu'est nginx. Et je vous avais rapidement présenté une "technique" permettant d'exécuter vos plus jolis scripts PHP.

Mais il s'avère que certaines personnes sont encore adeptes des bonnes vieilles CGI Perl.

Mais voilà, nginx n'est là que pour servir du contenu statique (ie. texte brut, css, images, etc.). Les contenus "dynamiques", également appelés CGI doivent être interprétés en dehors de nginx, et ce dernier se contentera de servir le "résultat".

FCGI présenté plus tôt lui ne fait qu'interpréter du PHP. Nous sommes donc ici dans une impasse... À moins que... nous n'implémentions un interpréteur de CGI de notre choix capable de dialoguer avec nginx.

Comme bien souvent avec les produits Open Source, on trouve de bonnes infos sur les sites officiels des projets. Pour ce qui suit, je me suis copieusement inspiré du wiki de nginx.

Le code de notre interpréteur de CGI est donc le suivant :


#!/usr/bin/perl
#
#       author          Daniel Dominik Rudnicki
#       thanks to:      Piotr Romanczuk
#       email           daniel@sardzent.org
#       version         0.4.3
#       webpage         http://www.nginx.eu/
#
#       BASED @ http://wiki.codemongers.com/NginxSimpleCGI
#
#
# use strict;
use FCGI;
use Getopt::Long;
use IO::All;
use Socket;

sub init {
        GetOptions(     "h"     => $help,
                        "verbose!"=>$verbose,
                        "pid=s" => $filepid,
                        "l=s" => $logfile,
                        "S:s"   => $unixsocket,
                        "P:i"   => $unixport) or usage();
                usage() if $help;  

        print " Starting Nginx-fcgi\n" if $verbose;
        print " Running with $> UID" if $verbose;
        print " Perl $]" if $verbose;

        if ( $> == "0" ) {
                print "\n\tERRORtRunning as a root!\n";
                print "\tSuggested not to do so !!!\n\n";
                exit 1;
        }

        if ( ! $logfile ) {
                print "\n\tERRORt log file must declared\n"
                        . "\tuse $0 with option -l filename\n\n";
                exit 1;
        }
        print " Using log file $logfile\n" if $verbose;
        "nn" >> io($logfile);
        addlog($logfile, "Starting Nginx-cfgi");
        addlog($logfile, "Running with $> UID");
        addlog($logfile, "Perl $]");
        addlog($logfile, "Testing socket options");

        if ( ($unixsocket && $unixport) || (!($unixsocket) && !($unixport)) ) {
                print "\n\tERRORtOnly one option can be used!\n";
                print "\tSuggested (beacuse of speed) is usage UNIX socket -S \n\n";
                exit 1;
        }

        if ($unixsocket) {
                print " Daemon listening at UNIX socket $unixsocket\n" if $versbose;
                addlog($logfile, "Deamon listening at UNIX socket $unixsocket");
        } else {
                print " Daemon listening at TCP/IP socket *:$unixport\n" if $verbose;
                #
                addlog($logfile, "Daemon listening at TCP/IP socket *:$unixport");
        }

        if ( -e $filepid ) {
                print "\n\tERRORt PID file $filepid already exists\n\n";
                addlog($logfile, "Can not use PID file $filepid, already exists.");
                exit 1;
        }

        if ( $unixsocket ) {
                print " Creating UNIX socket\n" if $verbose;
                $socket = FCGI::OpenSocket( $unixsocket, 10 );
                if ( !$socket) {   
                        print " Couldn't create socket\n";
                        addlog($logfile, "Couldn't create socket");
                        exit 1;
                }
                print " Using UNIX socket $unixsocket\n" if $verbose;
        } else {
                print " Creating TCP/IP socket\n" if $verbose;
                $portnumber = ":".$unixport;
                $socket = FCGI::OpenSocket( $portnumber, 10 );
                if ( !$socket ) {  
                        print " Couldn't create socket\n";
                        addlog($logfile, "Couldn't create socket");
                        exit 1;
                }
                print " Using port $unixport\n" if $verbose;
        }
        addlog($logfile, "Socket created");

        if ( ! $filepid ) {
                print "\n\tERRORt PID file must declared\n"
                        . "\tuse $0 with option -pid filename\n\n";
                exit 1;
        }
        print " Using PID file $filepid\n" if $verbose;
        addlog($logfile, "Using PID file $filepid");

        my $pidnumber = $$;
        $pidnumber > io($filepid); 
        print " PID number $$n" if $verbose;
        addlog($logfile, "PID number $pidnumber");

}

sub addzero {
        my ($date) = shift;
        if ($date < 10) {
                return "0$date";   
        }
       return $date;
}

sub logformat {
        my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$iddst) = localtime(time);
        my $datestring;
        $year += 1900;
        $mon++;
        $mon  = addzero($mon);
        $mday = addzero($mday);
        $min  = addzero($min);
        $datestring = "$year-$mon-$mday $hour:$min";
        return($datestring);
}

sub addlog {
        my ($log_file, $log_message) = @_;
        my $curr_time = logformat();
        my $write_message = "[$curr_time]   $log_message";
        $write_message >> io($log_file);
        "n" >> io($log_file);
}

sub printerror {
        my $message = @_;
        print "\n       Nginx FastCGI\tERROR\n"
                . "\t $message\n\n";
        exit 1;
}

sub usage {
        print "\n       Nginx FastCGI \n"
                . "\n\tusage: $0 [-h] -S string -P int\n"
                . "\n\t-htt: this (help) message"
                . "\n\t-S pathtt: path for UNIX socket"
                . "\n\t-P porttt: port number"
                . "\n\t-p filett: path for pid file"
                . "\n\t-l filett: path for logfile"
                . "\n\n\texample: $0 -S /var/run/nginx-perl_cgi.sock -l /var/log/nginx/nginx-cfgi.log -pid /var/run/nginx-fcgi.pid\n\n";
        exit 1;
}


init;
#
END() { } BEGIN() { }
*CORE::GLOBAL::exit = sub { die "fakeexitnrc=".shift()."n"; }; eval q{exit};
if ($@) {
        exit unless $@ =~ /^fakeexit/;
} ;

# fork part
my $pid = fork();

if( $pid == 0 ) {
        &main;
        exit 0;
}

print " Forking worker process with PID $pid\n" if $verbose;
addlog($logfile, "Forking worker process with PID $pid");
print " Update PID file $filepid\n" if $verbose;
addlog($logfile, "Update PID file $filepid");
$pid > io($filepid);
print " Worker process running.\n" if $verbose;
addlog ($logfile, "Parent process $$ is exiting");
exit 0;

sub main {
        $request = FCGI::Request( *STDIN, *STDOUT, *STDERR, %req_params, $socket );
        if ($request) { request_loop()};
                FCGI::CloseSocket( $socket );
}

sub request_loop {
        while( $request->Accept() >= 0 ) {
                # processing any STDIN input from WebServer (for CGI-POST actions)
                $stdin_passthrough = '';
                $req_len = 0 + $req_params{'CONTENT_LENGTH'};
                if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ){
                        while ($req_len) {
                                $stdin_passthrough .= getc(STDIN);
                                $req_len--;
                        }
                }

                # running the cgi app
                if ( (-x $req_params{SCRIPT_FILENAME}) &&
                        (-s $req_params{SCRIPT_FILENAME}) &&
                        (-r $req_params{SCRIPT_FILENAME})
                ){
                        foreach $key ( keys %req_params){
                                $ENV{$key} = $req_params{$key};
                        }
                        if ( $verbose ) {
                                addlog($logfile, "running $req_params{SCRIPT_FILENAME}");
                        }
                        # http://perldoc.perl.org/perlipc.html#Safe-Pipe-Opens
                        #
                        open $cgi_app, '-|', $req_params{SCRIPT_FILENAME}, $stdin_passthrough or print("Content-type: text/plain\r\n\r\n"); print "Error: CGI app returned no output - Executing $req_params{SCRIPT_FILENAME} failed !\n"; # addlog($logfile, "Error: CGI app returned no output - Executing $req_params{SCRIPT_FILENAME} failed !");

                        if ($cgi_app) {
                                print <$cgi_app>;
                                close $cgi_app;
                        }
                } else {
                        print("Content-type: text/plain\r\n\r\n");
                        print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.\n";
                        addlog($logfile, "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.");
                }
        }
}

Il faut ensuite lancer le script pour pouvoir interpréter nos CGI. Pour ça j'ai modifié le script init de nginx comme suit :


start_stop_cgi() {
    if [ -f /var/run/nginx-cgi.pid ]
    then
        pid=$(cat /var/run/nginx-cgi.pid)
        ps $pid 2>&1 >/dev/null
        if [ $? -eq 0 ]
        then
            kill $pid
        fi
        rm /var/run/nginx-cgi.pid
    fi
    if [ "$1" == "start" ]
    then
        su -l www-data -c "/opt/tools/cgi-wrapper/nginx-fcgi -S /var/run/nginx-cgi.sock -l /var/log/cgi/nginx-fcgi.log -pid /var/run/nginx-cgi.pid"
    fi
}

puis on remplace les sections start et stop par :


  start)
    echo -n "Starting $DESC: "
        test_nginx_config
    start-stop-daemon --start --quiet --pidfile /var/run/$NAME.pid 
        --exec $DAEMON -- $DAEMON_OPTS || true
    echo "$NAME."
    start_stop_cgi start
    ;;
  stop)
    echo -n "Stopping $DESC: "
    start-stop-daemon --stop --quiet --pidfile /var/run/$NAME.pid 
        --exec $DAEMON || true
    echo "$NAME."
    start_stop_cgi
    ;;

/opt/tools/cgi-wrapper/nginx-fcgi étant l'emplacement du script Perl donné ci-dessus.

Et enfin, dans les fichiers de configurations de nos sites, on ajoute un petit :


    location ~ .pl$ {
        gzip off;
        root             /var/www/nginx-default;
        fastcgi_pass     unix:/var/run/nginx-cgi.sock;
        fastcgi_param    SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param    QUERY_STRING  $query_string;
        fastcgi_param    REQUEST_METHOD  $request_method;
        fastcgi_param    CONTENT_TYPE  $content_type;
        fastcgi_param    CONTENT_LENGTH  $content_length;
        fastcgi_param    GATEWAY_INTERFACE  CGI/1.1;
        fastcgi_param    SERVER_SOFTWARE  nginx;
        fastcgi_param    SCRIPT_NAME  $fastcgi_script_name;
        fastcgi_param    REQUEST_URI  $request_uri;
        fastcgi_param    DOCUMENT_URI  $document_uri;
        fastcgi_param    DOCUMENT_ROOT  $document_root;
        fastcgi_param    SERVER_PROTOCOL  $server_protocol;
        fastcgi_param    REMOTE_ADDR  $remote_addr;
        fastcgi_param    REMOTE_PORT  $remote_port;
        fastcgi_param    SERVER_ADDR  $server_addr;
        fastcgi_param    SERVER_PORT  $server_port;
        fastcgi_param    SERVER_NAME  $server_name;
    }   

Maintenant on peut s'amuser à recoder son blog en Perl, ou bien exécuter awstats si on veut un truc plus conventionnel...

Et sinon, c'est quand même un peu stable :


% ps -o etime,cmd `cat /var/run/nginx-cgi.pid`
    ELAPSED CMD
148-11:43:14 /usr/bin/perl /opt/tools/cgi-wrapper/nginx-fcgi -S /var/run/nginx-cgi.sock -l /var/log/cgi/nginx-fcgi.log -pid /var/run/nginx-cgi.pid