我们用到的很多开源程序比如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); }