1
- import { PassThrough , pipeline , Readable , Transform } from 'node:stream' ;
2
1
import type Negotiator from 'negotiator' ;
2
+ import { PassThrough , pipeline , Readable , Transform } from 'node:stream' ;
3
3
import * as zlib from 'node:zlib' ;
4
4
import { RequestTracker } from '../sync/RequestTracker.js' ;
5
5
@@ -19,54 +19,51 @@ export function maybeCompressResponseStream(
19
19
tracker : RequestTracker
20
20
) : { stream : Readable ; encodingHeaders : { 'content-encoding' ?: string } } {
21
21
const encoding = ( negotiator as any ) . encoding ( [ 'identity' , 'gzip' , 'zstd' ] , { preferred : 'zstd' } ) ;
22
- if ( encoding == 'zstd' ) {
23
- tracker . setCompressed ( encoding ) ;
24
- return {
25
- stream : transform (
26
- stream ,
27
- // Available since Node v23.8.0, v22.15.0
28
- // This does the actual compression in a background thread pool.
29
- zlib . createZstdCompress ( {
30
- // We need to flush the frame after every new input chunk, to avoid delaying data
31
- // in the output stream.
32
- flush : zlib . constants . ZSTD_e_flush ,
33
- params : {
34
- // Default compression level is 3. We reduce this slightly to limit CPU overhead
35
- [ zlib . constants . ZSTD_c_compressionLevel ] : 2
36
- }
37
- } ) ,
38
- tracker
39
- ) ,
40
- encodingHeaders : { 'content-encoding' : 'zstd' }
41
- } ;
42
- } else if ( encoding == 'gzip' ) {
43
- tracker . setCompressed ( encoding ) ;
22
+ const transform = createCompressionTransform ( encoding ) ;
23
+ if ( transform == null ) {
24
+ // No matching compression supported - leave stream as-is
44
25
return {
45
- stream : transform (
46
- stream ,
47
- zlib . createGzip ( {
48
- // We need to flush the frame after every new input chunk, to avoid delaying data
49
- // in the output stream.
50
- flush : zlib . constants . Z_SYNC_FLUSH
51
- } ) ,
52
- tracker
53
- ) ,
54
- encodingHeaders : { 'content-encoding' : 'gzip' }
26
+ stream,
27
+ encodingHeaders : { }
55
28
} ;
56
29
} else {
30
+ tracker . setCompressed ( encoding ) ;
57
31
return {
58
- stream : stream ,
59
- encodingHeaders : { }
32
+ stream : transformStream ( stream , transform , tracker ) ,
33
+ encodingHeaders : { 'content-encoding' : encoding }
60
34
} ;
61
35
}
62
36
}
63
37
64
- function transform ( source : Readable , transform : Transform , tracker : RequestTracker ) {
38
+ function createCompressionTransform ( encoding : string | undefined ) : Transform | null {
39
+ if ( encoding == 'zstd' ) {
40
+ // Available since Node v23.8.0, v22.15.0
41
+ // This does the actual compression in a background thread pool.
42
+ return zlib . createZstdCompress ( {
43
+ // We need to flush the frame after every new input chunk, to avoid delaying data
44
+ // in the output stream.
45
+ flush : zlib . constants . ZSTD_e_flush ,
46
+ params : {
47
+ // Default compression level is 3. We reduce this slightly to limit CPU overhead
48
+ [ zlib . constants . ZSTD_c_compressionLevel ] : 2
49
+ }
50
+ } ) ;
51
+ } else if ( encoding == 'gzip' ) {
52
+ return zlib . createGzip ( {
53
+ // We need to flush the frame after every new input chunk, to avoid delaying data
54
+ // in the output stream.
55
+ flush : zlib . constants . Z_SYNC_FLUSH
56
+ } ) ;
57
+ }
58
+ return null ;
59
+ }
60
+
61
+ function transformStream ( source : Readable , transform : Transform , tracker : RequestTracker ) {
65
62
// pipe does not forward error events automatically, resulting in unhandled error
66
63
// events. This forwards it.
67
64
const out = new PassThrough ( ) ;
68
65
const trackingTransform = new Transform ( {
69
- transform ( chunk , encoding , callback ) {
66
+ transform ( chunk , _encoding , callback ) {
70
67
tracker . addCompressedDataSent ( chunk . length ) ;
71
68
callback ( null , chunk ) ;
72
69
}
0 commit comments