Skip to content

Commit bc409ff

Browse files
CopilotL3P3
andauthored
Add HTTP Range header support for partial static file serving (#62)
* Initial plan * Implement HTTP Range support for static files - Add Range header parsing for static files - Support three range types: start-end, start-, and -suffix - Return 206 Partial Content with Content-Range header - Return 416 Range Not Satisfiable for invalid ranges - Add Accept-Ranges: bytes header to all static responses - Only process ranges for uncompressed files (COMPRESS_METHOD_NONE) - HEAD requests bypass Range logic as expected - Fully backward compatible with non-Range requests Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> * Address code review feedback for Range support - Add comment clarifying only single ranges are supported (not multipart) - Remove redundant range_start < 0 validation check - Add error handling for file streams to prevent crashes - Multi-range requests now gracefully fall back to full file (200 OK) Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> * Refactor: eliminate code duplication in stream creation Use conditional object for createReadStream options instead of separate if/else branches. Pass {start, end} when is_range_request is true, otherwise pass empty object. Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> * version bump --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: L3P3 <4629449+L3P3@users.noreply.github.com> Co-authored-by: Len <dev@l3p3.de>
1 parent 14e6ec1 commit bc409ff

File tree

2 files changed

+81
-10
lines changed

2 files changed

+81
-10
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rtjscomp",
3-
"version": "0.9.11",
3+
"version": "0.9.12",
44
"description": "php-like server but with javascript",
55
"repository": {
66
"type": "git",

rtjscomp.js

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,22 +1532,93 @@ const request_handle = async (request, response, https) => {
15321532
response.setHeader('Vary', 'Accept-Encoding');
15331533
}
15341534

1535-
if (file_compression === COMPRESS_METHOD_NONE) {
1536-
response.setHeader('Content-Length', file_stat.size);
1535+
// Handle Range requests only for uncompressed files
1536+
let range_start = 0;
1537+
let range_end = file_stat.size - 1;
1538+
let is_range_request = false;
1539+
1540+
if (
1541+
file_compression === COMPRESS_METHOD_NONE &&
1542+
'range' in request_headers &&
1543+
!request_method_head
1544+
) {
1545+
response.setHeader('Accept-Ranges', 'bytes');
1546+
const range_header = request_headers['range'];
1547+
// Only single range requests supported (not multipart ranges)
1548+
const range_match = range_header.match(/^bytes=(\d*)-(\d*)$/);
1549+
1550+
if (range_match) {
1551+
const start = range_match[1];
1552+
const end = range_match[2];
1553+
1554+
if (start || end) {
1555+
is_range_request = true;
1556+
1557+
if (start && end) {
1558+
// Both start and end specified: bytes=10-20
1559+
range_start = parseInt(start, 10);
1560+
range_end = parseInt(end, 10);
1561+
} else if (start) {
1562+
// Only start specified: bytes=10-
1563+
range_start = parseInt(start, 10);
1564+
range_end = file_stat.size - 1;
1565+
} else {
1566+
// Only end specified (suffix-byte-range): bytes=-500
1567+
const suffix_length = parseInt(end, 10);
1568+
range_start = Math.max(0, file_stat.size - suffix_length);
1569+
range_end = file_stat.size - 1;
1570+
}
1571+
1572+
// Validate range
1573+
if (
1574+
range_end >= file_stat.size ||
1575+
range_start > range_end
1576+
) {
1577+
response.setHeader(
1578+
'Content-Range',
1579+
`bytes */${file_stat.size}`
1580+
);
1581+
throw 416;
1582+
}
1583+
1584+
response.statusCode = 206;
1585+
response.setHeader(
1586+
'Content-Range',
1587+
`bytes ${range_start}-${range_end}/${file_stat.size}`
1588+
);
1589+
response.setHeader('Content-Length', range_end - range_start + 1);
1590+
}
1591+
}
15371592
}
1538-
else {
1539-
response.setHeader(
1540-
'Content-Encoding',
1541-
COMPRESS_METHODS[file_compression]
1542-
);
1593+
1594+
if (!is_range_request) {
1595+
if (file_compression === COMPRESS_METHOD_NONE) {
1596+
response.setHeader('Accept-Ranges', 'bytes');
1597+
response.setHeader('Content-Length', file_stat.size);
1598+
}
1599+
else {
1600+
response.setHeader(
1601+
'Content-Encoding',
1602+
COMPRESS_METHODS[file_compression]
1603+
);
1604+
}
15431605
}
15441606

15451607
if (request_method_head) {
15461608
response.end();
15471609
}
15481610
else {
1549-
fs.createReadStream(path_real_send)
1550-
.pipe(response);
1611+
const stream = fs.createReadStream(
1612+
path_real_send,
1613+
is_range_request ? {start: range_start, end: range_end} : {}
1614+
);
1615+
stream.on('error', err => {
1616+
log(`[error] ${path} stream: ${err.message}`);
1617+
if (!response.headersSent) {
1618+
throw 500;
1619+
}
1620+
});
1621+
stream.pipe(response);
15511622
}
15521623
}
15531624
}

0 commit comments

Comments
 (0)