2014-02-10 4 views
0

HTTP를 통해 일부 스트리밍 데이터를 제공하려고합니다. 또한 대역폭을 절약하기 위해 데이터를 압축하고 싶습니다. 필자는 개별적으로 각 스트리밍 응답을 개별적으로 압축 할 수 있지만 필자의 경우 모든 단일 클라이언트 (팬 아웃)에 대해 데이터 스트림이 거의 동일하므로 각 연결에 대해 동일한 데이터를 압축하는 데 CPU 시간이 낭비되는 것처럼 보입니다. 내 계획은 스트리밍 데이터의 각 청크를 선점 적으로 압축하는 것이므로 언제든지 연결하는 클라이언트는 다음 청크 읽기를 시작할 수 있습니다 (압축 효율은 낮아 지지만 개별 데이터 청크가 충분히 크면이 괜찮을거야).스트리밍 HTTP 클라이언트간에 DEFLATE 압축 데이터 청크를 교환하십시오.

호환 HTTP 클라이언트는 분명히 Content-Encoding: gzip 응답 인 but the answers to this question indicate web browsers do not의 여러 gzip 파일을 허용 할 수 있습니다. 그러나 DEFLATE/zlib을 이해하면 Z_FULL_FLUSH0x0000FFFF 바이트를 보내 개별적으로 압축 할 수없는 청크와 동일한 효과를 제공하는 스트림을 재설정 할 수 있습니다.

node.js에 서버 보낸 이벤트 스트림으로 메시지를 스트리밍하는 간단한 POC를 설정했지만 웹 브라우저에서 데이터를 읽을 수 없습니다. 연결은 열리지 만 데이터는 플러시되지 않습니다. 단순화를 위해 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); 

그리고 간단한 클라이언트 :

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 압축 해제에 대한 추가 프레임을 추가해야합니까 :

<!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:8080xxd을 통해 파이프), 다음과 같이 플러시 포인트를 감지 했습니까?

EDIT : http 스트림을 유효한 DEFLATE 스트림으로 만들기 위해 zlib magic number을 추가했지만 웹 브라우저는 여전히 블록을 플러시하지 않습니다. 그러나 zlib.createInflate()을 통해 gzip 스트림을 다시 파이핑하면 매직 번호가 추가되면 올바르게 작동합니다. 또한 curl -N localhost:8080은 원시 바이트를 표시하기 때문에 http 스트림이 버퍼링되지 않는다는 것도 알고 있습니다.

답변

0

실제로 문제가 DEFLATE, zlib 또는 실제 프레이밍이 아닌 EventSource 클라이언트입니다. 당혹 스럽네.

Server-Sent Events specification는 세 EventSource 객체의 이벤트, open, errormessage을 정의합니다. message은 스트림에서 방출 된 모든 메시지를 시작합니다. addEventListener으로 구독 한 다른 모든 이벤트는 event 구문을 사용하여 서버가 지정한 이벤트 유형별로 필터링 할 수 있습니다.이 라인을 변경 즉

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

, client.html에 :

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

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

하려면 웹 브라우저가 제대로 서버와의 모든 "안녕하세요"메시지를 기록하게됩니다 그들은 플러시된다.

Re : 압축 : 질문의 코드는 유효한 DEFLATE 스트림을 생성하므로 모든 부분에서 유용합니다.

헤더 변경 사항이있는 gzip 스트림에도 적용 할 수 있다고 생각하지만, DEFLATE에 대한 gzip의 유일한 실제 변경 사항은 Mark Adler의 대답에서 언급 한 것처럼 스트림 끝의 무결성 검사입니다. 필자의 경우에는 스트림이 끝나지 않았으므로 체크섬을 보낼 수는 없으므로 gzip의 이점은 무시됩니다.

0

zlib 스트림은 마지막 블록과 무결성 검사로 종료되어야합니다. 마지막 블록은 블록의 시작 부분에 설정된 "마지막 비트"로 표시됩니다. 이 경우 마지막 블록 다음에 오는 블록이 저장된 블록이므로 바이트 경계에 놓으면 마지막 블록은 01 00 00 ff ff가 될 수 있습니다.

그런 다음 마지막 블록 다음에 스트림의 마지막 4 바이트로 압축되지 않은 데이터를 Adler-32 확인해야합니다.

+0

"마지막 블록"비트와 무결성 검사는 어떻게'Z_FULL_FLUSH'와 내 제한없는 스트림에서 재생됩니까? 모든 데이터 덩어리마다 마지막 블록 + 검사가 필요합니까? 압축 해제 기가 다른 블록의 무결성 검사를 넘어 읽을 것입니까? 내가 올바르게 당신을 이해한다면, 내 bytestream은 (마술) (저장 블록) * (01 00 00 ff ff) (Adler-32)) *'처럼 보일 것입니다. – blendmaster

+0

마지막 블록과 체크는 끝 부분에 있습니다. 따라서 당신의 표기법에서, (마법) (풀 플러시 블록) * (01 00 00 ff ff) (애들러 -32)'. 데이터가있는 마지막 블록이 바이트 경계에서 끝나는 지 확인하려면 전체 플러시가 필요합니다. –

+0

준비된 각 블록에 대해 Adler-32 검사를 미리 계산할 수 있으며 'adler32_combine()'을 사용하여 전체 스트림을 빌드 할 때 결합 할 수 있습니다. –