File size: 10,081 Bytes
7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 7a2f0ce a4dd161 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 259 260 |
import express from 'express';
import http from 'http'; // Нужен для второго сервера
import { spawn } from 'child_process';
import url from 'url'; // Нужен для formatRequestInfo для второго сервера
import fetch from 'node-fetch';
const PROXY_PORT = 3000; // Порт для CORS-прокси (будет туннелироваться)
const SECONDARY_SERVER_PORT = 7860; // Порт для второго сервера (Hugging Face / Hello World)
// Функция для форматирования информации о запросе (из оригинального server.js.txt)
// Немного адаптирована, чтобы можно было использовать для обоих типов серверов
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;
}
// --- Сервер 1: CORS Proxy на порту PROXY_PORT (будет туннелироваться) ---
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://')) {
targetUrlString = decoded;
}
} catch (e) { /* Игнорируем ошибку декодирования, используем как есть */ }
if (!targetUrlString) {
addCorsHeaders(res, clientRequestOrigin);
res.status(400).send('Target URL is missing in the path.');
return;
}
if (!targetUrlString.match(/^https?:\/\//i)) {
if (!targetUrlString.startsWith('http://') && !targetUrlString.startsWith('https://')) {
targetUrlString = 'http://' + targetUrlString;
}
}
let targetUrl;
try {
targetUrl = new URL(targetUrlString);
} catch (e) {
addCorsHeaders(res, clientRequestOrigin);
res.status(400).send(`Invalid target URL provided in path: ${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: ${req.url.substring(1)}`;
} 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 at ${req.url.substring(1)}`;
} else if (error.code === 'ERR_INVALID_URL') {
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(`Попытка запустить туннель localhost.run для порта ${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=10',
'nokey@localhost.run'
]);
tunnel.stdout.on('data', (data) => {
const output = data.toString();
console.log(`localhost.run stdout: ${output}`);
const urlMatch = output.match(/https?:\/\/[a-zA-Z0-9-]+\.(lhr\.life|lhr\.run|localhost\.run)/);
if (urlMatch) {
console.log(`>>> Туннель для PROXY (порт ${PROXY_PORT}) активен: ${urlMatch[0]}`);
}
});
tunnel.stderr.on('data', (data) => {
console.error(`localhost.run stderr: ${data}`);
});
tunnel.on('close', (code) => {
console.log(`Процесс localhost.run завершился с кодом ${code}`);
});
tunnel.on('error', (err) => {
console.error('Не удалось запустить процесс localhost.run:', err);
});
});
// --- Сервер 2: Простой HTTP-сервер на порту SECONDARY_SERVER_PORT (например, для Hugging Face / Hello World) ---
// Ты просил оставить "Hello World" или оригинальный сервер. Я оставлю оригинальный, который показывает инфо о запросе.
// Если нужен просто Hello World, замени тело обработчика.
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);
}); |