File size: 9,568 Bytes
7a2f0ce 4a537f4 7a2f0ce 4a537f4 a4dd161 7a2f0ce 4a537f4 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce 4a537f4 7a2f0ce a4dd161 4a537f4 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 7a2f0ce a4dd161 0e5f7f6 a4dd161 4a537f4 a4dd161 4a537f4 a4dd161 4a537f4 a4dd161 4a537f4 a4dd161 4a537f4 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 0e5f7f6 a4dd161 5a7f683 7a2f0ce a4dd161 4a537f4 0e5f7f6 4a537f4 7a2f0ce 4a537f4 5a7f683 7a2f0ce 4a537f4 7a2f0ce 4a537f4 7a2f0ce 5a7f683 7a2f0ce 5a7f683 7a2f0ce a4dd161 4a537f4 a4dd161 7a2f0ce a4dd161 4a537f4 7a2f0ce |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
import express from 'express';
import http from 'http';
import { spawn } from 'child_process';
import url from 'url';
import fetch from 'node-fetch';
const PROXY_PORT = 3000;
const SECONDARY_SERVER_PORT = 7860;
function formatRequestInfo(req, serverType = 'express') {
let info = 'ИНФОРМАЦИЯ О ЗАПРОСЕ:\n\n';
let reqUrl, queryParams, clientIp;
if (serverType === 'express') {
reqUrl = req.originalUrl;
queryParams = req.query;
clientIp = req.ip;
} else { // 'http'
reqUrl = req.url;
queryParams = url.parse(req.url, true).query;
clientIp = req.socket.remoteAddress;
}
info += `Метод запроса: ${req.method}\n`;
info += `URL: ${reqUrl}\n`;
info += `IP клиента: ${clientIp}\n\n`;
info += 'QUERY ПАРАМЕТРЫ:\n';
if (Object.keys(queryParams).length === 0) {
info += '(нет параметров)\n';
} else {
for (const [key, value] of Object.entries(queryParams)) {
info += `${key}: ${value}\n`;
}
}
info += '\n';
info += 'ЗАГОЛОВКИ ЗАПРОСА:\n';
const headers = req.headers;
for (const [key, value] of Object.entries(headers)) {
info += `${key}: ${value}\n`;
}
info += '\nИНФОРМАЦИЯ О БРАУЗЕРЕ:\n';
info += headers['user-agent'] || 'Информация недоступна';
return info;
}
const proxyApp = express();
proxyApp.use(express.raw({
type: '*/*',
limit: '100mb'
}));
function addCorsHeaders(res, clientRequestOrigin) {
if (clientRequestOrigin) {
res.setHeader('Access-Control-Allow-Origin', clientRequestOrigin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Vary', 'Origin');
} else {
res.setHeader('Access-Control-Allow-Origin', '*');
}
}
proxyApp.options('*', (req, res) => {
const origin = req.headers.origin;
addCorsHeaders(res, origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD');
const requestedHeaders = req.headers['access-control-request-headers'];
if (requestedHeaders) {
res.setHeader('Access-Control-Allow-Headers', requestedHeaders);
} else {
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, X-CSRF-Token, Accept, Origin, User-Agent');
}
res.setHeader('Access-Control-Max-Age', '86400');
res.status(204).end();
});
proxyApp.get('/', (req, res) => {
const origin = req.headers.origin;
addCorsHeaders(res, origin);
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(`CORS Proxy is running on port ${PROXY_PORT}. Append target URL to proxy, e.g., /http://example.com. Tunnel will be established for this port.`);
});
proxyApp.all('*', async (req, res) => {
if (req.path === '/') {
return;
}
const clientRequestOrigin = req.headers.origin;
try {
let targetUrlString = req.url.substring(1);
try {
const decoded = decodeURIComponent(targetUrlString);
if (decoded.startsWith('http://') || decoded.startsWith('https://') || decoded.match(/^[a-zA-Z]+:\/[^/]/)) { // Check also for scheme:/non-slash
targetUrlString = decoded;
}
} catch (e) { /* Игнорируем ошибку декодирования, используем как есть, если не смогли декодировать */ }
// Попытка исправить URL вида "scheme:/host" в "scheme://host"
// Это может произойти, если промежуточный прокси (например, serveo.net) нормализует "//" в "/" в пути
const mangledSchemeMatch = targetUrlString.match(/^([a-zA-Z]+):\/([^\/].*)$/);
if (mangledSchemeMatch) {
targetUrlString = `${mangledSchemeMatch[1]}://${mangledSchemeMatch[2]}`;
}
if (!targetUrlString) {
addCorsHeaders(res, clientRequestOrigin);
res.status(400).send('Target URL is missing in the path.');
return;
}
// Если после всех манипуляций URL все еще не начинается с http:// или https://,
// считаем, что это просто имя хоста, и добавляем http://
if (!targetUrlString.match(/^https?:\/\//i)) {
targetUrlString = 'http://' + targetUrlString;
}
let targetUrl;
try {
targetUrl = new URL(targetUrlString);
} catch (e) {
addCorsHeaders(res, clientRequestOrigin);
res.status(400).send(`Invalid target URL provided: "${targetUrlString}". Error: ${e.message}`);
return;
}
const requestHeaders = {...req.headers};
delete requestHeaders['host'];
delete requestHeaders['content-length'];
delete requestHeaders['connection'];
const proxyResponse = await fetch(targetUrl.toString(), {
method: req.method,
headers: requestHeaders,
body: (req.method !== 'GET' && req.method !== 'HEAD' && req.body && req.body.length > 0) ? req.body : undefined,
redirect: 'manual',
compress: false
});
proxyResponse.headers.forEach((value, key) => {
const lowerKey = key.toLowerCase();
if (!lowerKey.startsWith('access-control-') &&
!['strict-transport-security', 'content-security-policy', 'public-key-pins',
'transfer-encoding', 'connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade'].includes(lowerKey)
) {
res.setHeader(key, value);
}
});
addCorsHeaders(res, clientRequestOrigin);
const exposedHeaders = Array.from(proxyResponse.headers.keys()).filter(key =>
!key.toLowerCase().startsWith('access-control-') &&
key.toLowerCase() !== 'transfer-encoding' &&
key.toLowerCase() !== 'connection'
).join(', ');
if (exposedHeaders) {
res.setHeader('Access-Control-Expose-Headers', exposedHeaders);
} else {
res.setHeader('Access-Control-Expose-Headers', '*');
}
res.status(proxyResponse.status);
if (proxyResponse.body) {
proxyResponse.body.pipe(res);
} else {
res.end();
}
} catch (error) {
console.error('Proxy error:', error); // Логируем полную ошибку для диагностики
if (!res.headersSent) {
addCorsHeaders(res, clientRequestOrigin);
let statusCode = 500;
let message = 'Proxy error occurred.';
if (error.code === 'ENOTFOUND' || error.cause?.code === 'ENOTFOUND') {
statusCode = 404; message = `Target host not found for "${req.url.substring(1)}" (resolved as "${error.hostname || 'unknown'}").`;
} else if (error.message?.includes('Invalid URL') || (error.name === 'TypeError' && error.message?.includes('Invalid URL'))) {
statusCode = 400; message = `Invalid target URL in path: "${req.url.substring(1)}". Detail: ${error.message}`;
} else if (error.code === 'ECONNREFUSED' || error.cause?.code === 'ECONNREFUSED') {
statusCode = 502; message = `Bad Gateway: Could not connect to target server for "${req.url.substring(1)}".`;
} else if (error.code === 'ERR_INVALID_URL') { // Node.js specific URL error
statusCode = 400; message = `Invalid target URL format in path: "${req.url.substring(1)}".`;
}
res.status(statusCode).send(message);
} else {
res.end();
}
}
});
proxyApp.listen(PROXY_PORT, '0.0.0.0', () => {
console.log(`CORS Proxy server (для туннеля) слушает порт ${PROXY_PORT}`);
console.log(`Попытка запустить туннель serveo.net для порта ${PROXY_PORT}...`);
const tunnel = spawn('ssh', [
'-R', `80:localhost:${PROXY_PORT}`,
'-o', 'StrictHostKeyChecking=no',
'-o', 'UserKnownHostsFile=/dev/null',
'-o', 'ServerAliveInterval=60',
'-o', 'ExitOnForwardFailure=yes',
'-o', 'ConnectTimeout=20',
'serveo.net'
]);
tunnel.stdout.on('data', (data) => {
const output = data.toString();
console.log(`serveo.net stdout: ${output}`);
const urlMatch = output.match(/https?:\/\/[a-zA-Z0-9-]+\.serveo\.net/);
if (urlMatch) {
console.log(`>>> Туннель для PROXY (порт ${PROXY_PORT}) через serveo.net активен: ${urlMatch[0]}`);
}
});
tunnel.stderr.on('data', (data) => {
console.error(`serveo.net stderr: ${data}`);
});
tunnel.on('close', (code) => {
console.log(`Процесс serveo.net завершился с кодом ${code}`);
});
tunnel.on('error', (err) => {
console.error('Не удалось запустить процесс serveo.net:', err);
});
});
const secondaryServer = http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(`Hello World from secondary server on port ${SECONDARY_SERVER_PORT}!`);
} else {
const requestInfo = formatRequestInfo(req, 'http');
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(requestInfo);
}
});
secondaryServer.listen(SECONDARY_SERVER_PORT, '0.0.0.0', () => {
console.log(`Второй HTTP-сервер (для Hugging Face / Hello World) слушает порт ${SECONDARY_SERVER_PORT}`);
});
process.on('SIGINT', () => {
console.log('Получен SIGINT. Завершение...');
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('Получен SIGTERM. Завершение...');
process.exit(0);
}); |