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