@@ -20,7 +20,13 @@ const querystring_parse = require('querystring').decode;
2020const request_ip_get = require ( 'ipware' ) ( ) . get_ip ;
2121
2222// constants
23+ const COMPRESS_METHOD_NONE = 0 ;
24+ const COMPRESS_METHOD_GZIP = 1 ;
25+ const COMPRESS_METHOD_BROTLI = 2 ;
26+ const COMPRESS_METHOD_ZSTD = 3 ;
2327const GZIP_OPTIONS = { level : 9 } ;
28+ const HAS_BROTLI = zlib . createBrotliCompress != null ;
29+ const HAS_ZSTD = zlib . createZstdCompress != null ;
2430const HTTP_LIST_REG = / , \s * / ;
2531const IMPORT_REG = / \b i m p o r t \( / g;
2632const IS_BUN = typeof Bun !== 'undefined' ;
@@ -38,13 +44,19 @@ const SERVICE_STATUS_STOPPING = 4; // running stop fn
3844const SERVICE_STATUS_FAILED = 5 ; // waiting for fix
3945const VERSION = require ( './package.json' ) . version ;
4046const WATCH_OPTIONS = { persistent : true , interval : 1000 } ;
47+ const ZSTD_c_compressionLevel = 100 ;
48+ const ZSTD_OPTIONS = {
49+ params : {
50+ [ ZSTD_c_compressionLevel ] : 3 ,
51+ }
52+ }
4153
4254// config
4355const log_verbose_flag = process . argv . includes ( '-v' ) ;
4456let log_verbose = log_verbose_flag ;
4557let port_http = 0 ;
4658let port_https = 0 ;
47- let gz_enabled = true ;
59+ let compression_enabled = true ;
4860let exiting = false ;
4961/// any path -> file
5062const path_aliases = new Map ( [
@@ -863,13 +875,7 @@ const request_handle = async (request, response, https) => {
863875 file_type_index + 1
864876 ) . toLowerCase ( ) ;
865877
866- let file_gz_enabled = (
867- gz_enabled &&
868- ! request_method_head &&
869- 'accept-encoding' in request_headers &&
870- ! type_raws . has ( file_type ) &&
871- request_headers [ 'accept-encoding' ] . split ( HTTP_LIST_REG ) . includes ( 'gzip' )
872- ) ;
878+ let file_compression = COMPRESS_METHOD_NONE ;
873879
874880 const file_dyn_enabled = (
875881 type_dynamics . has ( file_type ) &&
@@ -1139,18 +1145,48 @@ const request_handle = async (request, response, https) => {
11391145 file_function_input [ 'path' ] = request_url_parsed . pathname ;
11401146 file_function_input [ 'user_agent' ] = request_headers [ 'user-agent' ] ;
11411147
1142- let file_function_output ;
1148+ let file_function_output = response ;
11431149 response . setHeader ( 'Cache-Control' , 'no-cache, no-store' ) ;
11441150
1145- if ( file_gz_enabled ) {
1146- response . setHeader ( 'Content-Encoding' , 'gzip' ) ;
1147-
1148- (
1149- file_function_output = zlib . createGzip ( GZIP_OPTIONS )
1150- ) . pipe ( response ) ;
1151- }
1152- else {
1153- file_function_output = response ;
1151+ if (
1152+ compression_enabled &&
1153+ 'accept-encoding' in request_headers &&
1154+ ! type_raws . has ( file_type )
1155+ ) {
1156+ const encodings = request_headers [ 'accept-encoding' ] . split ( HTTP_LIST_REG ) ;
1157+ if (
1158+ HAS_ZSTD &&
1159+ encodings . includes ( 'zstd' )
1160+ ) {
1161+ file_compression = COMPRESS_METHOD_ZSTD ;
1162+ response . setHeader ( 'Content-Encoding' , 'zstd' ) ;
1163+ if ( ! request_method_head ) {
1164+ (
1165+ file_function_output = zlib . createZstdCompress ( ZSTD_OPTIONS )
1166+ ) . pipe ( response ) ;
1167+ }
1168+ }
1169+ else if (
1170+ HAS_BROTLI &&
1171+ encodings . includes ( 'br' )
1172+ ) {
1173+ file_compression = COMPRESS_METHOD_BROTLI ;
1174+ response . setHeader ( 'Content-Encoding' , 'br' ) ;
1175+ if ( ! request_method_head ) {
1176+ (
1177+ file_function_output = zlib . createBrotliCompress ( )
1178+ ) . pipe ( response ) ;
1179+ }
1180+ }
1181+ else if ( encodings . includes ( 'gzip' ) ) {
1182+ file_compression = COMPRESS_METHOD_GZIP ;
1183+ response . setHeader ( 'Content-Encoding' , 'gzip' ) ;
1184+ if ( ! request_method_head ) {
1185+ (
1186+ file_function_output = zlib . createGzip ( GZIP_OPTIONS )
1187+ ) . pipe ( response ) ;
1188+ }
1189+ }
11541190 }
11551191
11561192 if ( spam_enabled ) spam ( 'execute' , [
@@ -1223,7 +1259,7 @@ const request_handle = async (request, response, https) => {
12231259 } ERROR!`) ;
12241260 }
12251261 }
1226- else if ( file_gz_enabled ) {
1262+ else if ( file_compression !== COMPRESS_METHOD_NONE ) {
12271263 response . removeHeader ( 'Content-Encoding' ) ;
12281264 }
12291265 if ( typeof returned !== 'number' ) {
@@ -1241,35 +1277,67 @@ const request_handle = async (request, response, https) => {
12411277 file_function_output . end ( ) ;
12421278 }
12431279 else { // static file
1244- let file_data = null ;
1280+ const compression_enabled_type = (
1281+ compression_enabled &&
1282+ ! type_raws . has ( file_type )
1283+ ) ;
1284+ let path_real_send = path_real ;
12451285
12461286 if (
1247- file_gz_enabled &&
1248- file_stat . size > 80 &&
1249- fs . existsSync ( path_real + '.gz' )
1287+ compression_enabled_type &&
1288+ 'accept-encoding' in request_headers
12501289 ) {
1251- file_data = fs . createReadStream ( path_real + '.gz' ) ;
1252- }
1253- else {
1254- file_gz_enabled = false ;
1255- file_data = fs . createReadStream ( path_real ) ;
1290+ const encodings = request_headers [ 'accept-encoding' ] . split ( HTTP_LIST_REG ) ;
1291+ if (
1292+ encodings . includes ( 'zstd' ) &&
1293+ fs . existsSync ( path_real + '.zst' )
1294+ ) {
1295+ file_compression = COMPRESS_METHOD_ZSTD ;
1296+ path_real_send += '.zst' ;
1297+ }
1298+ else if (
1299+ encodings . includes ( 'br' ) &&
1300+ fs . existsSync ( path_real + '.br' )
1301+ ) {
1302+ file_compression = COMPRESS_METHOD_BROTLI ;
1303+ path_real_send += '.br' ;
1304+ }
1305+ else if (
1306+ encodings . includes ( 'gzip' ) &&
1307+ fs . existsSync ( path_real + '.gz' )
1308+ ) {
1309+ file_compression = COMPRESS_METHOD_GZIP ;
1310+ path_real_send += '.gz' ;
1311+ }
12561312 }
12571313
1258- if ( spam_enabled ) spam ( 'static_send' , [ path , file_gz_enabled ] ) ;
1314+ if ( spam_enabled ) spam ( 'static_send' , [ path , file_compression ] ) ;
12591315 response . setHeader ( 'Cache-Control' , 'public, max-age=600' ) ;
1260-
1261- if ( file_gz_enabled ) {
1262- response . setHeader ( 'Content-Encoding' , 'gzip' ) ;
1316+ if ( compression_enabled_type ) {
1317+ response . setHeader ( 'Vary' , 'Accept-Encoding' ) ;
12631318 }
1264- else {
1319+
1320+ switch ( file_compression ) {
1321+ case COMPRESS_METHOD_NONE :
12651322 response . setHeader ( 'Content-Length' , file_stat . size ) ;
1323+ break ;
1324+ case COMPRESS_METHOD_GZIP :
1325+ response . setHeader ( 'Content-Encoding' , 'gzip' ) ;
1326+ break ;
1327+ case COMPRESS_METHOD_BROTLI :
1328+ response . setHeader ( 'Content-Encoding' , 'br' ) ;
1329+ break ;
1330+ case COMPRESS_METHOD_ZSTD :
1331+ response . setHeader ( 'Content-Encoding' , 'zstd' ) ;
1332+ break ;
12661333 }
12671334
12681335 if ( request_method_head ) {
12691336 response . end ( ) ;
12701337 }
12711338 else {
1272- file_data . pipe ( response ) ;
1339+ fs . createReadStream ( path_real_send )
1340+ . pipe ( response ) ;
12731341 }
12741342 }
12751343 }
@@ -1347,6 +1415,8 @@ log(`rtjscomp v${
13471415 process . platform
13481416 . replace ( 'win32' , 'windows' )
13491417} `) ;
1418+ if ( log_verbose && ! HAS_BROTLI ) log ( '[hint] brotli not available' ) ;
1419+ if ( log_verbose && ! HAS_ZSTD ) log ( '[hint] zstd not available' ) ;
13501420
13511421await file_keep_new ( PATH_CONFIG + 'init.js' , async data => {
13521422 if ( ! data ) return ;
@@ -1554,7 +1624,7 @@ try {
15541624 }
15551625}
15561626catch ( err ) {
1557- if ( log_verbose ) log ( 'https: no cert, disabled' ) ;
1627+ if ( log_verbose ) log ( '[hint] https: no cert, disabled' ) ;
15581628}
15591629
15601630// config
@@ -1565,7 +1635,8 @@ await file_keep_new('rtjscomp.json', data => {
15651635 throw 'must contain {}' ;
15661636 }
15671637
1568- const gzip_level_new = get_prop_uint ( data , 'gzip_level' , 9 ) ;
1638+ const compression_enabled_new = get_prop_bool ( data , 'compress' , true ) ;
1639+ const gzip_level_new = get_prop_uint ( data , 'gzip_level' , 3 ) ;
15691640 const log_verbose_new = get_prop_bool ( data , 'log_verbose' , log_verbose_flag ) ;
15701641 const path_aliases_new = get_prop_map ( data , 'path_aliases' ) ;
15711642 const path_ghosts_new = get_prop_list ( data , 'path_ghosts' ) ;
@@ -1577,6 +1648,7 @@ await file_keep_new('rtjscomp.json', data => {
15771648 const type_dynamics_new = get_prop_list ( data , 'type_dynamics' ) ;
15781649 const type_mimes_new = get_prop_map ( data , 'type_mimes' ) ;
15791650 const type_raws_new = get_prop_list ( data , 'type_raws' ) ;
1651+ const zstd_level_new = get_prop_uint ( data , 'zstd_level' , 3 ) ;
15801652
15811653 if ( data ) {
15821654 const keys_left = Object . keys ( data ) ;
@@ -1587,15 +1659,19 @@ await file_keep_new('rtjscomp.json', data => {
15871659 if ( gzip_level_new > 9 ) {
15881660 throw 'gzip_level > 9' ;
15891661 }
1662+ if ( zstd_level_new > 19 ) {
1663+ throw 'zstd_level > 19' ;
1664+ }
15901665 if (
15911666 port_http_new > 65535 ||
15921667 port_https_new > 65535
15931668 ) {
15941669 throw 'port > 65535' ;
15951670 }
15961671
1597- gz_enabled = gzip_level_new > 0 ;
1598- GZIP_OPTIONS . level = gzip_level_new ;
1672+ compression_enabled = compression_enabled_new ;
1673+ GZIP_OPTIONS . level = compression_enabled ? gzip_level_new : 0 ;
1674+ ZSTD_OPTIONS . params [ ZSTD_c_compressionLevel ] = zstd_level_new ;
15991675 log_verbose = log_verbose_new ;
16001676 if ( path_ghosts_new ) {
16011677 path_ghosts . clear ( ) ;
0 commit comments