在nginx上使用php代理运行cgi程序

我们用到的很多开源程序比如mailman, nagios等等,都有WEB端管理界面。在那个Apache一家独大的年代,这个问题可以很好解决,因为apache本身可以运行cgi程序。但随着 nginx服务器的大规模应用,而恰好nginx又没有cgi模块,所以我们不得不采用一些变通的手段来解决它。

在网上广为流传的解决方法是一个老外写的perl脚本,但这个脚本本身有很多问题,而且需要在后台启动一个守护进程,本人对用perl写的网络服务守护进程的稳定性很怀疑,在看了它的代码后,发现用PHP即可很好的解决这个问题。

CGI其实本质上就是一个普通的二进制程序,你可以在后台直接运行它。而服务器要做的事就是将WEB传递的变量作为参数传递给这个程序并执行,而将执行返回的结果显示到页面上。

明白了这个道理,我们就可以开始着手解决这个问题了。其过程无非就是将PHP作为一个proxy,使其运行指定的程序,并把程序输出结果echo出来。

我们把这个PHP脚本命名为cgi.php,把它随便放到一个你认为合适的位置,然后用rewrite将后缀为cgi的请求都转发到cgi.php上。以下为参考的配置格式

#rewrite cgi请求到cgi.php上,并把cgi文件名作为php的pathinfo
rewrite ^/nagios/cgi-bin/(.*) /cgi.php/$1 break;
 
location /nagios/
{
    gzip off;
    alias /usr/local/nagios/share/;
    index index.html index.htm index.php;
}
 
location ~ .*\.php(\/.*)*$ {
    fastcgi_pass    127.0.0.1:9000;
    fastcgi_index   index.php;
    include fcgi.conf;
    fastcgi_param SCRIPT_FILENAME /usr/local/nagios/share$fastcgi_script_name;
 
    #pathinfo必须设置
    fastcgi_param  PATH_INFO $fastcgi_script_name;
 
    #以下两个为cgi.php需要用到的变量名,分别为cgi程序目录,和cgi默认index程序
    fastcgi_param  CGI_BASE  /usr/local/nagios/sbin;
    fastcgi_param  CGI_INDEX status.cgi;
}

注意上面配置文件的注释部分,在你自己设置的时候必须填上合适的值。下面就是最重要的cgi.php文件了

<?php
/*
   use php to execute mailman cgi app
   hack by 70 (magike.net@gmail.com)
   https://joyqi.com
 */
 
// get cgi base from fastcgi param
$cgi_base = '';
if (isset($_SERVER['CGI_BASE'])) {
    $cgi_base = rtrim($_SERVER['CGI_BASE'], '/') . '/';
} else {
    die('PLEASE CONFIGURE YOUR CGI_BASE PARAM');
}
 
// get pathinfo
$pathinfo = '';
if (isset($_SERVER['PATH_INFO'])) {
    $pathinfo = $_SERVER['PATH_INFO'];
} else if (isset($_SERVER['CGI_INDEX'])) {
    $pathinfo = $_SERVER['CGI_INDEX'];
} else {
    die('PLEASE CONFIGURE YOUR PATH_INFO PARAM');
}
 
// get real cgi path
$cgi_path = $cgi_base;
$cgi_file = trim($pathinfo, '/');
$cgi_file_levels = explode('/', $cgi_file);
$cgi_file_exists = false;
 
while (count($cgi_file_levels) > 0) {
    $cgi_path = $cgi_path . '/' . array_shift($cgi_file_levels);
 
    if (is_file($cgi_path)) {
        $cgi_file_exists = true;
        break;
    }
}
 
if (!$cgi_file_exists) {
    die('NOT EXISTS PAGE!' . $cgi_file);
}
 
$cgi_pathinfo = '';
if (!empty($cgi_file_levels)) {
    $cgi_pathinfo = '/' . implode('/', $cgi_file_levels);
}
 
if (is_readable($cgi_path)) {
 
    $descriptorspec = array(
            0 => array("pipe", "r"),  // stdin is a pipe that the child will read from
            1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
            2 => array("file", "/tmp/error-output.txt", "a") // stderr is a file to write to
            );
 
    $cwd = $cgi_base;
    $env = $_ENV;
 
    $env['SCRIPT_FILENAME'] = $cgi_path;
    $env['SCRIPT_NAME'] = $cgi_file;
    $env['DOCUMENT_ROOT'] = CGI_BASE;
    $env['PATH_INFO'] = $cgi_pathinfo;
 
    // http auth support (nagios etc.)
    if (isset($_SERVER['PHP_AUTH_USER'])) {
        $env['REMOTE_USER'] = $_SERVER['PHP_AUTH_USER'];
    }
 
    $process = proc_open($cgi_path, $descriptorspec, $pipes, $cwd, $env);
    if (is_resource($process)) {
        $stdin = file_get_contents("php://input");
 
        if (!empty($stdin)) {
            fwrite($pipes[0], $stdin);
            fclose($pipes[0]);
        }
 
        //stream_set_blocking($pipes[1], 0);
        stream_set_timeout($pipes[1], 3);
        $result = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        $return_value = proc_close($process);
 
        list($header, $body) = preg_split("/\r?\n\r?\n/", $result, 2);
 
        $headers = explode("\n", $header);
        foreach ($headers as $line) {
            header(trim($line));
        }
 
        echo $body;
    } else {
        die('ERROR APPLICATION!');
    }
 
} else {
    die('ERROR PAGE!' . $cgi_path);
}

Nginx配置免费SSL证书StartSSL,解决Firefox不信任问题

先在StartSSL上申请免费一年的SSL证书,具体过程网上很多教程。然后把申请到的key和crt文件上传到服务器,比如/usr/local/nginx/certs/.

 Nginx配置SSL证书

直接贴上我的nginx的部分配置:

server {
        listen 443;
	server_name   liuzhichao.com www.liuzhichao.com ;
        ssl on;
        ssl_certificate /usr/local/nginx/ssl/ssl.crt;
        ssl_certificate_key /usr/local/nginx/ssl/ssl.key;if($http_transfer_encoding ~* chunked){return444;}

	gzip on;if(-d $request_filename){
		rewrite ^/(.*)([^/])$ $scheme://$host/$1$2/ permanent;}

	 root   /home/wwwroot/;

	 ssi off;
	 ssi_silent_errors off;
	 ssi_types text/shtml;

	 location /{
		 index  index.html index.htm index.shtml index.php;
		 autoindex	off;}

	location /nginx_status {
		stub_status on;
		access_log off;}

	 location ~(favicon.ico){ 
		 log_not_found off;
		 access_log   off;}

	 location ~* \.(gif|jpg|jpeg|png|bmp|swf)$ {
		 expires 1y;}

	 location ~* \.(js|css)$ {
		 expires 7d;}#------------
	 location ~*^(.+)\.(php[3-9]?|phtm[l]?)(\/.*)*$ {set $real_script_name $1.$2;set $path_info $3;if(!-f $document_root$real_script_name){return404;}

		  fastcgi_pass 127.0.0.1:8999; fastcgi_param HTTPS on;
		  include enable_php.conf;}}

现在重启Nginx,Chrome应该能正常显示Https.如果只想使用Https连接,可以再添加一个server,然后跳转到https

server {
        listen 80;
	server_name   liuzhichao.com www.liuzhichao.com ;
        rewrite     ^   https://$server_name$request_uri? permanent;}

 解决Firefox不信任StartSSL证书问题

wget http://cert.startssl.com/certs/ca.pem
wget http://cert.startssl.com/certs/sub.class1.server.ca.pem
cat ca.pem sub.class1.server.ca.pem >> ca-certs.crt
cat ca-certs.crt >> ssl.crt

再次重启Nginx,本想这下Firefox也应该能正常识别证书了,但是重启Nginx遇到了SSL: error:0906D066:PEM routines:PEM_read_bio:bad end line error错误。

[emerg]: SSL_CTX_use_certificate_chain_file("/usr/local/nginx/certs/ssl.crt")
 failed (SSL: error:0906D066:PEM routines:PEM_read_bio:bad end line error:140DC009:SSL routines:SSL_CTX_use_certificate_chain_file:PEM lib)
configuration file /usr/local/nginx/conf/nginx.conf test failed

这个的意思就是server.crt读取到意外错误行.这是因为我们在合并StartSSL提供的crt证书时,直接cat到了ssl.crt里。使用vi或者nano命令打开并编辑ssl.crt,找到:

-----END CERTIFICATE----------BEGIN CERTIFICATE-----

修改为:

-----END CERTIFICATE----------BEGIN CERTIFICATE-----

保存这个crt文件,再次重启Nginx服务,输入申请证书时私钥的密码,启动成功后,现在使用Firefox访问网站也能信任证书了。