This commit is contained in:
1 parent
2942f48ded
commit
1276dac371
7 files changed
+358
-142
No files matched your search
@@ -22,6 +22,8 @@ ENV LISTEN_PORT=8080 \
|
|||||||
PROBE_TIMEOUT_MS=8000 \
|
PROBE_TIMEOUT_MS=8000 \
|
||||||
UPSTREAM_TIMEOUT_MS=15000 \
|
UPSTREAM_TIMEOUT_MS=15000 \
|
||||||
ALLOW_BUSY_FALLBACK=1 \
|
ALLOW_BUSY_FALLBACK=1 \
|
||||||
|
PUBLIC_URL="" \
|
||||||
|
LOG_REQUESTS=0 \
|
||||||
PROXY_INSECURE_TLS=0
|
PROXY_INSECURE_TLS=0
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ services:
|
|||||||
PROBE_TIMEOUT_MS: "8000"
|
PROBE_TIMEOUT_MS: "8000"
|
||||||
UPSTREAM_TIMEOUT_MS: "15000"
|
UPSTREAM_TIMEOUT_MS: "15000"
|
||||||
ALLOW_BUSY_FALLBACK: "1"
|
ALLOW_BUSY_FALLBACK: "1"
|
||||||
|
# URL waarop clients de proxy bereiken, gebruikt in herschreven catalogus- en
|
||||||
|
# playlist-URLs. Leeg laten om de Host-header van het request te gebruiken.
|
||||||
|
# PUBLIC_URL: "http://192.168.0.123:8080"
|
||||||
|
# LOG_REQUESTS: "1" # log elke (stream)request en status
|
||||||
# PROXY_INSECURE_TLS: "1" # alleen nodig bij self-signed upstream-certificaten
|
# PROXY_INSECURE_TLS: "1" # alleen nodig bij self-signed upstream-certificaten
|
||||||
volumes:
|
volumes:
|
||||||
- ./upstreams.json:/config/upstreams.json:ro
|
- ./upstreams.json:/config/upstreams.json:ro
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function healthPayload() {
|
|||||||
return {
|
return {
|
||||||
ok: upstreams.some((upstream) => upstream.state.healthy),
|
ok: upstreams.some((upstream) => upstream.state.healthy),
|
||||||
configFile: settings.configFile,
|
configFile: settings.configFile,
|
||||||
|
publicUrl: settings.publicUrl || null,
|
||||||
probeIntervalSec: settings.probeIntervalSec,
|
probeIntervalSec: settings.probeIntervalSec,
|
||||||
lastProbeAt: getLastProbeAt(),
|
lastProbeAt: getLastProbeAt(),
|
||||||
maxConnections: totalConnectionCapacity(),
|
maxConnections: totalConnectionCapacity(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { settings } = require('./settings');
|
||||||
|
|
||||||
function sendText(res, statusCode, text, extraHeaders = {}) {
|
function sendText(res, statusCode, text, extraHeaders = {}) {
|
||||||
res.writeHead(statusCode, {
|
res.writeHead(statusCode, {
|
||||||
'Content-Type': 'text/plain; charset=utf-8',
|
'Content-Type': 'text/plain; charset=utf-8',
|
||||||
@@ -26,6 +28,8 @@ function firstHeader(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getProxyOrigin(req) {
|
function getProxyOrigin(req) {
|
||||||
|
if (settings.publicUrl) return settings.publicUrl;
|
||||||
|
|
||||||
const proto =
|
const proto =
|
||||||
firstHeader(req.headers['x-forwarded-proto']) ||
|
firstHeader(req.headers['x-forwarded-proto']) ||
|
||||||
(req.socket.encrypted ? 'https' : 'http');
|
(req.socket.encrypted ? 'https' : 'http');
|
||||||
|
|||||||
+283
-124
@@ -25,6 +25,9 @@ const {
|
|||||||
} = require('./rewrite');
|
} = require('./rewrite');
|
||||||
const { sendText, getProxyOrigin } = require('./http-utils');
|
const { sendText, getProxyOrigin } = require('./http-utils');
|
||||||
|
|
||||||
|
const MAX_REDIRECTS = 5;
|
||||||
|
const AUTH_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
|
||||||
|
|
||||||
function nowIso() {
|
function nowIso() {
|
||||||
return new Date().toISOString();
|
return new Date().toISOString();
|
||||||
}
|
}
|
||||||
@@ -40,7 +43,72 @@ function markUpstreamFailure(upstream, error) {
|
|||||||
requestProbeSoon();
|
requestProbeSoon();
|
||||||
}
|
}
|
||||||
|
|
||||||
const AUTH_RESPONSE_MAX_BYTES = 4 * 1024 * 1024;
|
function isRedirect(status) {
|
||||||
|
return (
|
||||||
|
status === 301 ||
|
||||||
|
status === 302 ||
|
||||||
|
status === 303 ||
|
||||||
|
status === 307 ||
|
||||||
|
status === 308
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRetryableStatus(status) {
|
||||||
|
return status === 429 || status >= 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldRetryStreamStatus(status) {
|
||||||
|
return (
|
||||||
|
isRetryableStatus(status) ||
|
||||||
|
status === 401 ||
|
||||||
|
status === 403 ||
|
||||||
|
status === 404 ||
|
||||||
|
status === 406
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redact(value) {
|
||||||
|
if (typeof value !== 'string') return value;
|
||||||
|
let result = value;
|
||||||
|
for (const secret of [settings.proxyUsername, settings.proxyPassword]) {
|
||||||
|
if (secret) result = result.split(secret).join('***');
|
||||||
|
const encoded = encodeURIComponent(secret || '');
|
||||||
|
if (secret && encoded !== secret) {
|
||||||
|
result = result.split(encoded).join('***');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestDescription(req, reqUrl) {
|
||||||
|
return `${req.method} ${redact(reqUrl.pathname)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestKind(streamRequest) {
|
||||||
|
return streamRequest ? '[stream]' : '[api]';
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRequestHeaders(req, target) {
|
||||||
|
const headers = filterHopByHop(req.headers);
|
||||||
|
delete headers.host;
|
||||||
|
delete headers['accept-encoding'];
|
||||||
|
headers.host = target.host;
|
||||||
|
headers['accept-encoding'] = 'identity';
|
||||||
|
|
||||||
|
const clientIp = req.socket.remoteAddress || '';
|
||||||
|
const existingForwardedFor = req.headers['x-forwarded-for'];
|
||||||
|
headers['x-forwarded-for'] = existingForwardedFor
|
||||||
|
? `${existingForwardedFor}, ${clientIp}`
|
||||||
|
: clientIp;
|
||||||
|
if (!headers['x-forwarded-proto']) {
|
||||||
|
headers['x-forwarded-proto'] = req.socket.encrypted ? 'https' : 'http';
|
||||||
|
}
|
||||||
|
if (!headers['x-forwarded-host'] && req.headers.host) {
|
||||||
|
headers['x-forwarded-host'] = req.headers.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
function isAuthRequest(req, reqUrl) {
|
function isAuthRequest(req, reqUrl) {
|
||||||
if (req.method === 'HEAD') return false;
|
if (req.method === 'HEAD') return false;
|
||||||
@@ -48,8 +116,11 @@ function isAuthRequest(req, reqUrl) {
|
|||||||
return /\/player_api\.php\/?$/i.test(reqUrl.pathname);
|
return /\/player_api\.php\/?$/i.test(reqUrl.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyConnectionCapacity(body, upstream, proxyOrigin) {
|
function applyAuthRewrites(body, upstream, proxyOrigin) {
|
||||||
let text = body.toString('utf8');
|
let text = body.toString('utf8');
|
||||||
|
const proxyUrl = new URL(proxyOrigin);
|
||||||
|
const proxyPort =
|
||||||
|
proxyUrl.port || (proxyUrl.protocol === 'https:' ? '443' : '80');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(text);
|
const json = JSON.parse(text);
|
||||||
@@ -59,9 +130,25 @@ function applyConnectionCapacity(body, upstream, proxyOrigin) {
|
|||||||
if (max > 0) {
|
if (max > 0) {
|
||||||
info.max_connections =
|
info.max_connections =
|
||||||
typeof info.max_connections === 'number' ? max : String(max);
|
typeof info.max_connections === 'number' ? max : String(max);
|
||||||
text = JSON.stringify(json);
|
}
|
||||||
|
if (settings.proxyUsername && 'username' in info) {
|
||||||
|
info.username = settings.proxyUsername;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const server = json && json.server_info;
|
||||||
|
if (server && typeof server === 'object') {
|
||||||
|
if ('url' in server) server.url = proxyUrl.hostname;
|
||||||
|
if ('port' in server) server.port = proxyPort;
|
||||||
|
if ('https_port' in server && proxyUrl.protocol === 'https:') {
|
||||||
|
server.https_port = proxyPort;
|
||||||
|
}
|
||||||
|
const protocol = proxyUrl.protocol.replace(':', '');
|
||||||
|
if ('server_protocol' in server) server.server_protocol = protocol;
|
||||||
|
if ('protocol' in server) server.protocol = protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
text = JSON.stringify(json);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
return Buffer.from(rewriteUrlValue(text, upstream, proxyOrigin));
|
return Buffer.from(rewriteUrlValue(text, upstream, proxyOrigin));
|
||||||
@@ -97,7 +184,7 @@ function sendAuthResponse(proxyRes, res, upstream, proxyOrigin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.end(
|
res.end(
|
||||||
applyConnectionCapacity(Buffer.concat(chunks), upstream, proxyOrigin)
|
applyAuthRewrites(Buffer.concat(chunks), upstream, proxyOrigin)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -169,127 +256,15 @@ function handleProxy(req, res, reqUrl) {
|
|||||||
|
|
||||||
const pathname = rewritePath(creds, upstream);
|
const pathname = rewritePath(creds, upstream);
|
||||||
const search = rewriteSearch(reqUrl, upstream);
|
const search = rewriteSearch(reqUrl, upstream);
|
||||||
const target = buildUpstreamUrl(upstream, pathname, search);
|
const initialTarget = buildUpstreamUrl(upstream, pathname, search);
|
||||||
|
const initialHeaders = buildRequestHeaders(req, initialTarget);
|
||||||
|
let bodySent = false;
|
||||||
|
|
||||||
const headers = filterHopByHop(req.headers);
|
const fail = (error) => {
|
||||||
delete headers.host;
|
|
||||||
delete headers['accept-encoding'];
|
|
||||||
headers.host = target.host;
|
|
||||||
headers['accept-encoding'] = 'identity';
|
|
||||||
|
|
||||||
const clientIp = req.socket.remoteAddress || '';
|
|
||||||
const existingForwardedFor = req.headers['x-forwarded-for'];
|
|
||||||
headers['x-forwarded-for'] = existingForwardedFor
|
|
||||||
? `${existingForwardedFor}, ${clientIp}`
|
|
||||||
: clientIp;
|
|
||||||
if (!headers['x-forwarded-proto']) {
|
|
||||||
headers['x-forwarded-proto'] = req.socket.encrypted ? 'https' : 'http';
|
|
||||||
}
|
|
||||||
if (!headers['x-forwarded-host'] && req.headers.host) {
|
|
||||||
headers['x-forwarded-host'] = req.headers.host;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lib = target.protocol === 'https:' ? https : http;
|
|
||||||
let proxyReq;
|
|
||||||
let headersTimeout = null;
|
|
||||||
try {
|
|
||||||
proxyReq = lib.request(
|
|
||||||
{
|
|
||||||
protocol: target.protocol,
|
|
||||||
hostname: target.hostname,
|
|
||||||
port: target.port || undefined,
|
|
||||||
path: `${target.pathname}${target.search}`,
|
|
||||||
method: req.method,
|
|
||||||
headers,
|
|
||||||
rejectUnauthorized: settings.rejectUnauthorized,
|
|
||||||
},
|
|
||||||
(proxyRes) => {
|
|
||||||
clearTimeout(headersTimeout);
|
|
||||||
|
|
||||||
const proxyOrigin = getProxyOrigin(req);
|
|
||||||
const rewriting = shouldRewriteBody(req, proxyRes, reqUrl.pathname);
|
|
||||||
const responseHeaders = filterResponseHeaders(
|
|
||||||
proxyRes.headers,
|
|
||||||
rewriting
|
|
||||||
);
|
|
||||||
|
|
||||||
if (responseHeaders.location) {
|
|
||||||
responseHeaders.location = rewriteUrlValue(
|
|
||||||
responseHeaders.location,
|
|
||||||
upstream,
|
|
||||||
proxyOrigin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
res.writeHead(proxyRes.statusCode || 502, responseHeaders);
|
|
||||||
} catch {
|
|
||||||
release();
|
|
||||||
try {
|
|
||||||
proxyRes.destroy();
|
|
||||||
} catch {}
|
|
||||||
try {
|
|
||||||
res.destroy();
|
|
||||||
} catch {}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
proxyRes.on('close', release);
|
|
||||||
proxyRes.on('error', () => {
|
|
||||||
release();
|
|
||||||
if (!res.headersSent) {
|
|
||||||
sendText(res, 502, 'Upstream response error');
|
|
||||||
} else {
|
|
||||||
res.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rewriting) {
|
|
||||||
if (isAuthRequest(req, reqUrl)) {
|
|
||||||
sendAuthResponse(proxyRes, res, upstream, proxyOrigin);
|
|
||||||
} else {
|
|
||||||
const replacer = new ReplaceStream(
|
|
||||||
buildReplacements(upstream, proxyOrigin)
|
|
||||||
);
|
|
||||||
replacer.on('error', () => {
|
|
||||||
try {
|
|
||||||
res.destroy();
|
|
||||||
} catch {}
|
|
||||||
release();
|
|
||||||
});
|
|
||||||
proxyRes.pipe(replacer).pipe(res);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
proxyRes.pipe(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
release();
|
|
||||||
markUpstreamFailure(upstream, e);
|
|
||||||
if (canRetry(req) && attempted.size < upstreamCount) {
|
|
||||||
attempt();
|
|
||||||
} else {
|
|
||||||
sendText(res, 502, 'Upstream request failed');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
headersTimeout = setTimeout(() => {
|
|
||||||
try {
|
|
||||||
proxyReq.destroy(new Error('upstream_timeout'));
|
|
||||||
} catch {}
|
|
||||||
}, settings.upstreamTimeoutMs);
|
|
||||||
|
|
||||||
currentProxyReq = proxyReq;
|
|
||||||
|
|
||||||
proxyReq.on('error', (err) => {
|
|
||||||
clearTimeout(headersTimeout);
|
|
||||||
if (currentProxyReq === proxyReq) currentProxyReq = null;
|
|
||||||
release();
|
release();
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
|
|
||||||
markUpstreamFailure(upstream, err);
|
markUpstreamFailure(upstream, error);
|
||||||
|
|
||||||
if (!res.headersSent && canRetry(req) && attempted.size < upstreamCount) {
|
if (!res.headersSent && canRetry(req) && attempted.size < upstreamCount) {
|
||||||
attempt();
|
attempt();
|
||||||
@@ -301,12 +276,196 @@ function handleProxy(req, res, reqUrl) {
|
|||||||
} else {
|
} else {
|
||||||
res.destroy();
|
res.destroy();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
req.pipe(proxyReq);
|
const send = (target, redirectsLeft) => {
|
||||||
|
if (closed || res.headersSent) {
|
||||||
|
release();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lib = target.protocol === 'https:' ? https : http;
|
||||||
|
const headers =
|
||||||
|
redirectsLeft === MAX_REDIRECTS
|
||||||
|
? initialHeaders
|
||||||
|
: buildRequestHeaders(req, target);
|
||||||
|
|
||||||
|
let proxyReq;
|
||||||
|
let headersTimeout = null;
|
||||||
|
let settled = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
proxyReq = lib.request(
|
||||||
|
{
|
||||||
|
protocol: target.protocol,
|
||||||
|
hostname: target.hostname,
|
||||||
|
port: target.port || undefined,
|
||||||
|
path: `${target.pathname}${target.search}`,
|
||||||
|
method: req.method,
|
||||||
|
headers,
|
||||||
|
rejectUnauthorized: settings.rejectUnauthorized,
|
||||||
|
},
|
||||||
|
(proxyRes) => {
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(headersTimeout);
|
||||||
|
if (currentProxyReq === proxyReq) currentProxyReq = null;
|
||||||
|
|
||||||
|
const status = proxyRes.statusCode || 502;
|
||||||
|
const location = proxyRes.headers.location;
|
||||||
|
|
||||||
|
if (
|
||||||
|
streamRequest &&
|
||||||
|
isRedirect(status) &&
|
||||||
|
location &&
|
||||||
|
redirectsLeft > 0
|
||||||
|
) {
|
||||||
|
let nextTarget = null;
|
||||||
|
try {
|
||||||
|
nextTarget = new URL(location, target);
|
||||||
|
} catch {}
|
||||||
|
if (
|
||||||
|
nextTarget &&
|
||||||
|
(nextTarget.protocol === 'http:' ||
|
||||||
|
nextTarget.protocol === 'https:')
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`${requestKind(streamRequest)} redirect ${status} ${
|
||||||
|
upstream.label
|
||||||
|
} -> ${nextTarget.host} (${requestDescription(
|
||||||
|
req,
|
||||||
|
reqUrl
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
proxyRes.on('error', () => {});
|
||||||
|
proxyRes.resume();
|
||||||
|
send(nextTarget, redirectsLeft - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
streamRequest &&
|
||||||
|
canRetry(req) &&
|
||||||
|
shouldRetryStreamStatus(status) &&
|
||||||
|
attempted.size < upstreamCount
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
`${requestKind(streamRequest)} upstream ${status} from ${
|
||||||
|
upstream.label
|
||||||
|
}, trying next (${requestDescription(req, reqUrl)})`
|
||||||
|
);
|
||||||
|
proxyRes.on('error', () => {});
|
||||||
|
proxyRes.resume();
|
||||||
|
release();
|
||||||
|
attempt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status >= 400) {
|
||||||
|
console.warn(
|
||||||
|
`${requestKind(streamRequest)} upstream ${status} from ${
|
||||||
|
upstream.label
|
||||||
|
} (${requestDescription(req, reqUrl)})`
|
||||||
|
);
|
||||||
|
} else if (settings.logRequests) {
|
||||||
|
console.log(
|
||||||
|
`${requestKind(streamRequest)} ${status} via ${
|
||||||
|
upstream.label
|
||||||
|
} (${requestDescription(req, reqUrl)})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyOrigin = getProxyOrigin(req);
|
||||||
|
const rewriting = shouldRewriteBody(req, proxyRes, reqUrl.pathname);
|
||||||
|
const responseHeaders = filterResponseHeaders(
|
||||||
|
proxyRes.headers,
|
||||||
|
rewriting
|
||||||
|
);
|
||||||
|
|
||||||
|
if (responseHeaders.location) {
|
||||||
|
responseHeaders.location = rewriteUrlValue(
|
||||||
|
responseHeaders.location,
|
||||||
|
upstream,
|
||||||
|
proxyOrigin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
res.writeHead(status, responseHeaders);
|
||||||
|
} catch {
|
||||||
|
release();
|
||||||
|
try {
|
||||||
|
proxyRes.destroy();
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
res.destroy();
|
||||||
|
} catch {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyRes.on('close', release);
|
||||||
|
proxyRes.on('error', () => {
|
||||||
|
release();
|
||||||
|
if (!res.headersSent) {
|
||||||
|
sendText(res, 502, 'Upstream response error');
|
||||||
|
} else {
|
||||||
|
res.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rewriting) {
|
||||||
|
if (isAuthRequest(req, reqUrl)) {
|
||||||
|
sendAuthResponse(proxyRes, res, upstream, proxyOrigin);
|
||||||
|
} else {
|
||||||
|
const replacer = new ReplaceStream(
|
||||||
|
buildReplacements(upstream, proxyOrigin)
|
||||||
|
);
|
||||||
|
replacer.on('error', () => {
|
||||||
|
try {
|
||||||
|
res.destroy();
|
||||||
|
} catch {}
|
||||||
|
release();
|
||||||
|
});
|
||||||
|
proxyRes.pipe(replacer).pipe(res);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxyRes.pipe(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
fail(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
headersTimeout = setTimeout(() => {
|
||||||
|
try {
|
||||||
|
proxyReq.destroy(new Error('upstream_timeout'));
|
||||||
|
} catch {}
|
||||||
|
}, settings.upstreamTimeoutMs);
|
||||||
|
|
||||||
|
currentProxyReq = proxyReq;
|
||||||
|
|
||||||
|
proxyReq.on('error', (err) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(headersTimeout);
|
||||||
|
if (currentProxyReq === proxyReq) currentProxyReq = null;
|
||||||
|
fail(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bodySent || canRetry(req)) {
|
||||||
|
proxyReq.end();
|
||||||
|
} else {
|
||||||
|
bodySent = true;
|
||||||
|
req.pipe(proxyReq);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
send(initialTarget, MAX_REDIRECTS);
|
||||||
};
|
};
|
||||||
|
|
||||||
attempt();
|
attempt();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { handleProxy };
|
module.exports = { handleProxy };
|
||||||
@@ -102,25 +102,67 @@ function buildReplacements(upstream, proxyOrigin) {
|
|||||||
add(`"url":"${bareHost}"`, `"url":"${proxyHost}"`);
|
add(`"url":"${bareHost}"`, `"url":"${proxyHost}"`);
|
||||||
add(`"url": "${bareHost}"`, `"url": "${proxyHost}"`);
|
add(`"url": "${bareHost}"`, `"url": "${proxyHost}"`);
|
||||||
|
|
||||||
add(upstream.username, settings.proxyUsername);
|
// Credentials only in credential positions, so that a numeric or short
|
||||||
add(
|
// username/password can never rewrite unrelated stream IDs or names.
|
||||||
encodeURIComponent(upstream.username),
|
const pathForms = (value) => [
|
||||||
encodeURIComponent(settings.proxyUsername)
|
value,
|
||||||
);
|
encodeURIComponent(value),
|
||||||
add(
|
];
|
||||||
encodeQueryComponent(upstream.username),
|
const queryForms = (value) => [
|
||||||
encodeQueryComponent(settings.proxyUsername)
|
value,
|
||||||
);
|
encodeURIComponent(value),
|
||||||
|
encodeQueryComponent(value),
|
||||||
|
];
|
||||||
|
|
||||||
add(upstream.password, settings.proxyPassword);
|
const userPath = pathForms(upstream.username);
|
||||||
add(
|
const passPath = pathForms(upstream.password);
|
||||||
encodeURIComponent(upstream.password),
|
const proxyUserPath = pathForms(settings.proxyUsername);
|
||||||
encodeURIComponent(settings.proxyPassword)
|
const proxyPassPath = pathForms(settings.proxyPassword);
|
||||||
);
|
|
||||||
add(
|
userPath.forEach((user, index) => {
|
||||||
encodeQueryComponent(upstream.password),
|
passPath.forEach((pass, passIndex) => {
|
||||||
encodeQueryComponent(settings.proxyPassword)
|
add(
|
||||||
);
|
`/${user}/${pass}/`,
|
||||||
|
`/${proxyUserPath[index]}/${proxyPassPath[passIndex]}/`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const userQuery = queryForms(upstream.username);
|
||||||
|
const passQuery = queryForms(upstream.password);
|
||||||
|
const proxyUserQuery = queryForms(settings.proxyUsername);
|
||||||
|
const proxyPassQuery = queryForms(settings.proxyPassword);
|
||||||
|
|
||||||
|
userQuery.forEach((user, index) => {
|
||||||
|
add(`username=${user}`, `username=${proxyUserQuery[index]}`);
|
||||||
|
add(`"username":"${user}"`, `"username":"${proxyUserQuery[index]}"`);
|
||||||
|
add(`"username": "${user}"`, `"username": "${proxyUserQuery[index]}"`);
|
||||||
|
|
||||||
|
passQuery.forEach((pass, passIndex) => {
|
||||||
|
add(
|
||||||
|
`username=${user}&password=${pass}`,
|
||||||
|
`username=${proxyUserQuery[index]}&password=${proxyPassQuery[passIndex]}`
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
`password=${pass}&username=${user}`,
|
||||||
|
`password=${proxyPassQuery[passIndex]}&username=${proxyUserQuery[index]}`
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
`"username":"${user}","password":"${pass}"`,
|
||||||
|
`"username":"${proxyUserQuery[index]}","password":"${proxyPassQuery[passIndex]}"`
|
||||||
|
);
|
||||||
|
add(
|
||||||
|
`"username": "${user}", "password": "${pass}"`,
|
||||||
|
`"username": "${proxyUserQuery[index]}", "password": "${proxyPassQuery[passIndex]}"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
passQuery.forEach((pass, index) => {
|
||||||
|
add(`password=${pass}`, `password=${proxyPassQuery[index]}`);
|
||||||
|
add(`"password":"${pass}"`, `"password":"${proxyPassQuery[index]}"`);
|
||||||
|
add(`"password": "${pass}"`, `"password": "${proxyPassQuery[index]}"`);
|
||||||
|
});
|
||||||
|
|
||||||
return replacements;
|
return replacements;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ const settings = {
|
|||||||
),
|
),
|
||||||
rejectUnauthorized: process.env.PROXY_INSECURE_TLS !== '1',
|
rejectUnauthorized: process.env.PROXY_INSECURE_TLS !== '1',
|
||||||
allowBusyFallback: process.env.ALLOW_BUSY_FALLBACK !== '0',
|
allowBusyFallback: process.env.ALLOW_BUSY_FALLBACK !== '0',
|
||||||
|
publicUrl: (process.env.PUBLIC_URL || process.env.PROXY_PUBLIC_URL || '')
|
||||||
|
.trim()
|
||||||
|
.replace(/\/+$/, ''),
|
||||||
|
logRequests: process.env.LOG_REQUESTS === '1',
|
||||||
userAgent: process.env.USER_AGENT || 'xtream-proxy/3.0',
|
userAgent: process.env.USER_AGENT || 'xtream-proxy/3.0',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user