【问题标题】:Parse multipart request string in Python在 Python 中解析多部分请求字符串
【发布时间】:2018-11-28 05:18:51
【问题描述】:

我有一个这样的字符串

"--5b34210d81fb44c5a0fdc1a1e5ce42c3\r\nContent-Disposition: form-data; name=\"author\"\r\n\r\nJohn Smith\r\n--5b34210d81fb44c5a0fdc1a1e5ce42c3\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example2.txt\"\r\nContent-Type: text/plain\r\nExpires: 0\r\n\r\nHello World\r\n--5b34210d81fb44c5a0fdc1a1e5ce42c3--\r\n"

我在其他变量中也有可用的请求标头。

如何使用 Python3 轻松解析?

我正在通过 API Gateway 在 AWS Lambda 中处理文件上传,请求正文和标头可通过 Python dicts 获得。

stackoverflow 上还有其他类似的问题,但大多数都假设使用requests 模块或其他模块,并期望请求详细信息采用特定对象或格式。

注意:我知道有可能让用户上传到 S3 并触发 Lambda,但在这种情况下我有意选择不这样做。

【问题讨论】:

    标签: python aws-lambda aws-api-gateway


    【解决方案1】:

    它可以通过使用类似的东西来解析

    from requests_toolbelt.multipart import decoder
    multipart_string = "--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"author\"\r\n\r\nJohn Smith\r\n--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example2.txt\"\r\nContent-Type: text/plain\r\nExpires: 0\r\n\r\nHello World\r\n--ce560532019a77d83195f9e9873e16a1--\r\n"
    content_type = "multipart/form-data; boundary=ce560532019a77d83195f9e9873e16a1"
    decoder.MultipartDecoder(multipart_string, content_type)
    

    【讨论】:

    • 您应该会发现multipart/form-data 足以与content_type... 因为边界字符串不是您应该自己找到的,并且通常会因每条消息而异。跨度>
    • 感谢您的信息。似乎 MultipartDecoder 实际上可能需要标头中的边界来解析多部分字符串。我最终实现了它以使用正确的 mime 类型,这在 AWS Lambda 提供的其他变量中可用。
    【解决方案2】:

    如果你想使用 Python 的 CGI,

    from cgi import parse_multipart, parse_header
    from io import BytesIO
    
    c_type, c_data = parse_header(event['headers']['Content-Type'])
    assert c_type == 'multipart/form-data'
    decoded_string = base64.b64decode(event['body'])
    #For Python 3: these two lines of bugfixing are mandatory
    #see also: https://stackoverflow.com/questions/31486618/cgi-parse-multipart-function-throws-typeerror-in-python-3
    c_data['boundary'] = bytes(c_data['boundary'], "utf-8")
    c_data['CONTENT-LENGTH'] = event['headers']['Content-length']
    form_data = parse_multipart(BytesIO(decoded_string), c_data)
    
    for image_str in form_data['file']:
        ...
    

    【讨论】:

      【解决方案3】:

      扩展 sam-anthony 的答案(我必须对其进行一些修复才能在 python 3.6.8 上运行):

      from requests_toolbelt.multipart import decoder
      
      multipart_string = b"--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"author\"\r\n\r\nJohn Smith\r\n--ce560532019a77d83195f9e9873e16a1\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example2.txt\"\r\nContent-Type: text/plain\r\nExpires: 0\r\n\r\nHello World\r\n--ce560532019a77d83195f9e9873e16a1--\r\n"
      content_type = "multipart/form-data; boundary=ce560532019a77d83195f9e9873e16a1"
      
      for part in decoder.MultipartDecoder(multipart_string, content_type).parts:
        print(part.text)
      
      John Smith
      Hello World
      

      您需要做的是通过 pip install requests-toolbelt --target=. 安装此库,然后将其与您的 lambda 脚本一起上传

      这是一个工作示例:

      from requests_toolbelt.multipart import decoder
      
      def lambda_handler(event, context):
      
          content_type_header = event['headers']['Content-Type']
      
          body = event["body"].encode()
      
          response = ''
          for part in decoder.MultipartDecoder(body, content_type_header).parts:
            response += part.text + "\n"
      
          return {
              'statusCode': 200,
              'body': response
          }
      

      这应该足以识别您的依赖项。如果不是,请尝试使用 zip 中的“/python/lib/python3.6/site-packages”文件结构,并将您的 python 脚本放在根目录下”

      【讨论】:

        【解决方案4】:

        有一堆奇怪的编码问题和 api 网关的奇怪行为,最初以字节接收请求的主体,然后在重新部署后开始以 base64 接收它们。无论如何,这是最终为我工作的代码。

        import json
        import base64
        import boto3
        from requests_toolbelt.multipart import decoder
        
        s3client = boto3.client("s3")
        def lambda_handler(event, context):
            content_type_header = event['headers']['content-type']
            postdata = base64.b64decode(event['body']).decode('iso-8859-1')
            imgInput = ''
            lst = []
            for part in decoder.MultipartDecoder(postdata.encode('utf-8'), content_type_header).parts:
                lst.append(part.text)
            response = s3client.put_object(  Body=lst[0].encode('iso-8859-1'),  Bucket='test',    Key='mypicturefinal.jpg')
            return {'statusCode': '200','body': 'Success', 'headers': { 'Content-Type': 'text/html' }}
        

        【讨论】:

          【解决方案5】:

          如果使用 CGI,我建议使用 FieldStorage:

          from cgi import FieldStorage
          
          fs = FieldStorage(fp=event['body'], headers=event['headers'], environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':event['headers']['Content-Type'], })['file']
          originalFileName = fs.filename
          binaryFileData = fs.file.read()
          

          另见: https://stackoverflow.com/a/38718958/10913265

          如果事件正文包含多个文件:

          fs = FieldStorage(fp=event['body'], headers=event['headers'], environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':event['headers']['Content-Type'], })['file']
          

          提供 FieldStorage 对象的列表。所以你可以这样做:

          for f in fs:
              originalFileName = f.filename
              binaryFileData = f.file.read()
          

          总的来说,我处理单个文件多个文件以及包含无文件的正文的解决方案并确保它是 mutlipart/form-data

          from cgi import parse_header, FieldStorage
          
          #see also: https://stackoverflow.com/a/56405982/10913265
          c_type, c_data = parse_header(event['headers']['Content-Type'])
          assert c_type == 'multipart/form-data'
          
          #see also: https://stackoverflow.com/a/38718958/10913265
          fs = FieldStorage(fp=event['body'], headers=event['headers'], environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':event['headers']['Content-Type'], })['file']
          
          #If fs contains a single file or no file: making FieldStorage object to a list, so it gets iterable
          if not(type(fs) == list):
              fs = [fs]
          
          for f in fs:
              originalFileName = f.filename
              #no file: 
              if originalFileName == '':
                  continue
              binaryFileData = f.file.read()
              #Do something with the data 
          

          【讨论】:

          • 这返回了TypeError: fp must be file pointer Traceback (most recent call last)
          猜你喜欢
          • 2011-05-24
          • 1970-01-01
          • 2016-01-29
          • 1970-01-01
          • 2015-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-20
          相关资源
          最近更新 更多