nginx支持cgi

nginx支持cgi

想在现有环境LNMP上部署一下Nagios,但是发现默认情况下Nginx自身是无法解析外部CGI程序的,于是官方给出了一个这样的做法http://wiki.nginx.org/NginxSimpleCGI,通过研究发现有两种方式可以实现让nginx支持cgi,两种方式都需要安装那几包.下面分别来介绍:

第一种方式操作步骤:

一、安装perl

perl-FCGI、perl-FCGI-ProcManager、perl-IO-ALL(如下文件均可以去http://search.cpan.org下载)

#wget http://www.cpan.org/modules/by-module/FCGI/FCGI-0.67.tar.gz

#tar zxvf FCGI-0.67.tar.gz

#cd FCGI-0.67

#perl Makefile.PL

#make && make install

#wget http://search.cpan.org/CPAN/authors/id/B/BO/BOBTFISH/FCGI-ProcManager-0.24.tar.gz

#tar zxvf FCGI-ProcManager-0.24.tar.gz

#cd FCGI-ProcManager-0.24

#perl Makefile.PL

#make && make install

#wget http://search.cpan.org/CPAN/authors/id/I/IN/INGY/IO-All-0.39.tar.gz

#tar zxvf IO-All-0.39.tar.gz

#cd IO-All-0.39

#perl Makefile.PL

#make && make install

二、生成perl守护进程

#vi nginx-cgi.pl,然后加入如下内容

#!/usr/bin/perl

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);

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 {

$proc_manager = FCGI::ProcManager->new( {n_processes => 5} );

$socket = FCGI::OpenSocket( "127.0.0.1:8999", 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";

}

}

}

#chmod 777 nginx-cgi.pl

#/usr/local/nginx/sbin/nginx-cgi.pl >/dev/null & //启动FCGI

#echo "/usr/local/nginx/sbin/nginx-cgi.pl >/dev/null &" >>/etc/rc.local

注:FCGI监听地址为127.0.0.1:8999,如想改变可以在nginx-cgi.pl内修改

三、配置Nginx

Nginx配置如下

server {

listen 80;

index index.phpindex.cgi;

server_name blog.carl.com;

root /usr/local/nginx/html;

location ~ \.php$ {

fastcgi_pass unix:/usr/local/nginx/php-cgi.sock;

fastcgi_indexindex.php;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

include fastcgi_params;

}

location ~ \.cgi$ {

root /usr/local/nginx/html;

fastcgi_pass 127.0.0.1:8999;

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

include fastcgi_params;

}

}

四、测试

#vi /usr/local/nginx/html/test.cgi

#!/usr/bin/perl

print "Content-type: text/html\n\n";

print "<html><body>Hello, world.</body></html>";

然后打开浏览器访问http://localhost/test.cgi,如果出现内容为Hello,world.则说明配置成功。

第二种方式:

vi /usr/local/nginx/perl-fcgi.pl

#!/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,

"p => \$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\tERROR\tRunning as a root!\n";

print "\tSuggested not to do so !!!\n\n";

exit 1;

}

if ( ! $logfile ) {

print "\n\tERROR\t log file must declared\n"

. "\tuse $0 with option -l filename\n\n";

exit 1;

}

print " Using log file $logfile\n" if $verbose;

"\n\n" >> 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\tERROR\tOnly 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\tERROR\t 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( $unixport, 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\tERROR\t 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-h\t\t: this (help) message"

. "\n\t-S path\t\t: path for UNIX socket"

. "\n\t-P port\t\t: port number"

. "\n\t-p file\t\t: path for pid file"

. "\n\t-l file\t\t: 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 "fakeexit\nrc=".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.");

}

}

}

nginx的配置

location ~ .*\.pl$ {

fastcgi_pass unix:/usr/local/nginx/logs/perl-fcgi.sock;

fastcgi_index index.pl;

fastcgi_param SCRIPT_FILENAME /usr/local/nagios/sbin$fastcgi_script_name;

include fastcgi.conf;

}

启动脚本为:

Vi /usr/local/nginx/start_perl_cgi.sh

#!/bin/bash

#set -x

dir=/usr/local/nginx

stop ()

{

#pkill -f $dir/perl-fcgi.pl

kill $(cat $dir/logs/perl-fcgi.pid)

rm $dir/logs/perl-fcgi.pid 2>/dev/null

rm $dir/logs/perl-fcgi.sock 2>/dev/null

echo "stop perl-fcgi done"

}

start ()

{

rm $dir/now_start_perl_fcgi.sh 2>/dev/null

chown nobody.root $dir/logs

echo "$dir/perl-fcgi.pl -l $dir/logs/perl-fcgi.log -pid $dir/logs/perl-fcgi.pid -S $dir/logs/perl-fcgi.sock" >>$dir/now_start_perl_fcgi.sh

chown nobody.nobody $dir/now_start_perl_fcgi.sh

chmod u+x $dir/now_start_perl_fcgi.sh

sudo -u nobody $dir/now_start_perl_fcgi.sh

echo "start perl-fcgi done"

}

case $1 in

stop)

stop

;;

start)

start

;;

restart)

stop

start

;;

esac