nginx用fastcgi方式运行perl脚本(转)

 

一、制作perl的fast cgi 接口文件

先在/bin目录下放置一个perl写的分发器,取名叫perl-fcgi

cat >>perl-fcgi<<'EOF'
 
#!/usr/bin/perl -w
use FCGI;
use Socket;
use FCGI::ProcManager;
sub shutdown { FCGI::CloseSocket($socket); exit; }
sub restart  { FCGI::CloseSocket($socket); &main; }
use sigtrap 'handler', \&shutdown, 'normal-signals';
use sigtrap 'handler', \&restart,  'HUP';
require 'syscall.ph';
use POSIX qw(setsid);
 
#&daemonize; we don't daemonize when running under runsv
#this keeps the program alive or something after exec'ing perl scripts
END()   { }
BEGIN() { }
{
    no warnings;
    *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( "127.0.0.1:10081", 10 ); #use IP sockets
    #$socket = FCGI::OpenSocket( "/var/run/nginx/perl_cgi-dispatch.sock", 10 ); 
    #use UNIX sockets - user running this script must have w access to the 'nginx' folder!!
    #foreach $item (keys %ENV) { delete $ENV{$item}; }
    $proc_manager = FCGI::ProcManager->new( {n_processes => 5} );
    #$socket = FCGI::OpenSocket( "/opt/nginx/fcgi/cgi.sock", 10 )
        ; #use UNIX sockets - user running this script must have w access to the 'nginx' folder!!
    $request =
        FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket,
        &FCGI::FAIL_ACCEPT_ON_INTR );
    $proc_manager->pm_manage();
    if ($request) { request_loop() }
    FCGI::CloseSocket($socket);
}
 
sub request_loop {
    while ( $request->Accept() >= 0 ) {
        $proc_manager->pm_pre_dispatch();
 
        #processing any STDIN input from WebServer (for CGI-POST actions)
        $stdin_passthrough = '';
        { no warnings; $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;
            }
        }
 
        #running the cgi app
        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 );
            pipe( PARENT_ERR, CHILD_ERR );
            my $pid = open( CHILD_O, "-|" );
            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;
            }
            $oldfh = select(PARENT_ERR);
            $|     = 1;
            select(CHILD_O);
            $| = 1;
            select($oldfh);
            if ( $pid > 0 ) {
                close(CHILD_RD);
                close(CHILD_ERR);
                print PARENT_WR $stdin_passthrough;
                close(PARENT_WR);
                $rin = $rout = $ein = $eout = '';
                vec( $rin, fileno(CHILD_O),    1 ) = 1;
                vec( $rin, fileno(PARENT_ERR), 1 ) = 1;
                $ein    = $rin;
                $nfound = 0;
 
                while ( $nfound =
                    select( $rout = $rin, undef, $ein = $eout, 10 ) )
                {
                    die "$!" unless $nfound != -1;
                    $r1 = vec( $rout, fileno(PARENT_ERR), 1 ) == 1;
                    $r2 = vec( $rout, fileno(CHILD_O),    1 ) == 1;
                    $e1 = vec( $eout, fileno(PARENT_ERR), 1 ) == 1;
                    $e2 = vec( $eout, fileno(CHILD_O),    1 ) == 1;
 
                    if ($r1) {
                        while ( $bytes = read( PARENT_ERR, $errbytes, 4096 ) ) {
                            print STDERR $errbytes;
                        }
                        if ($!) {
                            $err = $!;
                            die $!;
                            vec( $rin, fileno(PARENT_ERR), 1 ) = 0
                                unless ( $err == EINTR or $err == EAGAIN );
                        }
                    }
                    if ($r2) {
                        while ( $bytes = read( CHILD_O, $s, 4096 ) ) {
                            print $s;
                        }
                        if ( !defined($bytes) ) {
                            $err = $!;
                            die $!;
                            vec( $rin, fileno(CHILD_O), 1 ) = 0
                                unless ( $err == EINTR or $err == EAGAIN );
                        }
                    }
                    last if ( $e1 || $e2 );
                }
                close CHILD_RD;
                close PARENT_ERR;
                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(PARENT_ERR);
                close(STDIN);
                close(STDERR);
 
                #fcntl(CHILD_RD, F_DUPFD, 0);
                syscall( &SYS_dup2, fileno(CHILD_RD),  0 );
                syscall( &SYS_dup2, fileno(CHILD_ERR), 2 );
 
                #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";
        }
    }
}
 
EOF

给这个脚本执行权限

chmod +x  /bin/perl-fcgi

然后试试看能否启动

/bin/perl-fcgi &

如果遇到错误Can’t locate FCGI.pm,那么执行下面的命令

perl -MCPAN -e 'install FCGI'
perl -MCPAN -e 'install FCGI::ProcManager'
cd /usr/include; h2ph *.h */*.h

第一、二条命令是给perl安装FCGI模块,第三条是注册perl能识别的头文件,然后重新执行/bin/perl-fcgi, 如果正常的话,那么执行:

netstat -tunlp

应该可以看到这样的结果

[root@www Ext]# netstat -tunlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name
tcp        0      0 0.0.0.0:110                 0.0.0.0:*                   LISTEN      23923/couriertcpd
tcp        0      0 0.0.0.0:80                  0.0.0.0:*                   LISTEN      21582/nginx
tcp        0      0 0.0.0.0:21                  0.0.0.0:*                   LISTEN      18358/proftpd: (acc
tcp        0      0 0.0.0.0:22                  0.0.0.0:*                   LISTEN      14290/sshd
tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      23746/master
tcp        0      0 127.0.0.1:10080             0.0.0.0:*                   LISTEN      5602/php-cgi
tcp        0      0 127.0.0.1:10081             0.0.0.0:*                   LISTEN      5640/perl
tcp        0      0 0.0.0.0:3306                0.0.0.0:*                   LISTEN      17582/mysqld
[root@www Ext]#

检查无误后用下面的命令正式启动这个分发器

/bin/perl-fcgi > /dev/null 2>&1 &

/dev/null是一个吞噬任何东西的虚设备
0是标准输入,1是标准输出,2是错误信息输出。2>&1是指将错误信息转到标准输出,然后标准输出到/dev/null

上面的方式启动后perl-fcgi是以执行它的用户身份运行的,对于web程序来说这是很不利的。老外用perl写了一个脚本Noah Friedman可以用指定的用户来运行某个程序,源程序在这里,这里也贴出来方便查阅

cat >>/sbin/runas<<'EOF'
 
#!/bin/sh
exec ${PERL-perl} -Swx $0 ${1+"$@"}
#!perl		  [perl will skip all lines in this file before this line]
 
# with --- run program with special properties
 
# Copyright (C) 1995, 2000, 2002 Noah S. Friedman
 
# Author: Noah Friedman <friedman@splode.com>
# Created: 1995-08-14
 
# $Id: with,v 1.12 2004/02/16 22:51:49 friedman Exp $
 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, you can either send email to this
# program's maintainer or write to: The Free Software Foundation,
# Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
 
# Commentary:
 
# TODO: create optional socket streams for stdin or stdout before invoking
# subprocess.
 
# Code:
 
use Getopt::Long;
use POSIX qw(setsid);
use Symbol;
use strict;
 
(my $progname = $0) =~ s|.*/||;
my $bgfn;
my $bgopt = 0;
 
my $opt_cwd;
my $opt_egid;
my $opt_euid;
my $opt_gid;
my $opt_groups;
my @opt_include;
my $opt_name;
my $opt_pgrp;
my $opt_priority;
my $opt_root;
my $opt_uid;
my $opt_umask;
my $opt_foreground = 0;
 
sub err
{
  my $fh = (ref ($_[0]) ? shift : *STDERR{IO});
  print $fh join (": ", $progname, @_), "\n";
  exit (1);
}
 
sub get_includes
{
  unshift @INC, @_;
  push (@INC,
        "$ENV{HOME}/lib/perl",
        "$ENV{HOME}/lib/perl/include");
 
  eval { require "syscall.ph" } if defined $opt_groups;
}
 
sub numberp
{
  defined $_[0] && $_[0] =~ m/^-?\d+$/o;
}
 
sub group2gid
{
  my $g = shift;
  return $g if numberp ($g);
  my $gid = getgrnam ($g);
  return $gid if defined $gid && numberp ($gid);
  err ($g, "no such group");
}
 
sub user2uid
{
  my $u = shift;
  return $u if numberp ($u);
  my $uid = getpwnam ($u);
  return $uid if defined $uid && numberp ($uid);
  err ($u, "no such user");
}
 
sub set_cwd
{
  my $d = shift;
  chdir ($d) || err ("chdir", $d, $!);
}
 
sub set_egid
{
  my $sgid = group2gid (shift);
  my $egid = $) + 0;
 
  $) = $sgid;
  err ($sgid, "cannot set egid", $!) if ($) == $egid && $egid != $sgid);
}
 
sub set_gid
{
  my $sgid = group2gid (shift);
  my $rgid = $( + 0;
  my $egid = $) + 0;
 
  $( = $sgid;
  $) = $sgid;
  err ($sgid, "cannot set rgid", $!) if ($( == $rgid && $rgid != $sgid);
  err ($sgid, "cannot set egid", $!) if ($) == $egid && $egid != $sgid);
}
 
sub big_endian_p
{
  my $x = 1;
  my @y = unpack ("c2", pack ("i", $x));
  return ($y[0] == 1) ? 0 : 1;
}
 
# This function is more complex than it ought to be because perl does not
# export the setgroups function.  It exports the getgroups function by
# making $( and $) return multiple values in the form of a space-separated
# string, but you cannot *set* the group list by assigning those variables.
# There is no portable way to determine what size gid_t is, so we must guess.
sub set_groups
{
  my @glist = sort { $a <=> $b } map { group2gid ($_) } split (/[ ,]/, shift);
 
  my $expected = join (" ", $(+0, reverse @glist);
  my @p = (big_endian_p() ? ("n", "N", "i") : ("v", "V", "i"));
 
  for my $c (@p)
    {
      err ("setgroups", $!)
        if (syscall (&SYS_setgroups, @glist+0, pack ("$c*", @glist)) == -1);
      return if ("$(" eq $expected);
    }
  err ("setgroups", "Could not determine gid_t");
}
 
sub set_pgrp
{
  setpgrp ($$, shift) || err ("setpgrp", $!);
}
 
sub set_priority
{
  my $prio = shift () + 0;
  setpriority (0, 0, $prio) || err ("setpriority", $prio, $!);
}
 
sub set_root
{
  my $d = shift;
  chroot ($d) || err ("chroot", $d, $!);
  chdir ("/");
}
 
sub set_euid
{
  my $suid = user2uid (shift);
  my $euid = $>;
 
  $> = $suid;
  err ($suid, "cannot set euid", $!) if ($> == $euid && $euid != $suid);
}
 
sub set_uid
{
  my $suid = user2uid (shift);
  my $ruid = $<;
  my $euid = $>;
 
  $< = $suid;
  $> = $suid;
  err ($suid, "cannot set ruid", $!) if ($< == $ruid && $ruid != $suid);
  err ($suid, "cannot set euid", $!) if ($> == $euid && $euid != $suid);
}
 

sub background
{
  my $pid = fork;
  die "$@" if $pid < 0;
  if ($pid == 0)
    {
      # Backgrounded programs may expect to be able to read input from the
      # user if stdin is a tty, but we will no longer have any job control
      # management because of the double fork and exit.  This can result in
      # a program either blocking on input (if still associated with a
      # controlling terminal) and stopping, or stealing input from a
      # foreground process (e.g. a shell).  So redirect stdin to /dev/null.
      open (STDIN, "< /dev/null") if (-t STDIN);
      return *STDERR{IO};
    }
 
  exit (0) unless $opt_foreground;
  wait;
  exit ($?);
}
 
sub dosetsid
{
  background ();
  setsid (); # dissociate from controlling terminal
  return *STDERR{IO};
}
 
sub daemon
{
  # Don't allow any file descriptors, including stdin, stdout, or
  # stderr to be propagated to children.
  $^F = -1;
  dosetsid ();
  # Duped in case we've closed stderr but can't exec anything.
  my $saved_stderr = gensym;
  open ($saved_stderr, ">&STDERR");
  close (STDERR);
  close (STDOUT);
  close (STDIN);
  return $saved_stderr;
}
 
sub notty
{
  # Don't allow any file descriptors other than stdin, stdout, or stderr to
  # be propagated to children.
  $^F = 2;
  dosetsid ();
  # Duped in case we've closed stderr but can't exec anything.
  my $saved_stderr = gensym;
  open ($saved_stderr, ">&STDERR");
  open (STDIN,  "+</dev/null");
  open (STDERR, "+<&STDIN");
  open (STDOUT, "+<&STDIN");
  return $saved_stderr;
}
 

sub set_bg_option
{
  my %bgfntbl =
    ( 1 => \&background,
      2 => \&daemon,
      4 => \&notty,
      8 => \&dosetsid,
    );
 
  $bgopt = $_[0];
  $bgfn  = $bgfntbl{$bgopt};
}
 
sub parse_options
{
  Getopt::Long::config (qw(bundling autoabbrev require_order));
  my $succ = GetOptions
    ("h|help",          sub { usage () },
     "c|cwd=s",         \$opt_cwd,
     "d|display=s",     \$ENV{DISPLAY},
     "H|home=s",        \$ENV{HOME},
     "G|egid=s",        \$opt_egid,
     "g|gid=s",         \$opt_gid,
     "I|include=s@",    \@opt_include,
     "l|groups=s",      \$opt_groups,
     "m|umask=s",       \$opt_umask,
     "n|name=s",        \$opt_name,
     "P|priority=i",    \$opt_priority,
     "p|pgrp=i",        \$opt_pgrp,
     "r|root=s",        \$opt_root,
     "U|euid=s",        \$opt_euid,
     "u|uid=s",         \$opt_uid,
 
     "f|fg|foreground", \$opt_foreground,
 
     "b|bg|background", sub { set_bg_option (1); $opt_foreground = 0 },
     "a|daemon|demon",  sub { set_bg_option (2) },
     "N|no-tty|notty",  sub { set_bg_option (4) },
     "s|setsid",        sub { set_bg_option (8) },
    );
  usage () unless $succ;
 
  my $n = 0;
  do { $n++ if $bgopt & 1 } while ($bgopt >>= 1);
  err ("Can only specify one of --background, --daemon, --notty, or --setsid")
    if ($n > 1);
}
 
sub usage
{
  print STDERR "$progname: @_\n\n" if @_;
  print STDERR "Usage: $progname {options} [command {args...}]\n
Options are:
-h, --help            You're looking at it.
-D, --debug           Turn on interactive debugging in perl.
-I, --include   DIR   Include DIR in \@INC path for perl.
                      This option may be specified multiple times to append
                      search paths to perl.
 
-d, --display   DISP  Run with DISP as the X server display.
-H, --home      HOME  Set \$HOME.
-n, --name      ARGV0 Set name of running program (argv[0]).
 
-c, --cwd       DIR   Run with DIR as the current working directory.
                      This directory is relative to the root directory as
                      specified by \`--root', or \`/'.
-r, --root      ROOT  Set root directory (via \`chroot' syscall) to ROOT.
 
-G, --egid      EGID  Set \`effective' group ID.
-g, --gid       GID   Set both \`real' and \`effective' group ID.
-l, --groups    GLIST Set group list to comma-separated GLIST.
-U, --euid      EUID  Set \`effective' user ID.
-u, --uid       UID   Set both \`real' and \`effective' user ID.
 
-m, --umask     UMASK Set umask.
-P, --priority  NICE  Set scheduling priority to NICE (-20 to 20).
-p, --pgrp      PGRP  Set process group.
 
The following options cause the resulting process to be backgrounded
automatically but differ in various ways:
 
-b, --background      Run process in background.  This is the default with
                      the --daemon, --no-tty, and --setsid options.
 
-f, --foreground      Do not put process into the background when using
                      the --daemon, --no-tty, and --setsid options.
                      In all other cases the default is to remain in the
                      foreground.
 
-a, --daemon          Run process in \"daemon\" mode.
                      This closes stdin, stdout, and stderr, dissociates
                      the process from any controlling terminal, and
                      backgrounds the process.
 
-N, --no-tty          Run process in background with no controlling
                      terminal and with stdin, stdout, and stderr
                      redirected to /dev/null.
 
-s, --setsid          Dissociate from controlling terminal.
                      This automatically backgrounds the process but
                      does not redirect any file descriptors.\n";
  exit (1);
}
 
sub main
{
  parse_options ();
  usage () unless @ARGV;
 
  get_includes (@opt_include);
 
  umask        (oct ($opt_umask)) if defined $opt_umask;
  set_gid      ($opt_gid)         if defined $opt_gid;
  set_egid     ($opt_egid)        if defined $opt_egid;
  set_groups   ($opt_groups)      if defined $opt_groups;
  set_root     ($opt_root)        if defined $opt_root;
  set_cwd      ($opt_cwd)         if defined $opt_cwd;
  set_priority ($opt_priority)    if defined $opt_priority;
  set_uid      ($opt_uid)         if defined $opt_uid;
  set_euid     ($opt_euid)        if defined $opt_euid;
 
  my $stderr = $bgfn ? &$bgfn () : *STDERR{IO};
 
  my $runprog = $ARGV[0];
  if ($opt_name)
    {
      shift   @ARGV;
      unshift @ARGV, $opt_name;
    }
  local $^W = 0; # avoid implicit warnings from exec
  exec ($runprog @ARGV) || err ($stderr, "exec", $runprog, $!);
}
 
main ();
 
# local variables:
# mode: perl
# eval: (auto-fill-mode 1)
# end:
 
# with ends here
 
EOF

用www用户启动我们的分发器

chmod +x /sbin/runas
runas --daemon -g www -u www /bin/perl-fcgi

二、配置nginx

现在nginx的配置目录下放置这么一个文件fcgi_perl.conf

cat >>/usr/local/nginx/conf/fcgi_perl.conf<<EOF
 
fastcgi_pass    127.0.0.1:10081;
fastcgi_index   index.cgi;
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;
fastcgi_read_timeout            60;
 
EOF

然后在需要perl功能的虚拟机段做类似这样的配置:
     

cat >>/usr/local/nginx/conf/nginx.conf<<EOF
 
  server {
             listen             80;
             server_name        mail.ashopex.com;
             index              index.cgi
 
             root               /var/www;
 
             location ~  .*\.cgi? {
                  include         fcgi_perl.conf;
             }
     }
 
EOF

Posted by microsea


Trackback URL : 无法向此文章发送引用

« Previous : 1 : 2 : 3 : 4 : 5 : 6 : 7 : ... 351 : Next »

BlogImage

Microsea,Fiona & Owen's family BLOG

- microsea

Statistics Graph

Calendar

«   Mar 2010   »
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

闽ICP备06003076号