From 36cf561f198c557bd10c711405954969b98a5602 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:51:15 +0000 Subject: [PATCH 1/5] Initial plan From d56c9d68cbdf29903cb1ab1edb0a9b2dcc594be9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 18:56:46 +0000 Subject: [PATCH 2/5] Add HTTP cache headers for static files with ETag and Last-Modified support Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- package.json | 2 +- rtjscomp.js | 60 +++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 9b94f2e..713e315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rtjscomp", - "version": "0.9.12", + "version": "0.9.13", "description": "php-like server but with javascript", "repository": { "type": "git", diff --git a/rtjscomp.js b/rtjscomp.js index 30bc66b..957f3fd 100755 --- a/rtjscomp.js +++ b/rtjscomp.js @@ -1526,8 +1526,41 @@ const request_handle = async (request, response, https) => { } } + // Generate ETag from modification time and file size + const mtime_ms = file_stat.mtimeMs; + const etag = `"${mtime_ms.toString(36)}-${file_stat.size.toString(36)}"`; + const last_modified = new Date(mtime_ms).toUTCString(); + + // Check If-None-Match (ETag validation) + if ('if-none-match' in request_headers) { + const if_none_match = request_headers['if-none-match']; + if (if_none_match === etag || if_none_match === '*') { + response.setHeader('ETag', etag); + response.setHeader('Cache-Control', 'public, max-age=31536000'); + throw 304; + } + } + + // Check If-Modified-Since (time-based validation) + if ( + 'if-modified-since' in request_headers && + !('if-none-match' in request_headers) + ) { + const if_modified_since = new Date(request_headers['if-modified-since']); + // Round mtime down to seconds for comparison (HTTP dates don't have millisecond precision) + const mtime_seconds = Math.floor(mtime_ms / 1000) * 1000; + if (!isNaN(if_modified_since) && mtime_seconds <= if_modified_since.getTime()) { + response.setHeader('ETag', etag); + response.setHeader('Last-Modified', last_modified); + response.setHeader('Cache-Control', 'public, max-age=31536000'); + throw 304; + } + } + if (spam_enabled) spam('static_send', [path, file_compression]); - response.setHeader('Cache-Control', 'public, max-age=600'); + response.setHeader('Cache-Control', 'public, max-age=31536000'); + response.setHeader('ETag', etag); + response.setHeader('Last-Modified', last_modified); if (compression_enabled_type) { response.setHeader('Vary', 'Accept-Encoding'); } @@ -1637,15 +1670,22 @@ const request_handle = async (request, response, https) => { } if (!response.headersSent) { - response.writeHead(err, { - 'Content-Type': 'text/html', - 'Cache-Control': 'no-cache, no-store', - }); - response.end(`

HTTP ${ - err - }: ${ - http.STATUS_CODES[err] || 'Error' - }

`); + if (err === 304) { + // 304 Not Modified - headers already set, just send status + response.statusCode = 304; + response.end(); + } + else { + response.writeHead(err, { + 'Content-Type': 'text/html', + 'Cache-Control': 'no-cache, no-store', + }); + response.end(`

HTTP ${ + err + }: ${ + http.STATUS_CODES[err] || 'Error' + }

`); + } } if ('content-length' in request_headers) { request.socket.destroy(); From 8537cd26e271ca4d86a9c6ae0fc81d92b4e351bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:02:10 +0000 Subject: [PATCH 3/5] Remove redundant ETag implementation, keep only Last-Modified Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- rtjscomp.js | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/rtjscomp.js b/rtjscomp.js index 957f3fd..5fb04b4 100755 --- a/rtjscomp.js +++ b/rtjscomp.js @@ -1526,31 +1526,16 @@ const request_handle = async (request, response, https) => { } } - // Generate ETag from modification time and file size + // Generate Last-Modified header from file modification time const mtime_ms = file_stat.mtimeMs; - const etag = `"${mtime_ms.toString(36)}-${file_stat.size.toString(36)}"`; const last_modified = new Date(mtime_ms).toUTCString(); - // Check If-None-Match (ETag validation) - if ('if-none-match' in request_headers) { - const if_none_match = request_headers['if-none-match']; - if (if_none_match === etag || if_none_match === '*') { - response.setHeader('ETag', etag); - response.setHeader('Cache-Control', 'public, max-age=31536000'); - throw 304; - } - } - // Check If-Modified-Since (time-based validation) - if ( - 'if-modified-since' in request_headers && - !('if-none-match' in request_headers) - ) { + if ('if-modified-since' in request_headers) { const if_modified_since = new Date(request_headers['if-modified-since']); // Round mtime down to seconds for comparison (HTTP dates don't have millisecond precision) const mtime_seconds = Math.floor(mtime_ms / 1000) * 1000; if (!isNaN(if_modified_since) && mtime_seconds <= if_modified_since.getTime()) { - response.setHeader('ETag', etag); response.setHeader('Last-Modified', last_modified); response.setHeader('Cache-Control', 'public, max-age=31536000'); throw 304; @@ -1559,7 +1544,6 @@ const request_handle = async (request, response, https) => { if (spam_enabled) spam('static_send', [path, file_compression]); response.setHeader('Cache-Control', 'public, max-age=31536000'); - response.setHeader('ETag', etag); response.setHeader('Last-Modified', last_modified); if (compression_enabled_type) { response.setHeader('Vary', 'Accept-Encoding'); From 953213fe22ccbe6df7ddb71a7b3a485a56352ed9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 1 Jan 2026 19:13:55 +0000 Subject: [PATCH 4/5] Simplify cache implementation: inline variables, use return instead of throw Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> --- rtjscomp.js | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/rtjscomp.js b/rtjscomp.js index 5fb04b4..88dd110 100755 --- a/rtjscomp.js +++ b/rtjscomp.js @@ -1526,25 +1526,20 @@ const request_handle = async (request, response, https) => { } } - // Generate Last-Modified header from file modification time - const mtime_ms = file_stat.mtimeMs; - const last_modified = new Date(mtime_ms).toUTCString(); + if (spam_enabled) spam('static_send', [path, file_compression]); + response.setHeader('Cache-Control', 'public, max-age=31536000'); + response.setHeader('Last-Modified', new Date(file_stat.mtimeMs).toUTCString()); // Check If-Modified-Since (time-based validation) if ('if-modified-since' in request_headers) { const if_modified_since = new Date(request_headers['if-modified-since']); // Round mtime down to seconds for comparison (HTTP dates don't have millisecond precision) - const mtime_seconds = Math.floor(mtime_ms / 1000) * 1000; - if (!isNaN(if_modified_since) && mtime_seconds <= if_modified_since.getTime()) { - response.setHeader('Last-Modified', last_modified); - response.setHeader('Cache-Control', 'public, max-age=31536000'); - throw 304; + if (!isNaN(if_modified_since) && Math.floor(file_stat.mtimeMs / 1000) * 1000 <= if_modified_since.getTime()) { + response.statusCode = 304; + response.end(); + return; } } - - if (spam_enabled) spam('static_send', [path, file_compression]); - response.setHeader('Cache-Control', 'public, max-age=31536000'); - response.setHeader('Last-Modified', last_modified); if (compression_enabled_type) { response.setHeader('Vary', 'Accept-Encoding'); } @@ -1654,22 +1649,15 @@ const request_handle = async (request, response, https) => { } if (!response.headersSent) { - if (err === 304) { - // 304 Not Modified - headers already set, just send status - response.statusCode = 304; - response.end(); - } - else { - response.writeHead(err, { - 'Content-Type': 'text/html', - 'Cache-Control': 'no-cache, no-store', - }); - response.end(`

HTTP ${ - err - }: ${ - http.STATUS_CODES[err] || 'Error' - }

`); - } + response.writeHead(err, { + 'Content-Type': 'text/html', + 'Cache-Control': 'no-cache, no-store', + }); + response.end(`

HTTP ${ + err + }: ${ + http.STATUS_CODES[err] || 'Error' + }

`); } if ('content-length' in request_headers) { request.socket.destroy(); From 53f946cf5371fd3a5d07f96d121314d89300e40d Mon Sep 17 00:00:00 2001 From: Len Date: Thu, 1 Jan 2026 20:30:47 +0100 Subject: [PATCH 5/5] Refactor cache and range request handling --- rtjscomp.js | 110 ++++++++++++++++++++++++---------------------------- 1 file changed, 51 insertions(+), 59 deletions(-) diff --git a/rtjscomp.js b/rtjscomp.js index 88dd110..13deb43 100755 --- a/rtjscomp.js +++ b/rtjscomp.js @@ -1527,85 +1527,77 @@ const request_handle = async (request, response, https) => { } if (spam_enabled) spam('static_send', [path, file_compression]); - response.setHeader('Cache-Control', 'public, max-age=31536000'); - response.setHeader('Last-Modified', new Date(file_stat.mtimeMs).toUTCString()); - // Check If-Modified-Since (time-based validation) - if ('if-modified-since' in request_headers) { - const if_modified_since = new Date(request_headers['if-modified-since']); - // Round mtime down to seconds for comparison (HTTP dates don't have millisecond precision) - if (!isNaN(if_modified_since) && Math.floor(file_stat.mtimeMs / 1000) * 1000 <= if_modified_since.getTime()) { - response.statusCode = 304; - response.end(); - return; - } - } if (compression_enabled_type) { response.setHeader('Vary', 'Accept-Encoding'); } + response.setHeader('Cache-Control', 'public, max-age=31536000'); + response.setHeader('Last-Modified', new Date(file_stat.mtimeMs).toUTCString()); + + if ( + 'if-modified-since' in request_headers && + file_stat.mtimeMs <= new Date(request_headers['if-modified-since']) + ) { + response.statusCode = 304; + response.end(); + return; + } - // Handle Range requests only for uncompressed files let range_start = 0; let range_end = file_stat.size - 1; let is_range_request = false; - if ( - file_compression === COMPRESS_METHOD_NONE && - 'range' in request_headers && - !request_method_head - ) { + if (file_compression === COMPRESS_METHOD_NONE) { response.setHeader('Accept-Ranges', 'bytes'); - const range_header = request_headers['range']; - // Only single range requests supported (not multipart ranges) - const range_match = range_header.match(/^bytes=(\d*)-(\d*)$/); - - if (range_match) { - const start = range_match[1]; - const end = range_match[2]; - - if (start || end) { - is_range_request = true; - - if (start && end) { - // Both start and end specified: bytes=10-20 - range_start = parseInt(start, 10); - range_end = parseInt(end, 10); - } else if (start) { - // Only start specified: bytes=10- - range_start = parseInt(start, 10); - range_end = file_stat.size - 1; - } else { - // Only end specified (suffix-byte-range): bytes=-500 - const suffix_length = parseInt(end, 10); - range_start = Math.max(0, file_stat.size - suffix_length); - range_end = file_stat.size - 1; - } - - // Validate range - if ( - range_end >= file_stat.size || - range_start > range_end - ) { + if ( + !request_method_head && + 'range' in request_headers + ) { + const range_match = request_headers['range'].match(/^bytes=(\d*)-(\d*)$/); + if (range_match) { + const start = range_match[1]; + const end = range_match[2]; + if (start || end) { + is_range_request = true; + + if (start && end) { + range_start = parseInt(start); + range_end = parseInt(end); + } + else if (start) { + range_start = parseInt(start); + range_end = file_stat.size - 1; + } + else { + const suffix_length = parseInt(end); + range_start = Math.max(0, file_stat.size - suffix_length); + range_end = file_stat.size - 1; + } + + if ( + range_start > range_end || + range_end >= file_stat.size + ) { + response.setHeader( + 'Content-Range', + `bytes */${file_stat.size}` + ); + throw 416; + } + + response.statusCode = 206; + response.setHeader('Content-Length', range_end - range_start + 1); response.setHeader( 'Content-Range', - `bytes */${file_stat.size}` + `bytes ${range_start}-${range_end}/${file_stat.size}` ); - throw 416; } - - response.statusCode = 206; - response.setHeader( - 'Content-Range', - `bytes ${range_start}-${range_end}/${file_stat.size}` - ); - response.setHeader('Content-Length', range_end - range_start + 1); } } } if (!is_range_request) { if (file_compression === COMPRESS_METHOD_NONE) { - response.setHeader('Accept-Ranges', 'bytes'); response.setHeader('Content-Length', file_stat.size); } else {