了解 `mod_rewrite`:在内部将 `foo.txt` 重定向到 `foo.txt.cgi`,其中 `foo.txt` 不存在?

Understanding `mod_rewrite`: internally redirect `foo.txt` to `foo.txt.cgi` where `foo.txt` doesn't exist?

我的目标是从目录中提供 .txt 文件,但是如果特定的 .txt 文件不存在,我想执行 internal 重定向到类似命名的 .txt.cgi 脚本,如果存在这样的脚本。这个问题是关于为什么一种方法似乎有效,但两种替代方法有效。

我有以下目录结构(即,作为 /var/www 或等效目录中某处的子目录):

% ls -ARl rewritecgi
total 0
drwxr-sr-x  2 posita  posita  170 Mar 16 14:36 test1
drwxr-sr-x  2 posita  posita  170 Mar 16 14:38 test2
drwxr-sr-x  2 posita  posita  170 Mar 16 14:38 test3

rewritecgi/test1:
total 24
-rw-r--r--  1 posita  posita  288 Mar 16 14:36 .htaccess
-rw-r--r--  1 posita  posita   28 Mar 16 14:34 other.txt
-rwxr-xr-x  1 posita  posita  143 Mar 16 14:20 test.txt.cgi

rewritecgi/test2:
total 24
-rw-r--r--  1 posita  posita  301 Mar 16 14:38 .htaccess
-rw-r--r--  1 posita  posita   28 Mar 16 14:34 other.txt
-rwxr-xr-x  1 posita  posita  143 Mar 16 14:19 test.txt.cgi

rewritecgi/test3:
total 24
-rw-r--r--  1 posita  posita  288 Mar 16 14:38 .htaccess
-rw-r--r--  1 posita  posita   28 Mar 16 14:34 other.txt
-rwxr-xr-x  1 posita  posita  143 Mar 16 14:20 test.txt.cgi
每个 test{1,2,3} 子目录中的

other.txt 只是一个纯文本文件。

test.txt.cgi 在每个 test{1,2,3} 子目录中包含:

#!/usr/bin/env sh
cat <<EOF
Content-Type: text/plain

Hi! I'm \`[=15=]\`!
EOF

test1/.htaccess如下:

Options +FollowSymlinks -Indexes -MultiViews
AddHandler cgi-script .cgi
RewriteEngine on
RewriteBase /~posita/rewritecgi/test1/
<Files ~ "\.cgi$">
    Options +ExecCGI
</Files>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule "^(.*\.txt)$" ".cgi"

test2/.htaccesstest1/.htaccess 相同,但将 +ExecCGI 移动到顶层(如果我使用 <Files>,我会得到 403)并且将 [H=cgi-script] 标志添加到 RewriteRule:

Options +ExecCGI +FollowSymlinks -Indexes -MultiViews
AddHandler cgi-script .cgi
RewriteEngine on
RewriteBase /~posita/rewritecgi/test2/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule "^(.*\.txt)$" ".cgi" [H=cgi-script]

mod_rewrite documentation for the H flag 建议这应该有效。

test3/.htaccesstest1/.htaccess 相同,但启用 +MultiViews 选项:

Options +FollowSymlinks -Indexes +MultiViews
AddHandler cgi-script .cgi
RewriteEngine on
RewriteBase /~posita/rewritecgi/test3/
<Files ~ "\.cgi$">
    Options +ExecCGI
</Files>
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule "^(.*\.txt)$" ".cgi"

毫不奇怪,所有 other.txt 文件都可以毫无问题地解决:

% for i in 1 2 3 ; do echo "----> ${i} <----" ; curl --location --post30{1,2,3} --silent "http://localhost/~posita/rewritecgi/test${i}/other.txt" ; done
----> 1 <----
Just a plain old text file.
----> 2 <----
Just a plain old text file.
----> 3 <----
Just a plain old text file.

所有三个 test.txt.cgi 脚本 运行 在显式调用时正确:

% for i in 1 2 3 ; do echo "----> ${i} <----" ; curl --location --post30{1,2,3} --silent "http://localhost/~posita/rewritecgi/test${i}/test.txt.cgi" ; done
----> 1 <----
Hi! I'm `/.../posita/public_html/rewritecgi/test1/test.txt.cgi`!
----> 2 <----
Hi! I'm `/.../posita/public_html/rewritecgi/test2/test.txt.cgi`!
----> 3 <----
Hi! I'm `/.../posita/public_html/rewritecgi/test3/test.txt.cgi`!

但是,只有 http://localhost/~posita/rewritecgi/test1/test.txt 重定向到 CGI:

% for i in 1 2 3 ; do echo "----> ${i} <----" ; curl --location --post30{1,2,3} --silent "http://localhost/~posita/rewritecgi/test${i}/test.txt" ; done
----> 1 <----
Hi! I'm `/.../posita/public_html/rewritecgi/test1/test.txt.cgi`!
----> 2 <----
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /~posita/rewritecgi/test2/test.txt was not found on this server.</p>
</body></html>
----> 3 <----
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL /~posita/rewritecgi/test3/test.txt was not found on this server.</p>
</body></html>

使用 RewriteLogLevel 9 我得到以下 http://localhost/~posita/rewritecgi/test2/test.txt 的日志消息:

==> /var/log/apache2/rewrite_log <==
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (3) [perdir /.../posita/public_html/rewritecgi/test2/] strip per-dir prefix: /.../posita/public_html/rewritecgi/test2/test.txt -> test.txt
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (3) [perdir /.../posita/public_html/rewritecgi/test2/] applying pattern '^(.*\.txt)$' to uri 'test.txt'
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (4) [perdir /.../posita/public_html/rewritecgi/test2/] RewriteCond: input='/.../posita/public_html/rewritecgi/test2/test.txt' pattern='!-f' => matched
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (4) [perdir /.../posita/public_html/rewritecgi/test2/] RewriteCond: input='/.../posita/public_html/rewritecgi/test2/test.txt' pattern='!-d' => matched
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (2) [perdir /.../posita/public_html/rewritecgi/test2/] rewrite 'test.txt' -> 'test.txt.cgi'
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (3) [perdir /.../posita/public_html/rewritecgi/test2/] add per-dir prefix: test.txt.cgi -> /.../posita/public_html/rewritecgi/test2/test.txt.cgi
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (2) [perdir /.../posita/public_html/rewritecgi/test2/] remember /.../posita/public_html/rewritecgi/test2/test.txt.cgi to have Content-handler 'cgi-script'
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (2) [perdir /.../posita/public_html/rewritecgi/test2/] trying to replace prefix /.../posita/public_html/rewritecgi/test2/ with /~posita/rewritecgi/test2/
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (5) strip matching prefix: /.../posita/public_html/rewritecgi/test2/test.txt.cgi -> test.txt.cgi
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (4) add subst prefix: test.txt.cgi -> /~posita/rewritecgi/test2/test.txt.cgi
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (1) [perdir /.../posita/public_html/rewritecgi/test2/] internal redirect with /~posita/rewritecgi/test2/test.txt.cgi [INTERNAL REDIRECT]
127.0.0.1 - - [16/Mar/2015:15:30:42 --0700] [localhost/sid#7fb378812308][rid#7fb3788abea0/initial] (1) force filename redirect:/~posita/rewritecgi/test2/test.txt.cgi to have the Content-handler 'cgi-script'

==> /var/log/apache2/access_log <==
127.0.0.1 - - [16/Mar/2015:15:30:42 -0700] "GET /~posita/rewritecgi/test2/test.txt HTTP/1.1" 404 229

==> /var/log/apache2/error_log <==
[Mon Mar 16 15:30:42 2015] [error] [client 127.0.0.1] script not found or unable to stat: redirect:/~posita/rewritecgi/test2/test.txt.cgi

对于http://localhost/~posita/rewritecgi/test3/test.txt

==> /var/log/apache2/rewrite_log <==
127.0.0.1 - - [16/Mar/2015:15:35:47 --0700] [localhost/sid#7fb378812308][rid#7fb37889aea0/subreq] (3) [perdir /.../posita/public_html/rewritecgi/test3/] strip per-dir prefix: /.../posita/public_html/rewritecgi/test3/test.txt.cgi -> test.txt.cgi
127.0.0.1 - - [16/Mar/2015:15:35:47 --0700] [localhost/sid#7fb378812308][rid#7fb37889aea0/subreq] (3) [perdir /.../posita/public_html/rewritecgi/test3/] applying pattern '^(.*\.txt)$' to uri 'test.txt.cgi'
127.0.0.1 - - [16/Mar/2015:15:35:47 --0700] [localhost/sid#7fb378812308][rid#7fb37889aea0/subreq] (1) [perdir /.../posita/public_html/rewritecgi/test3/] pass through /.../posita/public_html/rewritecgi/test3/test.txt.cgi

==> /var/log/apache2/access_log <==
127.0.0.1 - - [16/Mar/2015:15:35:47 -0700] "GET /~posita/rewritecgi/test3/test.txt HTTP/1.1" 404 229

==> /var/log/apache2/error_log <==
[Mon Mar 16 15:35:47 2015] [error] [client 127.0.0.1] Negotiation: discovered file(s) matching request: /.../posita/public_html/rewritecgi/test3/test.txt (None could be negotiated).

那么是什么原因呢?为什么添加 [H=cgi-script] 会导致 test2 不调用 CGI?对于 test3,为什么启用 +MultiViews 会规避 mod_rewrite?我知道 mod_rewrite is voodoo,但我真的很想了解这些情况之间的细微差别。

如果在当前目录中找不到文件,Multiviews 是在其他目录中查找文件的选项。这意味着它查找结果不限于同一目录,而不是绕过mod_rewrite。它使用 mod_negotiation。使用选项 Multiviews 让服务器根据模式选择最好的文件。这不是你在使用 .htaccess 和 mod_rewrite 时遇到的情况。我从不使用它。

我注意到的是您的 .htaccess 中的 RewriteConds。它们不是必需的。没有他们,我也能正常工作。您在 .htaccess 中指定的越多,您遇到的问题就越多。 (重写确实是巫术。) 我在没有这些条件的情况下制作了一个 .htaccess 文件并且它有效:

Options +ExecCGI
AddHandler cgi-script .cgi
RewriteEngine on
RewriteBase /WhosebugQuestions/tests/cgi/
RewriteRule ^(.*)\.txt$ .cgi [L]

这足以使其正常工作。现在 RewriteRule 解析为相同的文件名,只是另一个扩展名,而不是文件名 + 扩展名。 在处理程序标签 [H...] 中,您是否需要指定 cgi 文件的 mime 类型。那是 application/x-httpd-cgi,不是 cgi-script。但是使用AddHandler时不需要指定H-flag。