我想通过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
将显示原始字节。
答案 0 :(得分:0)
zlib流需要由最后一个块和完整性检查终止。最后一个块由块开始处设置的“最后一位”表示。在这种情况下,由于倒数第二个块是一个存储块,将您置于字节边界,最后一个块可以是01 00 00 ff ff。
然后,您需要在未压缩数据上检查Adler-32,作为流的最后四个字节,在最后一个块之后。
答案 1 :(得分:0)
问题实际上不是DEFLATE,zlib或任何实际的框架,而是使用EventSource
客户端。尴尬。
Server-Sent Events specification在EventSource
对象,open
,error
和message
上定义了三个事件。 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的任何好处。