【问题标题】:How does cgi.FieldStorage store files?cgi.FieldStorage 如何存储文件?
【发布时间】:2011-10-14 08:25:06
【问题描述】:

所以我一直在玩原始的 WSGI、cgi.FieldStorage 和文件上传。而且我只是无法理解它如何处理文件上传。

起初它似乎只是将整个文件存储在内存中。我想嗯,这应该很容易测试 - 一个大文件应该堵塞内存!..它没有。不过,当我请求文件时,它是一个字符串,而不是迭代器、文件对象或任何东西。

我尝试阅读 cgi 模块的源代码并发现了一些关于临时文件的信息,但它返回了一个奇怪的字符串,而不是一个文件(类似)对象!那么......它是如何工作的?!

这是我使用的代码:

import cgi
from wsgiref.simple_server import make_server

def app(environ,start_response):
    start_response('200 OK',[('Content-Type','text/html')])
    output = """
    <form action="" method="post" enctype="multipart/form-data">
    <input type="file" name="failas" />
    <input type="submit" value="Varom" />
    </form>
    """
    fs = cgi.FieldStorage(fp=environ['wsgi.input'],environ=environ)
    f = fs.getfirst('failas')
    print type(f)
    return output


if __name__ == '__main__' :
    httpd = make_server('',8000,app)
    print 'Serving'
    httpd.serve_forever()

提前致谢! :)

【问题讨论】:

    标签: python cgi wsgi


    【解决方案1】:

    查看cgi module description,有一段讨论如何处理文件上传。

    如果一个字段表示一个上传的文件,则通过 value 属性或getvalue() 方法访问该值将整个文件作为字符串读取到内存中。这可能不是你想要的。您可以通过测试文件名属性或 file 属性来测试上传的文件。然后你可以从文件属性中随意读取数据:

    fileitem = form["userfile"]
    if fileitem.file:
        # It's an uploaded file; count lines
        linecount = 0
        while 1:
            line = fileitem.file.readline()
            if not line: break
            linecount = linecount + 1
    

    关于您的示例,getfirst() 只是getvalue() 的一个版本。 尝试替换

    f = fs.getfirst('failas')
    

    f = fs['failas'].file
    

    这将返回一个“在闲暇时”可读的类似文件的对象。

    【讨论】:

    • 谢谢 :) 我主要使用 Django,但有时我喜欢玩一些低级的东西 :)
    【解决方案2】:

    最好的方法是不要读取文件(或者甚至像 gimel 建议的那样一次读取每一行)。

    您可以使用一些继承并从 FieldStorage 扩展一个类,然后覆盖 make_file 函数。当 FieldStorage 是文件类型时调用 make_file。

    供您参考,默认的 make_file 如下所示:

    def make_file(self, binary=None):
        """Overridable: return a readable & writable file.
    
        The file will be used as follows:
        - data is written to it
        - seek(0)
        - data is read from it
    
        The 'binary' argument is unused -- the file is always opened
        in binary mode.
    
        This version opens a temporary file for reading and writing,
        and immediately deletes (unlinks) it.  The trick (on Unix!) is
        that the file can still be used, but it can't be opened by
        another process, and it will automatically be deleted when it
        is closed or when the current process terminates.
    
        If you want a more permanent file, you derive a class which
        overrides this method.  If you want a visible temporary file
        that is nevertheless automatically deleted when the script
        terminates, try defining a __del__ method in a derived class
        which unlinks the temporary files you have created.
    
        """
        import tempfile
        return tempfile.TemporaryFile("w+b")
    

    而不是创建临时文件,在任何你想要的地方永久创建文件。

    【讨论】:

      【解决方案3】:

      使用@hasanatkazmi 的答案(在 Twisted 应用程序中使用)我得到了类似的结果:

      #!/usr/bin/env python2
      # -*- coding: utf-8 -*-
      # -*- indent: 4 spc -*-
      import sys
      import cgi
      import tempfile
      
      
      class PredictableStorage(cgi.FieldStorage):
          def __init__(self, *args, **kwargs):
              self.path = kwargs.pop('path', None)
              cgi.FieldStorage.__init__(self, *args, **kwargs)
      
          def make_file(self, binary=None):
              if not self.path:
                  file = tempfile.NamedTemporaryFile("w+b", delete=False)
                  self.path = file.name
                  return file
              return open(self.path, 'w+b')
      

      请注意,该文件并非总是由 cgi 模块创建。根据这些cgi.py 行,只有在内容超过 1000 字节时才会创建它:

      if self.__file.tell() + len(line) > 1000:
          self.file = self.make_file('')
      

      因此,您必须检查该文件是否实际上是通过对自定义类的 path 字段的查询创建的,如下所示:

      if file_field.path:
          # Using an already created file...
      else:
          # Creating a temporary named file to store the content.
          import tempfile
          with tempfile.NamedTemporaryFile("w+b", delete=False) as f:
              f.write(file_field.value)
              # You can save the 'f.name' field for later usage.
      

      如果还为该字段设置了Content-Length,这似乎很少见,那么该文件也应该由cgi创建。

      就是这样。通过这种方式,您可以以可预测的方式存储文件,从而减少应用的内存使用量。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-12-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-25
        • 2018-03-13
        相关资源
        最近更新 更多