Nginx : exécution des scripts Perl par FastCGI

Boris HUISGEN
Boris HUISGEN
|

Suite à la configuration de PHP pour Nginx, cet article présente la configuration du support Perl.

A l’instar de PHP, le module FastCGI de Nginx va être utilisé pour externaliser l’appel vers l’interpréteur Perl. Toutefois spawn-fcgi ne peut être utilisé avec Perl ; un interpréteur Perl autonome va être exécuté en démon et sera en attente des requêtes FastCGI par une socket UNIX (ou TCP si l’exécution est déportée sur un autre host).

Le script de l’interpréteur Perl dédié à FastCGI :

# vim /usr/local/etc/nginx/perl-wrapper.pl
 1#!/usr/bin/perl
 2use FCGI;
 3use Socket;
 4use POSIX qw(setsid);
 5
 6require 'syscall.ph';
 7
 8&daemonize;
 9
10END() { } BEGIN() { }
11*CORE::GLOBAL::exit = sub { die "fakeexit\nrc=".shift()."\n"; };
12eval q{exit};
13if ($@) {
14  exit unless $@ =~ /^fakeexit/;
15};
16
17&main;
18
19sub daemonize() {
20  chdir '/'                 or die "Can't chdir to /: $!";
21  defined(my $pid = fork)   or die "Can't fork: $!";
22  exit if $pid;
23  setsid                    or die "Can't start a new session: $!";
24  umask 0;
25}
26
27sub main {
28  $socket = FCGI::OpenSocket( "/tmp/fcgi-perl.sock", 10 );
29  $request = FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket );
30  if ($request) { request_loop()};
31  FCGI::CloseSocket( $socket );
32}
33
34sub request_loop {
35  while( $request->Accept() >= 0 ) {
36    $stdin_passthrough ='';
37    $req_len = 0 + $req_params{'CONTENT_LENGTH'};
38    if (($req_params{'REQUEST_METHOD'} eq 'POST') && ($req_len != 0) ) {
39      my $bytes_read = 0;
40      while ($bytes_read < $req_len) {
41        my $data = '';
42        my $bytes = read(STDIN, $data, ($req_len - $bytes_read));
43        last if ($bytes == 0 || !defined($bytes));
44        $stdin_passthrough .= $data;
45        $bytes_read += $bytes;
46      }
47    }
48
49    if ((-x $req_params{SCRIPT_FILENAME}) &&
50        (-s $req_params{SCRIPT_FILENAME}) &&
51        (-r $req_params{SCRIPT_FILENAME})) {
52      pipe(CHILD_RD, PARENT_WR);
53      my $pid = open(KID_TO_READ, "-|");
54      unless(defined($pid)) {
55        print("Content-type: text/plain\r\n\r\n");
56        print "Error: CGI app returned no output - Executing $req_params{SCRIPT_FILENAME} failed !\n";
57        next;
58      }
59
60      if ($pid > 0) {
61        close(CHILD_RD);
62        print PARENT_WR $stdin_passthrough;
63        close(PARENT_WR);
64
65        while(my $s = <KID_TO_READ>) { print $s; }
66        close KID_TO_READ;
67        waitpid($pid, 0);
68      } else {
69        foreach $key ( keys %req_params) {
70          $ENV{$key} = $req_params{$key};
71        }
72
73        if ($req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/]+$/) {
74          chdir $1;
75        }
76
77        cose(PARENT_WR);
78        close(STDIN);
79        #fcntl(CHILD_RD, F_DUPFD, 0);
80        syscall(&SYS_dup2, fileno(CHILD_RD), 0);
81        #open(STDIN, "<&CHILD_RD");
82        exec($req_params{SCRIPT_FILENAME});
83        die("exec failed");
84      }
85    } else {
86      print("Content-type: text/plain\r\n\r\n");
87      print "Error: No such CGI app - $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.\n";
88    }
89  }
90}

Le script du service init.d associé est le suivant :

# vim /etc/init.d/fcgi-perl
 1#!/bin/bash
 2
 3#
 4# Boris HUISGEN <bhuisgen@hbis.fr>
 5#
 6
 7PROVIDES=fcgi-perl
 8
 9WRAPPER=/usr/local/etc/nginx/perl-wrapper.pl
10SOCKET=/tmp/fcgi-perl.sock
11SOCKET_USER=www
12SOCKET_GROUP=www
13SOCKET_PERMS=600
14
15# script
16
17cmd=$1
18
19pcgi_start(){
20echo "Starting $PROVIDES..."
21perl -I /usr/local/lib/perl5/site_perl/5.8.9/mach/sys/ $WRAPPER &
22
23until [ -S $SOCKET ]
24do
25sleep 1
26done
27
28chown $SOCKET_USER:$SOCKET_GROUP $SOCKET
29chmod $SOCKET_PERMS $SOCKET
30}
31
32pcgi_stop(){
33echo "Stopping $PROVIDES..."
34pkill -f $WRAPPER
35rm $SOCKET
36}
37
38pcgi_restart(){
39pcgi_stop
40pcgi_start
41}
42
43pcgi_status(){
44[ -S $SOCKET ] && echo "$PROVIDES running" || echo "$PROVIDES NOT running"
45}
46
47pcgi_help(){
48echo "Usage: $0 {start|stop|restart|status}"
49}
50
51case ${cmd} in
52  [Ss][tt][Aa][rr][Tt]) pcgi_start;;
53  [Ss][tt][Oo][pp]) pcgi_stop;;
54  [Rr][ee][Ss][tt][Aa][rr][Tt]) pcgi_restart;;
55  [Ss][tt][Aa][tt][Uu][ss]) pcgi_status 0;;
56  \*) pcgi_help ;;
57esac

La configuration du module FastCGI de Nginx est inchangée :

# vim /etc/nginx/fastcgi_params
 1fastcgi_param QUERY_STRING $query_string;
 2fastcgi_param REQUEST_METHOD $request_method;
 3fastcgi_param CONTENT_TYPE $content_type;
 4fastcgi_param CONTENT_LENGTH $content_length;
 5
 6fastcgi_param SCRIPT_NAME $fastcgi_script_name;
 7fastcgi_param REQUEST_URI $request_uri;
 8fastcgi_param DOCUMENT_URI $document_uri;
 9fastcgi_param DOCUMENT_ROOT $document_root;
10fastcgi_param SERVER_PROTOCOL $server_protocol;
11
12fastcgi_param GATEWAY_INTERFACE CGI/1.1;
13fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
14
15fastcgi_param REMOTE_ADDR $remote_addr;
16fastcgi_param REMOTE_PORT $remote_port;
17fastcgi_param SERVER_ADDR $server_addr;
18fastcgi_param SERVER_PORT $server_port;
19fastcgi_param SERVER_NAME $server_name;
20
21# PHP only, required if PHP was built with --enable-force-cgi-redirect
22fastcgi_param REDIRECT_STATUS 200;

Enfin, en ce qui concerne l’hôte virtuel Nginx :

# vim /etc/nginx/sites-available/site.fr.conf
server {
  listen 80;
  server_name cgi.site.fr;

  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 et vérifiez que le module perl-sys-syscall (devel/p5-Sys-Syscall) est installé. Si le wrapper ne parvient pas à trouver le fichier syscall.ph, il faut simplement le générer grâce à ces commandes :

# cd /usr/include
# h2ph _ sys/_

Le fichier va être généré dans un nouveau répertoire - pour ma part _/usr/local/lib/perl5/siteperl/5.8.9/mach/sys/ - que j’ajoute en option dans le script de démarrage du wrapper Perl. Ces modifications seront à effectuer en concordance avec votre installation.

Boris HUISGEN
Boris HUISGEN
Blog owner
  • #fastcgi
  • #nginx
  • #perl