Files
router-lists-ui/backend/routes/evobgpProxyRoutes.js
T
denozord e99700a916
Publish Docker image / build-and-push (push) Successful in 1m45s
fix(evobgp): update header handling in evobgpProxyRoutes to preserve content-encoding
- Modified header processing to retain content-encoding, ensuring proper handling of gzip responses from upstream servers.
- Added comments for clarity on the importance of this change to avoid issues with client response interpretation.

Made-with: Cursor
2026-04-08 00:13:10 +07:00

149 lines
4.6 KiB
JavaScript

const http = require('http');
const https = require('https');
const { URL } = require('url');
/**
* Нормализует базовый URL API EvoBGP до вида …/v1 (без завершающего слэша).
*/
function normalizeEvobgpBaseUrl(raw) {
if (!raw || !String(raw).trim()) return '';
const input = String(raw).trim();
try {
const u = new URL(input);
let path = (u.pathname || '').replace(/\/+$/, '');
if (!path || path === '/') {
u.pathname = '/v1';
} else if (!/\/v1$/i.test(path)) {
u.pathname = `${path}/v1`;
} else {
u.pathname = path;
}
return `${u.origin}${u.pathname}`.replace(/\/+$/, '');
} catch {
return '';
}
}
/**
* Прокси EvoBGP: браузер бьёт в тот же origin (/api/evobgp/…), сервер добавляет Bearer и ходит в EvoBGP по сети Docker (без CORS).
*/
function createEvobgpProxy({ logger = console } = {}) {
return function evobgpProxy(req, res) {
const base = normalizeEvobgpBaseUrl(process.env.EVOBGP_API_URL || '');
const token = String(process.env.EVOBGP_API_TOKEN || '').trim();
if (!base) {
return res.status(503).json({
code: 'E_EVOBGP_PROXY_NOT_CONFIGURED',
message:
'Прокси EvoBGP не настроен: задайте EVOBGP_API_URL (и при необходимости EVOBGP_API_TOKEN) в окружении контейнера.',
});
}
if (!token) {
return res.status(503).json({
code: 'E_EVOBGP_PROXY_NO_TOKEN',
message: 'Прокси EvoBGP: не задан EVOBGP_API_TOKEN в окружении сервера.',
});
}
const suffix = req.url && req.url.length ? req.url : '/';
const targetUrl = `${base}${suffix.startsWith('/') ? suffix : `/${suffix}`}`;
let parsed;
try {
parsed = new URL(targetUrl);
} catch {
return res.status(500).json({
code: 'E_EVOBGP_PROXY_BAD_URL',
message: 'Некорректный EVOBGP_API_URL',
});
}
const lib = parsed.protocol === 'https:' ? https : http;
const headers = { ...req.headers };
delete headers.host;
delete headers.connection;
delete headers['content-length'];
delete headers.cookie;
headers.authorization = `Bearer ${token}`;
headers.host = parsed.host;
const opts = {
hostname: parsed.hostname,
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
path: parsed.pathname + parsed.search,
method: req.method,
headers,
timeout: Math.min(120000, Number(process.env.EVOBGP_PROXY_TIMEOUT_MS) || 60000),
};
const clientReq = lib.request(opts, (clientRes) => {
res.status(clientRes.statusCode);
// Не отбрасываем content-encoding: иначе upstream может отдать gzip-тело,
// а клиент не узнает, что его нужно распаковать (в браузере будет «мусор»).
const skip = new Set(['connection', 'transfer-encoding']);
Object.keys(clientRes.headers).forEach((k) => {
if (!skip.has(k.toLowerCase())) {
res.setHeader(k, clientRes.headers[k]);
}
});
clientRes.pipe(res);
});
clientReq.on('error', (err) => {
try {
(req.log || logger).error({ err: err.message, target: targetUrl }, 'evobgp proxy');
} catch {
/* no-op */
}
if (!res.headersSent) {
res.status(502).json({
code: 'E_EVOBGP_PROXY_UPSTREAM',
message: err.message || 'Не удалось связаться с EvoBGP',
});
}
});
clientReq.on('timeout', () => {
clientReq.destroy();
if (!res.headersSent) {
res.status(504).json({
code: 'E_EVOBGP_PROXY_TIMEOUT',
message: 'Таймаут запроса к EvoBGP',
});
}
});
if (req.method === 'OPTIONS' || req.method === 'GET' || req.method === 'HEAD') {
clientReq.end();
return;
}
if (req.method === 'DELETE') {
clientReq.end();
return;
}
const body =
req.body !== undefined && req.body !== null && typeof req.body === 'object'
? JSON.stringify(req.body)
: typeof req.body === 'string'
? req.body
: '';
if (body) {
const buf = Buffer.from(body, 'utf8');
clientReq.setHeader('Content-Type', req.headers['content-type'] || 'application/json');
clientReq.setHeader('Content-Length', buf.length);
clientReq.end(buf);
} else {
clientReq.end();
}
};
}
module.exports = {
createEvobgpProxy,
normalizeEvobgpBaseUrl,
};