nginx + fcgiwrap:fastcgi_param 的顺序怎么这么重要?

x-y*_*uri 5 nginx fastcgi cgi

我正在运行Debian 6.0.3( squeeze), nginx-0.7.67, fcgiwrap-1.0-1+squeeze1. 这是测试脚本:

#!/usr/bin/perl
use 5.010;
use warnings;
use strict;

use Data::Dumper;

print "Content-Type: text/html\n\n";
say Dumper {map {$_ => $ENV{$_}} 'SCRIPT_NAME', 'DOCUMENT_ROOT', 'WHATEVER'};
say "$<, $>, $(, $)";
Run Code Online (Sandbox Code Playgroud)

这是nginx配置:

server {
    server_name   domain.com;
    root   /home/yuri/6;
    access_log   /var/log/nginx/domain.com-access.log;
    error_log   /var/log/nginx/domain.com-error.log;

    location /cgi-bin/ {
        fastcgi_pass   unix:/var/run/fcgiwrap.socket;
        fastcgi_param   DOCUMENT_ROOT   $document_root;
        fastcgi_param   DOCUMENT_ROOT   /home/yuri/7/;
        fastcgi_param   SCRIPT_NAME   $fastcgi_script_name;
        fastcgi_param   WHATEVER   1;
        fastcgi_param   WHATEVER   2;
    }

    location /1.php {
        fastcgi_pass   unix:/var/run/php5-fpm.sock;
        fastcgi_param   PHP_ADMIN_VALUE   cgi.fix_pathinfo=1;
        fastcgi_param   REQUEST_METHOD    $request_method;
        fastcgi_param   SCRIPT_FILENAME   whatever;
        fastcgi_param   SCRIPT_FILENAME   $document_root$fastcgi_script_name;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果要访问 url http://domain.com/cgi-bin/1.pl,这是我在浏览器中得到的:

$VAR1 = { 'SCRIPT_NAME' => '/cgi-bin/1.pl', 'DOCUMENT_ROOT' => '/home/yuri/7/', 'WHATEVER' => '2' };
Run Code Online (Sandbox Code Playgroud)

所以看起来,它fcgiwrap使用第一个DOCUMENT_ROOT来查找脚本,但脚本获取参数的最后一个值。如果您更改DOCUMENT_ROOT指令的顺序,Web 服务器将返回403. 问题是……怎么会?

php虽然按预期工作:第二个SCRIPT_FILENAME覆盖第一个。

Lek*_*eyn 6

fastcgi 库只是将给定的任何参数传递给environ指针。getenv()正如所使用的,fcgiwrap首先使用任何环境变量(优化?)。PHP FPM 可能会像数组数据类型一样对待这个环境,任何后一个键都会覆盖第一个键。

您不应依赖顺序并确保只有一把钥匙。我没有查看 FastCGI 规范,无论是否记录了正确的行为。

至于为什么会出现403,我猜是没有/home/yuri/7/1.pl文件。(请记住,第一个参数由 fcgiwrap 匹配)。由于它不可执行,fcgiwrap 返回 403。

测试

下面的补丁打印了如下所示的环境FCGI_Accept()

diff --git a/fcgiwrap.c b/fcgiwrap.c
index e86ff9d..0dff2e6 100644
--- a/fcgiwrap.c
+++ b/fcgiwrap.c
@@ -470,6 +470,11 @@ static void inherit_environment(void)
    char * const * p;
    char *q;

+   for (p = environ; *p; p++)
+       fprintf(stderr, "ENV: %s\n", *p);
+
+   fprintf(stderr, "ENV[FOO] = %s\n", getenv("FOO"));
+
    for (p = inherited_environ; *p; p++) {
        q = strchr(*p, '=');
        if (!q) {
Run Code Online (Sandbox Code Playgroud)

用于测试的nginx配置:

events {
    worker_connections 768;
}

pid pid;
error_log error_log;
http {
    server {
        access_log off;
        listen 5555;
        location / {
            fastcgi_param FOO BAR;
            fastcgi_param FOO BARRED;
            fastcgi_pass unix:/tmp/sock;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

启动服务器的命令(假设nginx.conf在当前目录中):

$ nginx -p . -c nginx.conf
$ env -i ./fcgiwrap -p /dev/null -s unix:/tmp/sock
Run Code Online (Sandbox Code Playgroud)

现在,运行curl http://localhost:5555/,stderr 将打印:

ENV: FCGI_ROLE=RESPONDER
ENV: FOO=BAR
ENV: FOO=BARRED
ENV: HTTP_USER_AGENT=curl/7.30.0
ENV: HTTP_HOST=localhost:5555
ENV: HTTP_ACCEPT=*/*
ENV[FOO] = BAR
Cannot get script name, are DOCUMENT_ROOT and SCRIPT_NAME (or SCRIPT_FILENAME) set and is the script executable?
Run Code Online (Sandbox Code Playgroud)

另外,这是一个开发版本。当前稳定版(1.1.0)不包含-p上述参数。对于结果来说,这并不重要,您也可以放弃它并收到一个没有SCRIPT_NAME给出的错误或其他错误。