302 从 CGI 脚本重定向在 Apache 2.4 中停止工作

302 Redirect from CGI script stopped working in Apache 2.4

我继承了 self-written CGI 应用程序的维护工作,但没有 文档并且从未见过原作者。应用程序 在 Debian 8 中停止工作,但在 Debian 7 和 CentOS 5 中工作。 主要变化是从 Apache 2.2 升级(由 Debian 7 和 CentOS 5) 到 Apache 2.4(由 Debian 8 使用)以及从 perl 升级 5.8(在 CentOS 5 中)分别是 perl 5.14(在 Debian 7 中)到 perl 5.20。 有问题的部分归结为以下脚本(a 302-重定向):

#!/usr/bin/perl
$|=1; # activate auto-flushing of stdout
use strict;
use warnings;
my $CRLF = "52";
print STDOUT "Status: 302 Moved Temporarily$CRLF" .
             "Location: /does_not_matter$CRLF" .
             "URI: /does_not_matter$CRLF" .
             "Connection: close$CRLF" .
             "Content-type: text/html; charset=UTF-8$CRLF$CRLF";
close STDOUT;
while(1) {
        sleep 1;
}

观察到的行为是 重定向永远不会到达客户端 只要脚本在与 Apache 2.4 一起使用时仍然是 运行,但是 Apache 的 error.log 中没有错误消息。我换了客户端 (Firefox, Chromium, wget), Apache 模块 (mod_cgid & mod_cgi), 发送了额外的 headers,移除了 close STDOUT,移除了 $|=1,将 $CRLF 替换为 \n 并制作了脚本 fork 并退出 parent 进程(这样 Apache 就不再是 parent 过程),都无济于事。唯一有用的东西:使用 Apache 2.2,将脚本变成 NPH-CGI-script(必须发送 完整的 HTTP headers,Apache 不会以任何方式修改,即使 它们包含错误),使脚本退出而不是输入 无限循环。我通过 tcpdump 确认了带有 重定向确实在脚本被杀死之前永远不会离开服务器。 并从响应中的 Date-line 和最终的时间 到达 我收集到 Apache 立即收到我的输出(& 立即将 Date-line 添加到 headers),但不发送 回复客户。

不用回答了,我已经自己想好了 并会写一个答案。我只想提供解决方案 给可能遇到同样问题的其他人。

问题是缺少邮件正文。 302 重定向可能包含 消息正文(参见 RFC 2616,第 4.3 节(消息正文):“所有其他 响应确实包含消息正文,尽管它可能为零 length”),但是 Content-Length-line 是可选的(RFC 第 4.4 节 2616 表示消息体长度可以通过关闭 如果缺少 Content-Length-line,则连接)。由于 Apache 可以 不知道要不要发消息体,要等 直到我关闭连接或实际发送消息正文 (Apache 2.2 显然在这里表现错误,不等待 消息正文 - 或者可能 close STDOUT; 在 perl 中不做 5.20 它在旧的 perl 版本中做了什么)。正确的脚本应该 因此看起来像这样(已验证在 Apache 2.2 和 Apache 2.4) - 唯一的区别是额外的 $CRLF 终止零长度消息正文:

#!/usr/bin/perl
$|=1; # activate auto-flushing of stdout
use strict;
use warnings;
my $CRLF = "52";
print STDOUT "Status: 302 Moved Temporarily$CRLF" .
             "Location: /doesNotMatter$CRLF" .
             "URI: /doesNotMatter$CRLF" .
             "Connection: close$CRLF" .
             "Content-type: text/html; charset=UTF-8$CRLF$CRLF$CRLF";
close STDOUT;
while(1) {
        sleep 1;
}

给我指明了正确的方向。