阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

如何在Nginx中使用CGI

156次阅读
没有评论

共计 8613 个字符,预计需要花费 22 分钟才能阅读完成。

缘起
在探索到 CGI 到底为何物时,google SimpleCGI 的时候,看到这篇文章,粗看了几眼,觉得不错,有几个感兴趣的 topic:CGI,FastCGI,Nginx。

正文
For the most part,lack of CGI support in Nginx is not an issue and actually has an important side-benefit: because Nginx cannot directly execute external programs (CGI), a malicious person can’t trick your system into uploading and executing an arbitrary script.

大体而言,Nginx 中缺乏对 CGI 的支持不是问题,实际上还带来一些好处:因为 Nginx 不直接执行外部程序(CGI),心怀不轨的人不能通过上传并执行任意脚本来攻击你的系统。

There are still ways, of course (uploading a PHP script into a directory where you’ve got a directive to execute PHP FastCGI scripts for example), but it does in fact make it a bit more difficult (or conversely, easier to secure).

当然,依然存在一些攻击系统的方法,比如上传 PHP 脚本到某个目录,但你定向到给目录时,执行 PHP FastCGI 脚本,但这显然更加困难一些。

Still, sometimes you have a one-off simple CGI program you need to support and here’s a recipe for exposing your CGI to Nginx as a FastCGI instead:

有时,需要支持一次性的 CGI 脚本。这里介绍一个将 CGI 程序转换为 FastCGI 然后传递给 Nginx 的方法:

Let’s say this is our CGI program, written in Perl:

下面介绍一下这个 Perl 编写的 CGI 程序:

#!/usr/bin/perl

print “Content-type: text/html\n\n”;
print “<html><body>Hello, world.</body></html>”;
Make sure that the web server can read the file and it is marked as executable.

确保 web 服务器能够读取并将其看作可执行文件。

Next, we need a Perl wrapper that will run as a FastCGI and run this for us:

接下来,需要一个运行为 FastCGI 的 Perl 包装器来运行这个程序:

This one is still pretty ugly, but forwards stdio around correctly (not necessarily efficiently or prettily), and also reads and spits back out STDERR into fcgi STDERR. I am not entirely confident there is no race condition in the event that an app STDERRS out without any output data, but it works for my purposes. This is for the most part, not my own work; 99.9% of this came from the various improvements on the spawner script worked on in the mailing list and wiki here.. I just added some more pipe handling to enable communicating through STDERR, and a select loop to properly recieve both data streams. — bryon@spideroak.com

下面的这个包装器相当的简陋,但在 stdio 上大体正确(不需要高效或完美),也可以将标准 STDERR 读取和输出为 fcgi 的 STDERR。不确定代码中在应用中没有任何输出数据的 STDERRS 中是否存在竞争条件,这个包装器很好满足了我的目标。包装器中 99.9% 的部分来自邮件列表和 wiki,我(原作者)只添加了一些管道处理从而确保能够通过 STDERR 进行通信,以及一个接受合适的数据流的选择循环。

#!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(“/var/run/nginx/cgiwrap-dispatch.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”;
    }
  }
}
Save the above script as /usr/local/bin/cgiwrap-fcgi.pl.
将上述文件保存为 /usr/local/bin/cgiwrap-fcgi.pl。

Just running the program as above will bind it to a unix socket at /var/run/nginx/cgiwrap-dispatch.sock . Be sure your nginx worker process user has read/write access to this file. The script does not fork itself, so you will need to background it somehow (with Bash add an ampersand “&” at the end of your command to execute it).

运行上述程序将会将其绑定到位于 /var/run/nginx/cgiwrap-dispatch.sock 的 Unix 套接字。取保 Nginx 工作进程具有文件的读写权限。该脚本不会自我终结(?),需要将其运行为后台程序(Bash 中在执行命令后添加 &)

If this all works, then the next part is to setup Nginx:

如果工作了,接下来就是启动 Nginx:

http {
  root  /var/www/htdocs;
  index index.html;
  location ~ ^/cgi-bin/.*\.cgi$ {
    gzip off; #gzip makes scripts feel slower since they have to complete before getting gzipped
    fastcgi_pass  unix:/var/run/nginx/cgiwrap-dispatch.sock;
    fastcgi_index index.cgi;
    fastcgi_param SCRIPT_FILENAME /var/www/cgi-bin$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;
  }
}
Restart Nginx and point your browser at your CGI program. The above sample config will execute any .cgi file in cgi-bin with the cgiwrap-fcgi.pl wrapper, tweak this to your heart’s content.
重启 Nginx 并将浏览器定向到 CGI 程序的 URL。上面的简单配置将会使用 cgiwrap-fcgi.pl 包装执行位于 cgi-bin 所有.cgi 后缀的文件,请谨记这一点,这可能会很危险。

I’ve been able to run Python, Perl, and C++ cgi apps with this – apps that use GET or POST.

可以使用 GET 或 POST 方法的 C ++,Perl,Python 的 cgi 应用都可运行。

You may find that $document_root does not point to your actual document root (hardcoded upon build), so you can replace the fastcgi param DOCUMENT_ROOT with “/the/real/path”. Also replace the SCRIPT_FILENAME param if that is used by your CGI wrapper (the one above does)

可以看到,$document_root 没有指向实际的文档 root(依赖构建的硬编码),需要将 fastcgi 参数 DOCUMENT_ROOT 替代为真实可用的参数,并将 SCRIPT_FILENAME 参书替代为 CGI 包装器使用的真实值。

后记
虽然,没有写过 CGI 代码,也没有配置安装过 Nginx,但我觉的这些东西都很有趣,等我学完 Rails,一定要好好研究研究 Nginx,和 Apahce 一起比对学习。

更多 Nginx 相关教程见以下内容

CentOS 6.2 实战部署 Nginx+MySQL+PHP http://www.linuxidc.com/Linux/2013-09/90020.htm

使用 Nginx 搭建 WEB 服务器 http://www.linuxidc.com/Linux/2013-09/89768.htm

搭建基于 Linux6.3+Nginx1.2+PHP5+MySQL5.5 的 Web 服务器全过程 http://www.linuxidc.com/Linux/2013-09/89692.htm

CentOS 6.3 下 Nginx 性能调优 http://www.linuxidc.com/Linux/2013-09/89656.htm

CentOS 6.3 下配置 Nginx 加载 ngx_pagespeed 模块 http://www.linuxidc.com/Linux/2013-09/89657.htm

CentOS 6.4 安装配置 Nginx+Pcre+php-fpm http://www.linuxidc.com/Linux/2013-08/88984.htm

Nginx 安装配置使用详细笔记 http://www.linuxidc.com/Linux/2014-07/104499.htm

Nginx 日志过滤 使用 ngx_log_if 不记录特定日志 http://www.linuxidc.com/Linux/2014-07/104686.htm

Nginx 的详细介绍:请点这里
Nginx 的下载地址:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-05/131051.htm

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计8613字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中