【问题标题】:Python 2.7 ZIP archive broken when sending as HTTP responsePython 2.7 ZIP 存档在作为 HTTP 响应发送时损坏
【发布时间】:2020-10-24 00:32:06
【问题描述】:

我在 Win 10 上的 Apache 2.4 上将 Python 2.7 脚本作为 CGI 运行,sctipt 在 HTTP 响应中发送一个 ZIP 存档作为下载。我关注了这个线程How to deploy zip files (or other binaries) trough cgi in Python?,但不断收到损坏的 ZIP 文件。我希望有人可以提供帮助,因为我已经尝试解决此问题 2 天,但找不到有关此行为的任何信息。

演示脚本:

import cgi, cgitb, os
import shutil

cgitb.enable()

out_path = os.path.dirname(__file__) + "\\tmp_uploads\\test2.zip"  
            
# send output zip as download
import sys
print "Content-Disposition: attachment; filename=\"test2.zip\""
print "Content-Type: application/zip"
print

##sys.stdout.flush()

with open(out_path,'rb') as zf:
    shutil.copyfileobj(zf, sys.stdout)
##    print zf.read()

启用sys.stdout.flush() 或使用print zf.read() 代替shutil.copyfileobj(zf, sys.stdout) 没有区别。

原始 ZIP 文件完好无损:

下载的存档损坏:

【问题讨论】:

    标签: python python-2.7 apache http


    【解决方案1】:

    我有同样的问题,Python 2.7.18 脚本作为 CGI 在 Win10 上的 lighttpd 网络服务器上。我将下载的 zip 文件与原始文件进行了比较,发现了问题。 Python 自动将标准输出中的所有 \n 转换为 \r\n。我发现Prevent Python print()'s automatic newline conversion to CRLF on Windows 的唯一方法是使用 sys.stdout.buffer,它在 Python 2.7 中不可用。

    更新: 正如 Dalen 所回答的那样,关闭缓冲是关键。 如果您不想设置特定于系统的 shebang,我找到了另一种关闭它的方法:

    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
    

    【讨论】:

    • 这不是唯一的方法。看我的回答。在 Python 2 中,Windows 正在制造一个问题,它认为 STDOUT 是文本,而不是二进制。如果 PIPE 没有缓冲(或者 Windows 不再是 PITA),Apache 的 mod_cgi 确实会强制执行二进制文件,为什么我没有任何想法,但它可以工作。 Python 3 对 STDOUT 强制执行二进制模式,但使用支持 unicode 文本的类文件对象覆盖它。要绕过它,您可以按照您所说的写入 sys.stdout.buffer 。所以在 Py 3 中这个问题实际上已经解决了。
    • 它有效,谢谢。我无法设置 shebang,但我找到了另一种关闭缓冲的方法。查看我的更新。
    • 太棒了!并不是说它很重要,但是这会破坏打印声明吗?它不应该,但 Windows 很奇怪。无论如何,我也会将 sys.stdout.write() 用于标头,以防万一。
    • 我使用 print 作为标题,使用 sys.stdout.write() 作为内容。到目前为止,我的测试没有问题。我将 msvcrt.setmode 和导入包装在 try-except 中,因此相同的代码可以在 unix 和 windows 上运行。不确定我是否应该在 unix 上也将模式设置为二进制,但到目前为止它可以工作。
    • 你也没有。 Unixoides 具有 fcntl 和 ioctl 系统调用来操作文件描述符,但任何 PIPE 默认情况下都以二进制形式打开。在 Linux 或 Mac 上使用 PIPE 的 IPC 或 Apache 或 Nginx 分叉和执行以任何语言编写的 CGI 脚本时,我从来没有遇到过任何问题。无论如何,您知道即使 *nixes 上的文件访问库也没有本机文本模式。在 Py2 中,在 *nix 机器上使用内置 open() 时,“r”和“rb”没有区别。在 Python3 文本模式下,*nix 和 Windows 都模拟了文本模式,因此在任何地方都可以顺利运行。
    【解决方案2】:

    首先,如果您希望 Windows 使用 PIPE 传输更多内容,尤其是二进制内容,在这种情况下,STDOUT,当分层更多的 PIPE (Apache CGI) 时,您必须完全关闭缓冲,并采取自己控制。 所以你的脚本的执行行应该是这样的:

    #!C:\Python27\python.exe -u
    

    其次,您必须使用 flush() 并分部分提供内容,即缓冲内容,否则,如果您发送的文件很大,则任何 HTTP 服务器都会认为您的脚本已冻结并终止它,发送超时或内部服务器错误响应客户端,或者客户端将在等待响应时间过长时终止连接。所以 STDOUT PIPE 必须是活泼的。

    data = zf.read(8192)
    while data:
        sys.stdout.write(data)
        sys.stdout.flush()
        data = zf.read(8192)
    

    最后。务必将 Content-Transfer-Encoding 标头声明为“二进制”。它有助于。此外,建议提供 Content-Length 标头,因为脚本的输出是连续流,并且 Apache 直到脚本结束才知道它将包含多少字节,此时为时已晚所有标头都已发送。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-25
      • 1970-01-01
      • 2023-01-31
      相关资源
      最近更新 更多