Serving CGI Scripts With Nginx On Ubuntu 12.04 - Page 2

Want to support HowtoForge? Become a subscriber!
 
Submitted by falko (Contact Author) (Forums) on Tue, 2012-09-04 17:00. ::

3 Using Simple CGI

Another approach is to use a CGI wrapper script, as described here: http://wiki.nginx.org/NginxSimpleCGI

The advantage over Thttpd is that each vhost can have its own cgi-bin directory. The CGI wrapper should work well for simple CGI scripts, but you might get problems with more complex scripts.

Let's install the requirements for the CGI wrapper script:

apt-get install libfcgi-perl libfcgi-procmanager-perl

Now we create the /usr/local/bin/cgiwrap-fcgi.pl wrapper script (the script is taken from http://wiki.nginx.org/NginxSimpleCGI):

vi /usr/local/bin/cgiwrap-fcgi.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( "/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";
    }
  }
}

Make the script executable...

chmod 755 /usr/local/bin/cgiwrap-fcgi.pl

... and create the /var/run/nginx/ directory where the script will create its socket (that nginx will connect to):

mkdir /var/run/nginx/

By default, nginx runs as the www-data user; nginx must have read and write access to the /var/run/nginx/ directory, therefore we do a chown:

chown www-data:www-data /var/run/nginx/

Because the socket /var/run/nginx/cgiwrap-dispatch.sock (which will be created by the wrapper) must also be owned by www-data, we start the wrapper as the www-data user:

su www-data

/usr/local/bin/cgiwrap-fcgi.pl &> /dev/null &

The wrapper should now run as a daemon in the background, so we can become root again:

exit

Now open your vhost configuration file...

vi /etc/nginx/sites-enabled/www.example.com.vhost

... and add a location /cgi-bin {} section to the server {} container:

server {
[...]
   location /cgi-bin {
      root /var/www/www.example.com;
      gzip off; #gzip makes scripts feel slower since they have to complete before getting gzipped
      fastcgi_pass  unix:/var/run/nginx/cgiwrap-dispatch.sock;
      include /etc/nginx/fastcgi_params;
      fastcgi_index index.cgi;
      fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
   }
[...]
}

Reload nginx:

/etc/init.d/nginx reload

Next we create our cgi-bin directory - /var/www/www.example.com/cgi-bin because we defined root /var/www/www.example.com; in the location /cgi-bin {} container:

mkdir /var/www/www.example.com/cgi-bin

Now we place our CGI scripts in it and make them executable. For testing purposes I will create a small Hello World Perl script (instead of hello_world.cgi you can also use the extension .pl -> hello_world.pl):

vi /var/www/www.example.com/cgi-bin/hello_world.cgi

#!/usr/bin/perl -w

     # Tell perl to send a html header.
     # So your browser gets the output
     # rather then <stdout>(command line
     # on the server.)
print "Content-type: text/html\n\n";

     # print your basic html tags.
     # and the content of them.
print "<html><head><title>Hello World!! </title></head>\n";
print "<body><h1>Hello world</h1></body></html>\n";

chmod 755 /var/www/www.example.com/cgi-bin/hello_world.cgi

Open a browser and test the script:

http://www.example.com/cgi-bin/hello_world.cgi

If all goes well, you should get the following output:


Please do not use the comment function to ask for help! If you need help, please use our forum.
Comments will be published after administrator approval.
Submitted by Anonymous (not registered) on Thu, 2012-09-06 16:47.
i don't believe that thttpd is included in ubuntu 12.04.  looks like it was dropped in debian: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=653752