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

Python3 cgi.FieldStorage parses file name but not contents between boundary tags

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

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

kissakissakissa
kissakissakissa
kissakissakissa

Headers:

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 进程(我会说这是理所当然的)。但是,是的,数据内容去了哪里?

而不是 cgi 模块需要一个可以流式传输数据而不是将所有数据读取到 RAM 的多部分解析器。据我所知,标准库中没有任何用处,但这个模块可能有用: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字节。为了完美,您可以使用实际长度。

发生的问题比我最初想象的要多。

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

其次,我的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 中的某处一次,然后尝试写入最终文件并遇到“设备上没有 space”错误。

为了解决这个问题,我保留了 nginx.conf 添加内容,只是自己在一个循环中手动阅读了 self.rfile,完全阅读了 ['Content-Length'](任何其他内容都会使它变得疯狂)。这样可以一次pass就把它救的干干净净; /tmp 的单次使用量不超过 70MB。