FastCGI в Nginx

Как использовать Nginx для работы с любым языком программирования? Использовать универсальный протокол FastCGI!

Установка fcgiwrap

Для работы Nginx с FastCGI необходимо установить пакет fcgiwrap:

sudo apt-get install fcgiwrap

Настройка Nginx

Конфиг Nginx, в примере порт 8300:

fcgi.conf
server {
 
  server_name _;
  listen 8300;
  error_log /var/log/nginx/8300-error.log error;
  access_log /var/log/nginx/8300.log combined;
 
  proxy_connect_timeout       600;
  proxy_send_timeout          600;
  proxy_read_timeout          600;
  send_timeout                600;
 
  # Для данного location будем использовать только FastCGI
 
  location ^~ /cgi/ {
    gzip           off;
    root           /var/www;
    fastcgi_pass   unix:/var/run/fcgiwrap.socket;
 
    # Эту часть можно вынести в отдельный файл и делать include fastcgi_params;
 
    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  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
    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  GATEWAY_INTERFACE  CGI/1.1;
    fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;
 
    fastcgi_param  REMOTE_ADDR        $remote_addr;
    fastcgi_param  REMOTE_PORT        $remote_port;
    fastcgi_param  SERVER_ADDR        $server_addr;
    fastcgi_param  SERVER_PORT        $server_port;
 
    # According to RFC3875 (https://tools.ietf.org/html/rfc3875#section-4.1.14) in SERVER_NAME
    # we should put actual hostname user came to. For nginx it is in $host
    # This will allow to run multihost instances
 
    fastcgi_param  SERVER_NAME        $host;
  }
}

Пример FastCGI на bash

Bash скрипт, который формирует ответ:

/var/www/cgi/index.sh
#!/bin/bash
NAME=`"cpuinfo"`
echo "Content-type:text/html"
echo
echo "<html><head>"
echo "<title>$NAME</title>"
echo '<meta name="description" content="'$NAME'">'
echo '<meta name="keywords" content="'$NAME'">'
echo '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">'
echo '<meta name="ROBOTS" content="noindex">'
echo "</head><body><pre>"
date
echo -e "\nuname -a"
uname -a
echo -e "\ncpuinfo"
cat /proc/cpuinfo
echo "</pre></body></html>"

Для CGI скриптов следует выставить атрибут выполнения (chmod +x файл-скрипта), а сам скрипт будет выполнен под пользователем, от которого работает Nginx.

Теперь по адресу http://localhost:8300/cgi/index.sh будет выполняться Bash-скрипт:

Bash-скрипт как веб-сервер

2018/09/17 16:32:09 [error] 1254#1254: *187 FastCGI sent in stderr: "Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?" while reading response header from upstream, client: 127.0.0.1, server: _, request: "GET /cgi/index.sh HTTP/1.1", upstream: "fastcgi://unix:/var/run/fcgiwrap.socket:", host: "localhost:8300"

Такая ошибка указывает на то, что забыли выставить +x для скрипта.

Пример FastCGI на NodeJS

NodeJS в данном случае запускается каждый раз, и НЕ работает как отдельный веб-сервер.

/var/www/cgi/index.js
#!/usr/bin/node
console.log("Content-type:text/html");
console.log("");
console.log("<html><head>");
console.log("<meta http-equiv=\"Content-type\" content=\"text/html;charset=UTF-8\">");
console.log("</head><body>");
console.log("Текущее время: ", new Date());
console.log("</body></html>");

Пример FastCGI на C

fastcgi.c
#include <stdio.h>
int main(void){
  printf("Content-Type: text/html\r\n");
  printf("Connection: close\r\n");
  printf("\r\n \r\n");
  printf("<html><head></head>\r\n");
  printf("<body>\r\n");
  printf("Hello world.\r\n");
  printf("<br />\r\n");
  printf("Bye Bye\r\n");
  printf("</body></html>\r\n");
  return 0;
}

Далее скомпилируем:

gcc -o fastcgi fastcgi.c

При переходе по http://localhost:8300/cgi/fastcgi увидим

Hello world. 
Bye Bye

Взято отсюда

Пример FastCGI на PHP

PHP можно использовать через различные интерфейсы SAPI.

Чтобы понять все различия, я написал этот скрипт:

fast.php
#!/usr/bin/php
<?php
 
if('cli' === php_sapi_name()) {
  /*
    Nginx с помощью fcgiwrap запускает скрипт через cli-интерфейс, поэтому отправляем заголовок "вручную", иначе получим "502 Bad Gateway"
  */
  echo "Content-type: text/plain\n\n";
} else {
  /*
    В других случаях используем штатный способ, и удаляем "#!/usr/bin/php" (PHP буферизирует вывод, и такая небольшая строка как раз умещается)
  */
  ob_end_clean();
  ob_start();
  header("Content-type: text/plain");
  /*
    Откроем stdout и stderr (в cli они уже открыты и доступны через константы STDOUT и STDERR)
  */
  define('STDOUT', fopen('php://stdout', 'w'));
  define('STDERR', fopen('php://stderr', 'w'));
}
 
echo "php_sapi_name: " . php_sapi_name() . "\n";
 
echo "stdout: ";
fwrite(STDOUT, "STDOUT");
echo "\n";
 
/*
  При SAPI !== 'cli' вызов `ob_end_clean();` очистит предыдущий вывод
*/
 
// ob_end_clean();
 
/*
  - Если используется веб-сервер, он "отрежет" вывод ошибок, вне зависимости от SAPI
  - Если веб-сервера нет, и запуск через командную строку, ошибка будет выведена в stderr
*/
echo "stderr: ";
fwrite(STDERR, "STDERR");
echo "\n";

FastCGI, работающий через fcgiwrap, который запускает cli:

php_sapi_name: cli
stdout: STDOUT
stderr: 

fpm-fcgi:

php_sapi_name: fpm-fcgi
stdout: 
stderr: 

cgi-fcgi:

php_sapi_name: cgi-fcgi
stdout: 
stderr: 

php fast.php

Content-type: text/plain

php_sapi_name: cli
stdout: STDOUT
stderr: STDERR

Таким образом, запись в stdout с выводом на экран работает только для Cli-SAPI, в остальных случаях - молчание.