Parse multipart / form-данные с помощью cgi.FieldStorage; нет ключей

следующий фрагмент кода должен иметь возможность работать в Python 2.7 и Python 3.x.

from __future__ import unicode_literals
from __future__ import print_function

import cgi
try:
    from StringIO import StringIO as IO
except ImportError:
    from io import BytesIO as IO

body = """
--spam
Content-Disposition: form-data; name="param1"; filename=blob
Content-Type: binary/octet-stream

value1
--spam--
"""

parsed = cgi.FieldStorage(
    IO(body.encode('utf-8')),
    headers={'content-type': 'multipart/form-data; boundary=spam'},
    environ={'REQUEST_METHOD': 'POST'})

print([key for key in parsed])

в Python 2.7 работает нормально и выводит ['param1']. Однако в Python 3.4, он выводит [None].

Я не могу сделать FieldStorage чтобы получить полезный результат в Python 3. Я подозреваю, что что-то внутренне изменилось, и теперь я использую его неправильно. Однако я не могу понять, что именно. Любая помощь приветствуется.

2 ответов


эти изменения сделают ваш скрипт работать одинаково в обоих Python 2.7.x и 3.4.x:

(я буду использовать эти аббревиатуры для cgi.FieldStorage(): Python 2.7.x:FS27, Python 3.4.x:FS34)

1 - а FS27 обрабатывает новую строку перед границей правильно, это не относится к FS34 поэтому решение начать с границы(spam) напрямую.

body = """--spam
Content-Disposition: form-data; name="param1"; filename=blob
Content-type: binary/octet-stream

value1
--spam--
"""

2 - цитировать от cgi.py источник (in FS34 это комментарии к определению):

аргументы, все необязательные:

FP: указатель файла; по умолчанию: sys.стандартный ввод.буфер (не используется, когда метод запроса GET)

        Can be :
        1. a TextIOWrapper object
        2. an object whose read() and readline() methods return bytes

серая часть отсутствует в FS27 определение, так, большая часть различия между FS27 и FS34 лежать в обращении строки(FS27) и двоичные потоки(FS34).

в этой связи FS34 может легко испортить семантику анализируемого объекта, если ему не даны правильные указания о том, как правильно обращаться с этим. Видимо,headers запись в словаре 'content-type': 'multipart/form-data; boundary=spam' недостаточно; вы должны предоставить информацию о длине сообщения.

вы можете добейся этого,эффективно, добавив вторую запись в headers:

headers={'content-type': 'multipart/form-data; boundary=spam;',
'content-length': len(body)}

здесь стоимостью на content-length ключ - это body длина (включая границы начала/конца).


эти изменения, в сочетании, приводят к желаемому результату:

$ python script.py
['param1']
$ python3 script.py
['param1']

As доказательств -- принципиальной схемы, эти возвращенные parsed предметов FS27 и FS34:

...
print(parsed)
...

выходы:

FieldStorage(None, None, [FieldStorage('param1', 'blob', 'value1')])

на FS27 и

FieldStorage(None, None, [FieldStorage('param1', 'blob', b'value1')])

на FS34.


в Python 2.7 и Python 3.5 (по какой-то причине не работает в Python 3.4) желаемый результат возвращается путем добавления Content-Length в тело ответа:

body = """
--spam
Content-Disposition: form-data; name="param1"; filename=blob
Content-Length: 6
Content-Type: binary/octet-stream

value1
--spam--
"""