fix(anime): preserve HLS playlist rewriting and fetch timeouts

- Force identity encoding for upstream playlist responses
- Keep inactivity timeouts active while streaming response bodies
This commit is contained in:
2026-08-16 14:20:08 -07:00
parent f6993ae507
commit 1d1575f414
3 changed files with 38 additions and 12 deletions
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
import http from 'node:http';
import net from 'node:net';
import type { AddressInfo } from 'node:net';
import { gzipSync } from 'node:zlib';
import {
findTsSyncOffset,
rewritePlaylistOrigins,
@@ -204,6 +205,31 @@ test('proxy leaves non-TS bodies alone', async () => {
);
});
test('proxy forces identity encoding so playlists remain readable for rewriting', async () => {
let upstreamAcceptEncoding: string | undefined;
await withProxy(
(req, res) => {
upstreamAcceptEncoding = req.headers['accept-encoding'];
const body = `#EXTM3U\n#EXTINF:6,\nhttp://${req.headers.host}/video/seg.ts\n`;
const compressed = upstreamAcceptEncoding?.includes('gzip') === true;
res.writeHead(200, {
'content-type': 'application/vnd.apple.mpegurl',
...(compressed ? { 'content-encoding': 'gzip' } : {}),
});
res.end(compressed ? gzipSync(body) : body);
},
async (proxyOrigin, upstreamOrigin) => {
const response = await fetch(`${proxyOrigin}/video/list.m3u8`, {
headers: { 'accept-encoding': 'gzip' },
});
const body = await response.text();
assert.equal(upstreamAcceptEncoding, 'identity');
assert.ok(body.includes(`${proxyOrigin}/video/seg.ts`));
assert.ok(!body.includes(upstreamOrigin));
},
);
});
test('proxy accepts only origin-form targets for its configured HTTP upstream', async () => {
let upstreamHits = 0;
await withProxy(
+10 -10
View File
@@ -7,22 +7,23 @@ interface ClientRequestLifecycle {
closed: boolean;
}
const DROPPED_REQUEST_HEADERS = new Set([
'accept-encoding',
'connection',
'keep-alive',
'transfer-encoding',
'content-length',
]);
export function forwardableRequestHeaders(
headers: http.IncomingHttpHeaders,
): http.OutgoingHttpHeaders {
const result: http.OutgoingHttpHeaders = {};
for (const [name, value] of Object.entries(headers)) {
if (
value === undefined ||
name.toLowerCase() === 'connection' ||
name.toLowerCase() === 'keep-alive' ||
name.toLowerCase() === 'transfer-encoding' ||
name.toLowerCase() === 'content-length'
) {
continue;
}
if (value === undefined || DROPPED_REQUEST_HEADERS.has(name.toLowerCase())) continue;
result[name] = value;
}
result['accept-encoding'] = 'identity';
return result;
}
@@ -75,7 +76,6 @@ function requestAttempt(
};
upstream.once('end', clearActiveRequest);
upstream.once('close', clearActiveRequest);
upstreamRequest.setTimeout(0);
const status = upstream.statusCode ?? 502;
if (status === 404 || status >= 500) {
if (mayRetry) {