使用Python的"requests"库读取流式的HTTP响应

15

我正在尝试使用 requests 模块消费由 Kubernetes API 提供的事件流。我遇到了一个缓冲问题:似乎 requests 模块落后了一个事件。

我的代码大致如下:

r = requests.get('http://localhost:8080/api/v1beta1/watch/services',
                 stream=True)

for line in r.iter_lines():
    print 'LINE:', line

随着Kubernetes发出事件通知,当新事件到来时,此代码仅会显示最后一个发出的事件,这使得它对需要响应服务添加/删除事件的代码几乎完全无用。

我通过在子进程中生成curl而不是使用requests库来解决这个问题:

p = subprocess.Popen(['curl', '-sfN',
                      'http://localhost:8080/api/watch/services'],
                     stdout=subprocess.PIPE,
                     bufsize=1)

for line in iter(p.stdout.readline, b''):
    print 'LINE:', line

这个方法虽然可行,但会牺牲一些灵活性。是否有一种方法可以使用requests库避免这个缓冲问题呢?

1个回答

11

这种行为是由于requests库中iter_lines方法的错误实现导致的。

iter_lines使用iter_content迭代器以chunk_size块数据的形式迭代响应内容。如果从远程服务器读取的数据少于chunk_size字节(通常在读取输出的最后一行时会出现这种情况),则读取操作将阻塞,直到有chunk_size字节的数据可用。

我编写了自己的iter_lines例程,可以正确地处理这个问题:

import os


def iter_lines(fd, chunk_size=1024):
    '''Iterates over the content of a file-like object line-by-line.'''

    pending = None

    while True:
        chunk = os.read(fd.fileno(), chunk_size)
        if not chunk:
            break

        if pending is not None:
            chunk = pending + chunk
            pending = None

        lines = chunk.splitlines()

        if lines and lines[-1]:
            pending = lines.pop()

        for line in lines:
            yield line

    if pending:
        yield(pending)

这是因为 os.read 将返回少于 chunk_size 字节的数据,而不是等待缓冲区填满。


可以争论哪种实现是正确的 - 如果有更多数据可用,您的实现将插入一个虚假的“逻辑换行符”。正确的方法似乎是找出数据的总大小(指定一个是TCP通信的要求),并仅在已知结束处使用部分读取。 - ivan_pozdeev
我认为你不能说现有的实现是正确的。我的实现虽然没有经过严格的测试,但肯定更好用。一个更正确的实现——最好作为上游补丁提交——将非常有用。 - larsks
@ivan_pozdeev "正确的方法似乎是找出数据的总大小(指定一个是TCP通信的要求)" -- 不,TCP是一个流,并且可以具有无限长度。我不确定你从哪里听到的,但它在根本上是不正确的。 - Jonathon Reinhart
@JonathonReinhart,因此,任何值得信赖的TCP协议都要求发送方指定它将要发送的每个数据块的长度。这就是我所说的“要求”。 - ivan_pozdeev
@ivan_pozdeev “如果有更多的数据可用,您的代码将插入一个虚假的“逻辑换行符”” - 我不认为这是正确的。如果此方法未完全读取到换行符,则会将尾随内容缓冲在“pending”中,直到读取下一个块后才处理。我看不出这样做会产生引入响应中实际不存在的额外换行符的效果;您如何想到这是可能的呢? - Mark Amery
显示剩余3条评论

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