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 {