Publish Docker image / build-and-push (push) Successful in 1m39s
- Express проксирует на EVOBGP_API_URL с EVOBGP_API_TOKEN (без CORS) - фронт по умолчанию baseURL /api/evobgp; прямой режим через VITE или localStorage - Communities: подсказка про переменные контейнера; очистка URL включает прокси - DOCKER.md и Dockerfile.fast: документация переменных Made-with: Cursor
147 lines
4.4 KiB
JavaScript
147 lines
4.4 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);
|
|
const skip = new Set(['connection', 'transfer-encoding', 'content-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,
|
|
};
|