python - Python3 cgi.FieldStorage 解析文件名但不解析边界标签之间的内容

我继承了一个 python 项目,我们试图用 python 3.5.6 解析一个 70 MB 的文件。我正在使用 cgi.FieldStorage

我正在尝试发送的文件(名为:paketti.ipk):

kissakissakissa
kissakissakissa
kissakissakissa

标题:

X-FILE: /tmp/nginx/0000000001
Host: localhost:8082
Connection: close
Content-Length: 21
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:100.0) Gecko/20100101 Firefox/100.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------264635460442698726183359332565
Origin: http://172.16.8.12
Referer: http://172.16.8.12/
DNT: 1
Sec-GPC: 1

临时文件 /tmp/nginx/0000000001:

-----------------------------264635460442698726183359332565
Content-Disposition: form-data; name="file"; filename="paketti.ipk"
Content-Type: application/octet-stream

kissakissakissa
kissakissakissa
kissakissakissa

-----------------------------264635460442698726183359332565--

代码:

class S(BaseHTTPRequestHandler):
  def do_POST(self):
    temp_filename = self.headers['X-FILE']
    temp_file_pointer=open(temp_filename,"rb")
    form = cgi.FieldStorage( fp=temp_file_pointer, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], 'CONTENT_LENGTH':self.headers['Content-Length'] }, )
    actual_filename = form['file'].filename
    logging.info("ACTUAL FILENAME={}".format(actual_filename))
    open("/tmp/nginx/{}".format(actual_filename), "wb").write(form['file'].file.read())
    logging.info("FORM={}".format(form))

现在最奇怪的事情。日志显示:

INFO:root:ACTUAL FILENAME=paketti.ipk
INFO:root:FORM=FieldStorage(None, None, [FieldStorage('file', 'paketti.ipk', b'')])

查看 /tmp/nginx 目录:

root@am335x-evm:/tmp# ls -la /tmp/nginx/*
-rw-------    1 www      www            286 May 18 20:48 /tmp/nginx/0000000001
-rw-r--r--    1 root     root             0 May 18 20:48 /tmp/nginx/paketti.ipk

因此,这就像部分工作,因为名称已获得。但是为什么它不解析数据内容呢?我错过了什么?

这在 python 上是否可行,还是我应该只编写一个 C 实用程序?该文件为 70 MB,如果我在内存中读取它,OOM-killer 会杀死 python3 进程(我会说这是理所当然的)。但是,是的,数据内容去哪里了?

回答1

而不是 cgi 模块需要一个多部分解析器,它可以流式传输数据,而不是将其全部读取到 RAM。 AFAIK 标准库中没有任何用处,但此模块可能有用:https://github.com/defnull/multipart

或者,按照这些思路DIY一些东西应该可以工作:

boundary = b"-----whatever"
# Begin and end lines (as per your example, I didn't check the RFCs)
begin = b"\r\n%b\r\n" % boundary
end = b"\r\n%b--\r\n" % boundary
# Prefer with blocks to open files so that they are also closed properly
with open(temp_filename, "rb") as f:
  buf = bytearray()
  # Search for the boundary
  while begin not in buf:
    block = f.read(4096)
    if not block: raise ValueError("EOF without boundary begin")
    buf = buf[-1024:] + block  # Keep up to 5 KiB buffered
  # Delete buffer contents until the end of the boundary
  del buf[:buf.find(begin) + len(begin)]

  # Copy data to another file (or do what you need to do with it)
  with open("output.dat", "wb") as f2:
    while end not in buf:
      f2.write(buf[:-1024])
      del buf[:-1024]
      buf += f.read(4096)
      if not buf: raise ValueError("EOF without boundary end")
    f2.write(buf[:buf.find(end)])

假定边界最多只有 1024 个字节。您可以使用实际长度来代替完美。

回答2

游戏中的问题比我最初想象的要多。

首先,/tmp 来自最大大小为 120MB 的 tmpfs。

其次,我的 nginx.conf 有问题。我需要注释掉这样的东西来清理它:

#client_body_in_file_only       on
#proxy_set_header               X-FILE $request_body_file;
#proxy_set_body                 $request_body_file;

然后我需要添加这些

proxy_redirect                 off; # Maybe not that importnat
proxy_request_buffering        off; # Very important

在此之后的代码

form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], })

开始“工作”。我正在监视 /tmp 的使用情况,它首先使用 70MB,然后使用完整的 120MB。上传的文件被截断为 50 MB。

因此,当我在 4096 个字符的循环中读取和写入解析的 cgi.FieldStorage 时,系统会自动将其完全读取到 /tmp 中的某个位置,然后尝试写入最终文件并遇到“设备上没有剩余空间“ 错误。

为了解决这个问题,我保留了 nginx.conf 的附加内容,并在循环中手动读取 self.rfile,完全读取 ['Content-Length'] (其他任何东西都会让它变得疯狂)。这样一来就可以将其保存干净; /tmp 的使用不超过一次 70MB。

相似文章

最新文章