【问题标题】:Can I POST data with python requests lib with http-gzip or deflate compression?我可以使用带有 http-gzip 或 deflate 压缩的 python 请求库发布数据吗?
【发布时间】:2013-12-23 22:08:23
【问题描述】:

我使用 python 2.7 的请求模块将更大的数据块发布到我无法更改的服务。由于数据主要是文本,它很大但压缩得很好。服务器将接受 gzip 或 deflate 编码,但是我不知道如何指示请求执行 POST 并自动正确编码数据。

是否有一个可用的最小示例来说明这是如何实现的?

【问题讨论】:

  • 看起来不可能,你看过this和this吗?
  • 不,但这无关紧要,因为我不感兴趣它本身是否可能(确实如此),但如果它是可能的 python 的“请求”模块。
  • 您能否发布一个小例子来说明您现在是如何做到的,没有压缩?我特别想知道您是否在requests.post() 通话中使用data=。
  • requests.post(url, params=params_dict, data=json_string, headers=headers_dict)

标签: python http gzip deflate


【解决方案1】:
# Works if backend supports gzip

additional_headers['content-encoding'] = 'gzip'
request_body = zlib.compress(json.dumps(post_data))
r = requests.post('http://post.example.url', data=request_body, headers=additional_headers)

【讨论】:

  • 想知道为什么这不适用于 AWS API Gateway。我可以使用您推荐的更改在本地测试烧瓶应用程序,但是当我将其部署到 Lambda 时,API 网关会在到达应用程序之前提供 HTTP 415。似乎您的解决方案与 AWS 的说明完全对称:docs.aws.amazon.com/apigateway/latest/developerguide/…
  • @ScottSmith 我让它在 AWS API Gateway 上工作,但我必须使用 Python 的 gzip 库而不是 zlib。所以我设置了payload = gzip.compress(json.dumps(payload).encode('utf-8')) 并设置了标题:Content-Type=application/json 和Content-Encoding=gzip
  • 此代码在 python3 上失败,看起来缺少 utf-8 编码。
【解决方案2】:

我已经测试了 Robᵩ 提出的解决方案并进行了一些修改,并且它可以工作。

PSEUDOCODE(对不起,我是从我的代码中推断出来的,所以我不得不删掉一些部分并且没有测试,无论如何你可以得到你的想法)

additional_headers['content-encoding'] = 'gzip'
s = StringIO.StringIO()
g = gzip.GzipFile(fileobj=s, mode='w')
g.write(json_body)
g.close()
gzipped_body = s.getvalue()
request_body = gzipped_body

r = requests.post(endpoint_url, data=request_body, headers=additional_headers)

【讨论】:

  • 如果你可以让 gzip 写入套接字会更好,而不是写入 StringIO 然后发送它。
【解决方案3】:

我无法让它工作,但您可以将 gzip 数据插入到准备好的请求中:

#UNPROVEN
r=requests.Request('POST', 'http://httpbin.org/post', data={"hello":"goodbye"})
p=r.prepare()
s=StringIO.StringIO()
g=gzip.GzipFile(fileobj=s,mode='w')
g.write(p.body)
g.close()
p.body=s.getvalue()
p.headers['content-encoding']='gzip'
p.headers['content-length'] = str(len(p.body))  # Not sure about this
r=requests.Session().send(p)

【讨论】:

    【解决方案4】:

    对于python 3:

    from io import BytesIO
    import gzip
    
    def zip_payload(payload: str) -> bytes:
        btsio = BytesIO()
        g = gzip.GzipFile(fileobj=btsio, mode='w')
        g.write(bytes(payload, 'utf8'))
        g.close()
        return btsio.getvalue()
    
    headers = {
        'Content-Encoding': 'gzip'
    }
    zipped_payload = zip_payload(payload)
    requests.post(url, zipped_payload, headers=headers)
    
    

    【讨论】:

    • 使用这种单行代码可以简化压缩:zipped_payload = gzip.compress("Hello world".encode('utf-8'))。
    【解决方案5】:

    我需要将我的帖子分块,因为我有几个非常大的文件要并行上传。这是我想出的解决方案。

    import requests
    import zlib
    
    """Generator that reads a file in chunks and compresses them"""
    def chunked_read_and_compress(file_to_send, zlib_obj, chunk_size):
        compression_incomplete = True
        with open(file_to_send,'rb') as f:
            # The zlib might not give us any data back, so we have nothing to yield, just
            # run another loop until we get data to yield.
            while compression_incomplete:
                plain_data = f.read(chunk_size)
                if plain_data:
                    compressed_data = zlib_obj.compress(plain_data)
                else:
                    compressed_data = zlib_obj.flush()
                    compression_incomplete = False
                if compressed_data:
                    yield compressed_data
    
    """Post a file to a url that is content-encoded gzipped compressed and chunked (for large files)"""
    def post_file_gzipped(url, file_to_send, chunk_size=5*1024*1024, compress_level=6, headers={}, requests_kwargs={}):
        headers_to_send = {'Content-Encoding': 'gzip'}
        headers_to_send.update(headers)
        zlib_obj = zlib.compressobj(compress_level, zlib.DEFLATED, 31)
        return requests.post(url, data=chunked_read_and_compress(file_to_send, zlib_obj, chunk_size), headers=headers_to_send, **requests_kwargs)
    
    resp = post_file_gzipped('http://httpbin.org/post', 'somefile')
    resp.raise_for_status()
    

    【讨论】:

      【解决方案6】:

      由于标题不正确或缺失,接受的答案可能是错误的:

      additional_headers['content-encoding'] = 'gzip'
      request_body = zlib.compress(json.dumps(post_data))
      

      使用 zlib 模块的 compressobj 方法提供 wbits 参数来指定标头格式应该可以工作。 默认值为MAX_WBITS=15,表示zlib 标头格式。这对于Content-Encoding: deflate 是正确的。 对于compress 方法,此参数不可用,并且文档没有提及不幸的是使用了哪个标头(如果有)。

      对于Content-Encoding: gzip wbits 应该介于16 + (9 to 15) 之间,所以16+zlib.MAX_WBITS 将是一个不错的选择。

      我检查了urllib3 如何对这两种情况的响应进行解码,并为 deflate 实现了试错机制(它尝试原始和 zlib 标头格式)。这可以解释为什么有些人对接受的答案的解决方案有问题,而其他人没有。


      tl;博士

      压缩包

      additional_headers['Content-Encoding'] = 'gzip'
      compress = zlib.compressobj(wbits=16+zlib.MAX_WBITS)
      body = compress.compress(data) + compress.flush()
      

      放气

      additional_headers['Content-Encoding'] = 'deflate'
      compress = zlib.compressobj()
      body = compress.compress(data) + compress.flush()
      

      【讨论】:

        猜你喜欢
        • 2010-11-29
        • 1970-01-01
        • 2011-06-11
        • 2013-11-11
        • 1970-01-01
        • 1970-01-01
        • 2013-05-05
        • 2011-03-20
        相关资源
        最近更新 更多