@@ -71,6 +71,7 @@ const crypto = require('crypto');
7171const fs = require ( 'fs' ) ;
7272const http = require ( 'http' ) ;
7373const path = require ( 'path' ) ;
74+ const zlib = require ( 'zlib' ) ;
7475const pjoin = path . join ;
7576
7677const ROOT_DIR = path . dirname ( __dirname ) ; // The repo root.
@@ -715,6 +716,29 @@ function startServer() {
715716 return ;
716717 }
717718
719+ let stat ;
720+ try {
721+ stat = fs . statSync ( absPath ) ;
722+ } catch ( statErr ) {
723+ res . writeHead ( 404 ) ;
724+ res . end ( JSON . stringify ( statErr ) ) ;
725+ return ;
726+ }
727+
728+ // Truncate to second precision: HTTP dates have 1s resolution, so the
729+ // sub-millisecond part of mtime would cause a permanent mismatch.
730+ const mtimeSec = Math . floor ( stat . mtime . getTime ( ) / 1000 ) * 1000 ;
731+ const mtimeStr = new Date ( mtimeSec ) . toUTCString ( ) ;
732+
733+ // Return 304 if the browser's cached copy is still fresh.
734+ const ifModifiedSince = req . headers [ 'if-modified-since' ] ;
735+ if ( ifModifiedSince !== undefined &&
736+ new Date ( ifModifiedSince ) . getTime ( ) >= mtimeSec ) {
737+ res . writeHead ( 304 ) ;
738+ res . end ( ) ;
739+ return ;
740+ }
741+
718742 fs . readFile ( absPath , function ( err , data ) {
719743 if ( err ) {
720744 res . writeHead ( 404 ) ;
@@ -730,25 +754,37 @@ function startServer() {
730754 } ;
731755 const ext = uri . split ( '.' ) . pop ( ) ;
732756 const cType = mimeMap [ ext ] || 'octect/stream' ;
733- const head = {
734- 'Content-Type' : cType ,
735- 'Content-Length' : data . length ,
736- 'Last-Modified' : fs . statSync ( absPath ) . mtime . toUTCString ( ) ,
737- 'Cache-Control' : 'no-cache' ,
757+ const acceptsGzip =
758+ ( req . headers [ 'accept-encoding' ] || '' ) . includes ( 'gzip' ) ;
759+ const finalize = ( body ) => {
760+ const head = {
761+ 'Content-Type' : cType ,
762+ 'Content-Length' : body . length ,
763+ 'Last-Modified' : mtimeStr ,
764+ 'Cache-Control' : 'no-cache' ,
765+ } ;
766+ if ( acceptsGzip ) head [ 'Content-Encoding' ] = 'gzip' ;
767+ if ( cfg . crossOriginIsolation ) {
768+ head [ 'Cross-Origin-Opener-Policy' ] = 'same-origin' ;
769+ head [ 'Cross-Origin-Embedder-Policy' ] = 'require-corp' ;
770+ }
771+ res . writeHead ( 200 , head ) ;
772+ res . write ( body ) ;
773+ res . end ( ) ;
738774 } ;
739- if ( cfg . crossOriginIsolation ) {
740- head [ 'Cross-Origin-Opener-Policy' ] = 'same-origin' ;
741- head [ 'Cross-Origin-Embedder-Policy' ] = 'require-corp' ;
775+ if ( acceptsGzip ) {
776+ zlib . gzip ( data , ( gzErr , compressed ) => {
777+ finalize ( gzErr ? data : compressed ) ;
778+ } ) ;
779+ } else {
780+ finalize ( data ) ;
742781 }
743- res . writeHead ( 200 , head ) ;
744- res . write ( data ) ;
745- res . end ( ) ;
746782 } ) ;
747783 } ) ;
748784
749785 let port = cfg . httpServerListenPort ?? DEFAULT_PORT ;
750786 let retryCount = 0 ;
751-
787+
752788 // Pick the next free port starting at 10000
753789 server . on ( 'error' , ( e ) => {
754790 if ( e . code === 'EADDRINUSE' ) {
0 commit comments