在Server-Sent Events (SSE)中使用gzip压缩是否可行?

12

我想知道是否可以为服务器发送事件(SSE;Content-Type: text/event-stream)启用gzip压缩。

根据这本书,似乎是可能的: http://chimera.labs.oreilly.com/books/1230000000545/ch16.html

但我找不到任何使用gzip压缩的SSE示例。我尝试发送带有响应头字段 Content-Encoding设置为"gzip"的压缩消息,但没有成功。

为了测试SSE,我正在使用Python编写的小型Web应用程序进行实验,使用瓶装框架+bottle WSGI服务器。

@bottle.get('/data_stream')
def stream_data():
    bottle.response.content_type = "text/event-stream"
    bottle.response.add_header("Connection", "keep-alive")
    bottle.response.add_header("Cache-Control", "no-cache")
    bottle.response.add_header("Content-Encoding", "gzip")
    while True:
        # new_data is a gevent AsyncResult object,
        # .get() just returns a data string when new
        # data is available
        data = new_data.get()
        yield zlib.compress("data: %s\n\n" % data)
        #yield "data: %s\n\n" % data

没有压缩的代码(最后一行已注释)而且没有gzip content-encoding头字段,工作得非常好。

编辑:感谢回复和这个问题:Python: Creating a streaming gzip'd file-like?,我成功解决了问题:

@bottle.route("/stream")
def stream_data():
    compressed_stream = zlib.compressobj()
    bottle.response.content_type = "text/event-stream"
    bottle.response.add_header("Connection", "keep-alive")
    bottle.response.add_header("Cache-Control", "no-cache, must-revalidate")
    bottle.response.add_header("Content-Encoding", "deflate")
    bottle.response.add_header("Transfer-Encoding", "chunked")
    while True:
        data = new_data.get()
        yield compressed_stream.compress("data: %s\n\n" % data)
        yield compressed_stream.flush(zlib.Z_SYNC_FLUSH)

是的,这是可能的;浏览器支持这个功能;但框架需要足够聪明 -- 你需要在每个事件(或者一段时间后)之后刷新压缩代码块,以确保及时地传递事件。 - Dima Tisnek
2个回答

7

简而言之:如果请求没有被缓存,您可能需要使用zlib并声明Content-Encoding为“deflate”。仅做出这种更改即可使您的代码正常工作。


如果您声明Content-Encoding为gzip,则需要实际使用gzip。它们基于相同的压缩算法,但gzip具有一些额外的框架。例如:

import gzip
import StringIO
from bottle import response, route
@route('/')
def get_data():
    response.add_header("Content-Encoding", "gzip")
    s = StringIO.StringIO()
    with gzip.GzipFile(fileobj=s, mode='w') as f:
        f.write('Hello World')
    return s.getvalue()

但只有使用实际文件作为缓存时才有意义。


感谢您的解释。确实,将Content-Encoding更改为deflate有所帮助:第一条消息在客户端上被处理。但只有第一个 :( 您有任何想法吗?提前致谢。 - mguijarr
你是在尝试为每个数据块独立调用压缩吗?我认为这样做不会起作用。所有数据应该在一个单一的压缩流中。这意味着gzip与其流接口可能实际上是正确的选择。但是,我需要看到更多你的代码才能提供具体的指针。 - otus
非常感谢!终于成功了!我编辑了我的问题,说明了我所做的更改。 - mguijarr
NP。我忘了zlib对象可以刷新,这是一个非常有用的模式需要记住。 - otus
如果不使用Bottle,而是使用Flask-SSE,这个怎么实现呢?我似乎无法压缩流数据。 - Source Matters

2

还有一种中间件可以使用,这样您就不需要为每个方法压缩响应而担心。这是我最近使用的一个。

https://code.google.com/p/ibkon-wsgi-gzip-middleware/

这是我如何使用它的(我正在使用带有gevent服务器的bottle.py)

from gzip_middleware import Gzipper
import bottle
app = Gzipper(bottle.app())
run(app = app, host='0.0.0.0', port=8080, server='gevent')

对于这个特定的库,您可以通过修改DEFAULT_COMPRESSABLES变量来设置想要压缩的响应类型,例如:

DEFAULT_COMPRESSABLES = set(['text/plain', 'text/html', 'text/css',
'application/json', 'application/x-javascript', 'text/xml',
'application/xml', 'application/xml+rss', 'text/javascript',     
'image/gif'])

所有响应均经过中间件处理并进行gzip压缩,而不会修改您现有的代码。默认情况下,它会压缩内容类型属于DEFAULT_COMPRESSABLES且内容长度大于200个字符的响应。

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