在流式HTTP客户端之间共享DEFLATE压缩的数据块

时间:2014-02-10 01:20:13

标签: node.js http zlib server-sent-events deflate

我想通过HTTP提供一些流数据。另外,我想压缩数据以节省带宽。我可以单独压缩每个流响应,但在我的情况下,每个客户端(扇出)的数据流大致相同,因此为每个连接压缩相同数据似乎浪费了CPU时间。我的计划是抢先压缩每一块流数据,因此任何时候连接的客户都可以开始读取下一个块(这是以降低压缩效率为代价的,但只要单个数据块足够大,这个应该没问题。

合规HTTP客户端显然应该能够在Content-Encoding: gzip响应but the answers to this question indicate web browsers do not中接受多个gzip压缩文件。但是,根据我对DEFLATE / zlib的理解,您可以发送Z_FULL_FLUSH 0x0000FFFF个字节来重置流,该流应该具有与单个可解压缩块相同的效果。

我在node.js中设置了一个简单的POC,它将消息作为服务器发送事件流进行流式传输,但我无法通过Web浏览器读取数据。它将打开连接,但永远不会刷新数据。为简单起见,我使用Z_NO_COMPRESSION

var http, zlib, gzip, numClients;
http = require('http');
zlib = require('zlib');
gzip = zlib.createDeflateRaw({
  flush: zlib.Z_SYNC_FLUSH,
  level: zlib.Z_NO_COMPRESSION
});
numClients = 0;
setInterval(function(){
  if (numClients > 0) {
    gzip.write("data: hi\n\n");
  }
}, 1000);
http.createServer(function(req, res){
  res.socket.setTimeout(Infinity);
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Content-Encoding': 'deflate',
    'Transfer-Encoding': 'identity',
    'Access-Control-Allow-Origin': '*'
  });

  res.write('\x78\x01'); // write zlib magic number
  gzip.pipe(res);
  numClients++;
  res.on('close', function(){
    numClients--;
    gzip.unpipe(res);
  });
  res.on('error', function(){
    numClients--;
    gzip.unpipe(res);
  });
}).listen(8080);
numClients++;
gzip.pipe(process.stdout);

一个简单的客户:

<!DOCTYPE html5>
<html lang=en>
<meta charset=utf-8>
<title>hi</title>

<script>
var es = new EventSource("http://localhost:8080/");
es.addEventListener('data', console.log);
es.addEventListener('open', console.log);
es.addEventListener('error', console.log);
</script>

字节看起来像这样,(curl -N localhost:8080管道通过xxd):

0000000: 7801 000a 00f5 ff64 6174 613a 2068 690a  x......data: hi.
0000010: 0a00 0000 ffff 000a 00f5 ff64 6174 613a  ...........data:
0000020: 2068 690a 0a00 0000 ffff 000a 00f5 ff64   hi............d
0000030: 6174 613a 2068 690a 0a00 0000 ffff 000a  ata: hi.........
0000040: 00f5 ff64 6174 613a 2068 690a 0a00 0000  ...data: hi.....
0000050: ffff 000a 00f5 ff64 6174 613a 2068 690a  .......data: hi.
0000060: 0a00 0000 ffff 000a 00f5 ff64 6174 613a  ...........data:

我是否需要为DEFLATE解压缩程序添加额外的框架来检测刷新点?

编辑:我添加了zlib magic number以使http流成为有效的DEFLATE流,但是 Web浏览器仍然不会刷新块。但是,如果添加了幻数,则将gzip流回流到zlib.createInflate()可以正常工作。我也知道http流没有被缓冲,因为curl -N localhost:8080将显示原始字节。

2 个答案:

答案 0 :(得分:0)

zlib流需要由最后一个块和完整性检查终止。最后一个块由块开始处设置的“最后一位”表示。在这种情况下,由于倒数第二个块是一个存储块,将您置于字节边界,最后一个块可以是01 00 00 ff ff。

然后,您需要在未压缩数据上检查Adler-32,作为流的最后四个字节,在最后一个块之后。

答案 1 :(得分:0)

问题实际上不是DEFLATE,zlib或任何实际的框架,而是使用EventSource客户端。尴尬。

Server-Sent Events specificationEventSource对象,openerrormessage上定义了三个事件。 message将触发流上发出的所有消息。订阅addEventListener的任何其他事件都可以使用event语法作为服务器指定事件类型进行过滤的糖,例如

event: <event-name>
data: ...

换句话说,在client.html更改此行:

es.addEventListener('data', console.log);

es.addEventListener('message', console.log);

在刷新时,将导致Web浏览器正确记录来自服务器的所有“hi”消息。

Re:压缩,问题中的代码确实生成了一个有效的DEFLATE流,所以这部分都很好。

我认为它也可以适应gzip流,但有一些标题更改,但gzip与DEFLATE的唯一真正变化是流末尾的完整性检查,如Mark Adler的回答中所述。在我的情况下,我没有流结束,所以无论如何我永远不会发送校验和,否定gzip的任何好处。