Suite à la configuration du support PHP sous Nginx, il m’a été nécessaire de pouvoir exécuter les scripts CGI et Perl. Comme avec PHP, la configuration s’articule autour de FastCGI côté Nginx. Par contre côté interpréteur, spawn-fcgi ne peut pas être utilisé. Un interpréteur Perl doit donc être « préchargé » et en attente des requêtes clients par la socket dont il a au préalable créée. Socket au choix : UNIX ou TCP si l’exécution doit être locale ou déportée.

Voici le script de l’interpréteur perl-wrapper.pl :

#!/usr/bin/perl

use FCGI;
use Socket;
use POSIX qw(setsid);

require 'syscall.ph';

&daemonize;

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

&main;

sub daemonize() {
    chdir '/'                 or die "Can't chdir to /: $!";
    defined(my $pid = fork)   or die "Can't fork: $!";
    exit if $pid;
    setsid                    or die "Can't start a new session: $!";
    umask 0;
}

sub main {
        $socket = FCGI::OpenSocket( "/tmp/fcgi-perl.sock", 10 );
        $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket );
        if ($request) { request_loop()};
            FCGI::CloseSocket( $socket );
}

sub request_loop {
        while( $request->Accept() >= 0 ) {

           $stdin_passthrough ='';
           $req_len = 0 + $req_params{'CONTENT_LENGTH'};
           if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ){
                my $bytes_read = 0;
                while ($bytes_read < $req_len) {
                        my $data = '';
                        my $bytes = read(STDIN, $data, ($req_len - $bytes_read));
                        last if ($bytes == 0 || !defined($bytes));
                        $stdin_passthrough .= $data;
                        $bytes_read += $bytes;
                }
            }

            if ( (-x $req_params{SCRIPT_FILENAME}) &&  #can I execute this?
                 (-s $req_params{SCRIPT_FILENAME}) &&  #Is this file empty?
                 (-r $req_params{SCRIPT_FILENAME})     #can I read this file?
            ){
		pipe(CHILD_RD, PARENT_WR);
		my $pid = open(KID_TO_READ, "-|");
		unless(defined($pid)) {
			print("Content-type: text/plain\r\n\r\n");
                        print "Error: CGI app returned no output - \
Executing $req_params{SCRIPT_FILENAME} failed !\n";
			next;
		}
		if ($pid > 0) {
			close(CHILD_RD);
			print PARENT_WR $stdin_passthrough;
			close(PARENT_WR);

			while(my $s = <KID_TO_READ>) { print $s; }
			close KID_TO_READ;
			waitpid($pid, 0);
		} else {
	                foreach $key ( keys %req_params){
        	           $ENV{$key} = $req_params{$key};
                	}
        	        # cd to the script's local directory
	                if ($req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/]+$/) {
                        	chdir $1;
                	}

			close(PARENT_WR);
			close(STDIN);
			#fcntl(CHILD_RD, F_DUPFD, 0);
			syscall(&SYS_dup2, fileno(CHILD_RD), 0);
			#open(STDIN, "<&CHILD_RD");
			exec($req_params{SCRIPT_FILENAME});
			die("exec failed");
		}
            }
            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";
            }
        }
}

Ensuite, le script de startup fcgi-perl.sh :

#!/bin/sh
#
# Boris HUISGEN <bhuisgen@hbis.fr>
#

PROVIDES=fcgi-perl

WRAPPER=/usr/local/etc/nginx/perl-wrapper.pl
SOCKET=/tmp/fcgi-perl.sock
SOCKET_USER=www
SOCKET_GROUP=www
SOCKET_PERMS=600

# script

cmd=$1

pcgi_start(){
 echo "Starting $PROVIDES..."
 perl -I /usr/local/lib/perl5/site_perl/5.8.9/mach/sys/ $WRAPPER &

 until [ -S $SOCKET ]
 do
 sleep 1
 done

 chown $SOCKET_USER:$SOCKET_GROUP $SOCKET
 chmod $SOCKET_PERMS $SOCKET
}

pcgi_stop(){
 echo "Stopping $PROVIDES..."
 pkill -f $WRAPPER
 rm $SOCKET
}

pcgi_restart(){
 pcgi_stop
 pcgi_start
}

pcgi_status(){
 [ -S $SOCKET ] && echo "$PROVIDES running" || echo "$PROVIDES NOT running"
}

pcgi_help(){
 echo "Usage: $0 {start|stop|restart|status}"
}

case ${cmd} in
[Ss][Tt][Aa][Rr][Tt]) pcgi_start;;
[Ss][Tt][Oo][Pp]) pcgi_stop;;
[Rr][Ee][Ss][Tt][Aa][Rr][Tt]) pcgi_restart;;
[Ss][Tt][Aa][Tt][Uu][Ss]) pcgi_status 0;;
*)      pcgi_help ;;
esac

Le support CGI/Perl au niveau de Nginx est calqué sur celui de PHP : un mapping d’extension + un nom de socket :

server {
   listen  80;
   server_name     cgi.site.fr;</pre>
location / {
      root /home/www/site.fr/cgi-bin;
      index index.cgi;
   }

   location ~ \.cgi$ {
      fastcgi_pass   unix:/tmp/fcgi-perl.sock;
      fastcgi_index  index.cgi;
      fastcgi_param  HTTP_ACCEPT_ENCODING gzip,deflate;
      fastcgi_param  SCRIPT_FILENAME  /home/www/site.fr/cgi-bin/$fastcgi_script_name;
      include        fastcgi_params;
   }
}

Pour détecter tout problème d’exécution, lancez le wrapper Perl en console. Vérifiez que le module Perl perl-sys-syscall (devel/p5-Sys-Syscall) est installé. Si le wrapper ne parvient pas à trouver le fichier syscall.ph, il faut le générer :

# cd /usr/include
# h2ph * sys/*

Le fichier va être ajouté dans un nouveau répertoire, pour ma part /usr/local/lib/perl5/site_perl/5.8.9/mach/sys/, que j’ajoute en option à Perl dans le script de startup. Ces modifications seront à effectuer en concordance avec votre installation Perl.