@@ -1526,8 +1526,41 @@ const request_handle = async (request, response, https) => {
15261526 }
15271527 }
15281528
1529+ // Generate ETag from modification time and file size
1530+ const mtime_ms = file_stat . mtimeMs ;
1531+ const etag = `"${ mtime_ms . toString ( 36 ) } -${ file_stat . size . toString ( 36 ) } "` ;
1532+ const last_modified = new Date ( mtime_ms ) . toUTCString ( ) ;
1533+
1534+ // Check If-None-Match (ETag validation)
1535+ if ( 'if-none-match' in request_headers ) {
1536+ const if_none_match = request_headers [ 'if-none-match' ] ;
1537+ if ( if_none_match === etag || if_none_match === '*' ) {
1538+ response . setHeader ( 'ETag' , etag ) ;
1539+ response . setHeader ( 'Cache-Control' , 'public, max-age=31536000' ) ;
1540+ throw 304 ;
1541+ }
1542+ }
1543+
1544+ // Check If-Modified-Since (time-based validation)
1545+ if (
1546+ 'if-modified-since' in request_headers &&
1547+ ! ( 'if-none-match' in request_headers )
1548+ ) {
1549+ const if_modified_since = new Date ( request_headers [ 'if-modified-since' ] ) ;
1550+ // Round mtime down to seconds for comparison (HTTP dates don't have millisecond precision)
1551+ const mtime_seconds = Math . floor ( mtime_ms / 1000 ) * 1000 ;
1552+ if ( ! isNaN ( if_modified_since ) && mtime_seconds <= if_modified_since . getTime ( ) ) {
1553+ response . setHeader ( 'ETag' , etag ) ;
1554+ response . setHeader ( 'Last-Modified' , last_modified ) ;
1555+ response . setHeader ( 'Cache-Control' , 'public, max-age=31536000' ) ;
1556+ throw 304 ;
1557+ }
1558+ }
1559+
15291560 if ( spam_enabled ) spam ( 'static_send' , [ path , file_compression ] ) ;
1530- response . setHeader ( 'Cache-Control' , 'public, max-age=600' ) ;
1561+ response . setHeader ( 'Cache-Control' , 'public, max-age=31536000' ) ;
1562+ response . setHeader ( 'ETag' , etag ) ;
1563+ response . setHeader ( 'Last-Modified' , last_modified ) ;
15311564 if ( compression_enabled_type ) {
15321565 response . setHeader ( 'Vary' , 'Accept-Encoding' ) ;
15331566 }
@@ -1637,15 +1670,22 @@ const request_handle = async (request, response, https) => {
16371670 }
16381671
16391672 if ( ! response . headersSent ) {
1640- response . writeHead ( err , {
1641- 'Content-Type' : 'text/html' ,
1642- 'Cache-Control' : 'no-cache, no-store' ,
1643- } ) ;
1644- response . end ( `<!DOCTYPE html><html><body><h1>HTTP ${
1645- err
1646- } : ${
1647- http . STATUS_CODES [ err ] || 'Error'
1648- } </h1></body></html>`) ;
1673+ if ( err === 304 ) {
1674+ // 304 Not Modified - headers already set, just send status
1675+ response . statusCode = 304 ;
1676+ response . end ( ) ;
1677+ }
1678+ else {
1679+ response . writeHead ( err , {
1680+ 'Content-Type' : 'text/html' ,
1681+ 'Cache-Control' : 'no-cache, no-store' ,
1682+ } ) ;
1683+ response . end ( `<!DOCTYPE html><html><body><h1>HTTP ${
1684+ err
1685+ } : ${
1686+ http . STATUS_CODES [ err ] || 'Error'
1687+ } </h1></body></html>`) ;
1688+ }
16491689 }
16501690 if ( 'content-length' in request_headers ) {
16511691 request . socket . destroy ( ) ;
0 commit comments