我能用Python的requests库发送POST请求并使用http-gzip或deflate压缩吗?

21

我使用Python 2.7的request模块将大块数据发送到一个我无法更改的服务。由于数据主要是文本,因此虽然它很大但可以压缩得很好。服务器会接受gzip-或deflate-编码,但是我不知道如何指示请求自动进行POST并正确地对数据进行编码。

是否有一个最简化的示例可用,展示如何实现这一点?


看起来似乎不可能,你看过这个这个吗? - Paul Mougel
不,但这并不重要,因为我对它是否可能本身并不感兴趣(它是可能的),而是对于使用Python的“request”模块是否可行。 - AME
1
你能否提供一个最简单的示例,展示你现在是如何做的,不使用压缩?我特别想知道你是否在requests.post()调用中使用了data= - Robᵩ
requests.post(url, params=params_dict, data=json_string, headers=headers_dict) - AME
6个回答

21
# 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)

1
请问您是否知道为什么这个AWS API网关无法正常工作。我可以使用您推荐的更改在本地测试flask应用程序,但是当我将其部署到Lambda时,API网关在到达应用程序之前会返回HTTP 415错误。看起来您的解决方案与AWS的说明完全对称:https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-make-request-with-compressed-payload.html - Scott Smith
1
@ScottSmith 我已经在AWS API Gateway上使其工作,但我不得不使用Python的gzip库而不是zlib。因此,我设置了payload = gzip.compress(json.dumps(payload).encode('utf-8'))并设置了标题:Content-Type=application/jsonContent-Encoding=gzip - tobycoleman
这段代码在Python3上失败了,看起来缺少UTF-8编码。 - Jean Carlo Machado

14

我已经测试了Robᵩ提出的解决方案并进行了一些修改,它确实有效。

伪代码(抱歉,我从我的代码中推断了它,因此我不得不裁剪掉一些部分,并且没有进行测试,但是您可以获得您的想法)

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再发送。 - aaa90210

3

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

最初的回答:

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()

3

For 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)


2
可以使用以下一行代码来简化压缩过程:zipped_payload = gzip.compress("Hello world".encode('utf-8')) - illagrenan

2

由于头文件不正确或缺失,所以被接受的答案可能是错误的:

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到15)之间,因此16+zlib.MAX_WBITS是一个不错的选择。
我检查了urllib3如何解码这两种情况的响应,并为deflate实现了尝试和错误机制(它尝试使用原始和zlib报头格式)。这可能可以解释为什么一些人对接受答案的解决方案有问题,而其他人则没有。

tl;dr

gzip

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

deflate

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

这应该是被接受的答案。(例如,influxdb不支持deflate压缩) - undefined

1
我无法让它正常工作,但你可以尝试将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)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接