如何使用Django流式传输HttpResponse

63

我正在尝试使用生成器和 yield 函数在 Django (1.2) 中实现流式响应的 "hello world"。但是响应仍然没有流式传输。我怀疑有一个中间件在干扰它——也许是 ETAG 计算器?但我不确定如何禁用它。能否有人帮忙吗?

这是目前我拥有的流式传输的 “hello world”:

def stream_response(request):
    resp = HttpResponse( stream_response_generator())
    return resp

def stream_response_generator():
    for x in range(1,11):
        yield "%s\n" % x  # Returns a chunk of the response to the browser
        time.sleep(1)

1
@Tomasz:WSGI协议规范http://www.python.org/dev/peps/pep-0333/ - Van Gale
2个回答

52
您可以使用条件装饰器禁用ETAG中间件。这将使您的响应通过HTTP流回。您可以使用像curl这样的命令行工具确认此操作。但是,这可能不足以使您的浏览器显示正在流式传输的响应。为了鼓励浏览器在流式传输时显示响应,您可以推送一堆空格到管道中以强制其缓冲区填充。示例如下:
from django.views.decorators.http import condition

@condition(etag_func=None)
def stream_response(request):
    resp = HttpResponse( stream_response_generator(), content_type='text/html')
    return resp

def stream_response_generator():
    yield "<html><body>\n"
    for x in range(1,11):
        yield "<div>%s</div>\n" % x
        yield " " * 1024  # Encourage browser to render incrementally
        time.sleep(1)
    yield "</body></html>\n"

5
在我的测试中,Django的GZipMiddleware可以防止它起作用。 - Xealot
1
是的,我预计许多中间件可能会干扰它,所以如果它不起作用,请尝试禁用所有中间件并逐步重新启用它们。GZip需要在压缩整个响应之前拥有整个响应,因此它不允许您进行流式传输。 - Leopd
1
@Xealot:我在GzipMiddleware方面也遇到了类似的问题。已经提交了一个错误报告,因为该中间件不支持生成器(它会意外清除生成器):Django ticket #15066 - AndiDog
1
我通过向StreamingHttpResponse插入一个伪标头来禁用了此调用中的GZipMiddleware: response['Content-Encoding'] = 'identity'。这可能在18种不同的方式上都是无效的HTTP,但在我的情况下,这只是一个内部端点,所以对我来说已经足够了。 - Henrik Heimbuerger
也许更好的方法是对其进行子类化,并自定义以忽略所有带有 X-Streaming 标头或类似标头的响应。 - skywalker

44
很多 Django 中间件都会阻止你进行内容流式传输。如果你想使用 Django 管理应用,大部分中间件都需要启用,这可能会很烦人。幸运的是,在 django 1.5 release 中解决了这个问题。你可以使用 StreamingHttpResponse 来指示你想要流式传输结果,Django 随附的所有中间件都知道这一点,并相应地采取措施,不缓存您的内容输出,而是直接将其发送到终端。然后,你的代码将会像以下代码一样使用新的 StreamingHttpResponse 对象。
def stream_response(request):
    return StreamingHttpResponse(stream_response_generator())

def stream_response_generator():
    for x in range(1,11):
        yield "%s\n" % x  # Returns a chunk of the response to the browser
        time.sleep(1)

关于Apache的注意事项

我在Ubuntu 13.04上使用Apache 2.2进行了测试。我测试时启用了默认设置中的Apache模块mod_deflate,该模块会缓冲您尝试流式传输的内容,直到达到某个块大小,然后压缩内容并将其发送到浏览器。这将阻止上述示例按预期工作。避免这种情况的一种方法是通过在Apache配置中添加以下行来禁用mod_deflate:

SetEnvIf Request_URI ^/mysite no-gzip=1

这个问题在如何在apache2中禁用mod_deflate?问题中有更详细的讨论。


它可以工作!但是我应该如何将其实时渲染到模板中呢?目前我正在使用带有ajax调用的Javascript.. 但是它只在处理完成后才会发布输出.. 所以在将其渲染到浏览器时并不是“流式传输”。任何帮助将不胜感激.. 谢谢 :) - First Blood
@FirstBlood 通过使用Ajax,您可以在流式传输时获取回调,而不仅仅是在下载完成时。请查看此答案https://dev59.com/xW865IYBdhLWcg3wQMWW#4488132以显示Ajax调用的进度。 - Marwan Alsabbagh
谢谢。我想要实现在浏览器上实时打印Ping输出状态。例如:ping -c 3 www.google.com,命令提示符会给出输出结果。在Django中是否可能通过subprocess将输出结果传输到浏览器?我尝试使用StreamingHttpResponse,但似乎无法实时流式传输。我甚至尝试设置headers: Keep-Alive,但没有效果 :( - First Blood
你的Web服务器(例如Apache)可能也会缓冲输出,如果进程运行时间不长或输出不多,则可能会导致Web服务器将其全部缓冲,然后一次性传递。 - Anentropic
@Anentropic 那是真的。我已经更新了答案,并附上了有关Apache的注释。 - Marwan Alsabbagh

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