Spaces:
Sleeping
Sleeping
Update index.js
Browse files
index.js
CHANGED
|
@@ -2,6 +2,8 @@ import express from 'express';
|
|
| 2 |
import fetch from 'node-fetch';
|
| 3 |
|
| 4 |
const app = express();
|
|
|
|
|
|
|
| 5 |
app.use(express.raw({type: '*/*'}));
|
| 6 |
|
| 7 |
// Hello World на главной
|
|
@@ -11,49 +13,52 @@ app.get('/', (req, res) => {
|
|
| 11 |
|
| 12 |
// Прокси для всех остальных путей
|
| 13 |
app.all('*', async (req, res) => {
|
| 14 |
-
// Этот if (req.url === '/') вероятно, не нужен, если app.get('/') обрабатывает '/',
|
| 15 |
-
// но оставим для безопасности, если app.all('*') почему-то получит запрос к '/'
|
| 16 |
if (req.url === '/') {
|
| 17 |
-
//
|
| 18 |
-
// то здесь можно вернуть ошибку или специфический ответ.
|
| 19 |
-
// В данном случае, лучше ничего не делать, так как app.get('/') должен его поймать.
|
| 20 |
-
return;
|
| 21 |
}
|
| 22 |
|
| 23 |
try {
|
| 24 |
-
|
| 25 |
-
// req.url будет выглядеть как "/https://anotherurl.com?param=value"
|
| 26 |
-
// Мы удаляем первый символ '/', чтобы получить "https://anotherurl.com?param=value"
|
| 27 |
-
const targetUrl = req.url.substring(1);
|
| 28 |
|
| 29 |
-
|
| 30 |
-
if (!targetUrl) {
|
| 31 |
res.status(400).send('Target URL is missing in the path.');
|
| 32 |
return;
|
| 33 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
const response = await fetch(targetUrl, { // Используем
|
| 41 |
method: req.method,
|
| 42 |
-
headers,
|
| 43 |
-
// Отправляем тело запроса, если это не GET или HEAD запрос
|
| 44 |
body: (req.method !== 'GET' && req.method !== 'HEAD') ? req.body : undefined,
|
| 45 |
-
|
| 46 |
-
//
|
| 47 |
-
//
|
| 48 |
-
|
|
|
|
|
|
|
| 49 |
});
|
| 50 |
|
| 51 |
// Копируем все заголовки из ответа целевого сервера
|
|
|
|
| 52 |
Object.entries(response.headers.raw()).forEach(([key, value]) => {
|
| 53 |
-
// Некоторые
|
| 54 |
-
//
|
| 55 |
-
//
|
| 56 |
-
// Для безопасности, можно добавить фильтрацию специфичных заголовков здесь при необходимости.
|
| 57 |
if (value) {
|
| 58 |
res.setHeader(key, value.length === 1 ? value[0] : value);
|
| 59 |
}
|
|
@@ -61,16 +66,7 @@ app.all('*', async (req, res) => {
|
|
| 61 |
|
| 62 |
res.status(response.status);
|
| 63 |
|
| 64 |
-
//
|
| 65 |
-
if (response.headers.get('content-type')?.includes('text/event-stream')) {
|
| 66 |
-
// res.setHeader('Content-Type', 'text/event-stream'); // Уже установлено циклом выше
|
| 67 |
-
response.body.pipe(res);
|
| 68 |
-
return;
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
// Regular response
|
| 72 |
-
// Используем response.body.pipe(res) для более эффективной передачи больших ответов
|
| 73 |
-
// response.buffer() загружает весь ответ в память перед отправкой.
|
| 74 |
if (response.body) {
|
| 75 |
response.body.pipe(res);
|
| 76 |
} else {
|
|
@@ -78,22 +74,34 @@ app.all('*', async (req, res) => {
|
|
| 78 |
}
|
| 79 |
|
| 80 |
} catch (error) {
|
| 81 |
-
console.error(
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
}
|
| 94 |
});
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
| 99 |
});
|
|
|
|
| 2 |
import fetch from 'node-fetch';
|
| 3 |
|
| 4 |
const app = express();
|
| 5 |
+
// Увеличим лимит размера тела запроса, если необходимо (по умолчанию 100kb)
|
| 6 |
+
// express.raw({type: '*/*', limit: '10mb'})
|
| 7 |
app.use(express.raw({type: '*/*'}));
|
| 8 |
|
| 9 |
// Hello World на главной
|
|
|
|
| 13 |
|
| 14 |
// Прокси для всех остальных путей
|
| 15 |
app.all('*', async (req, res) => {
|
|
|
|
|
|
|
| 16 |
if (req.url === '/') {
|
| 17 |
+
return; // Уже обработано app.get('/')
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
| 19 |
|
| 20 |
try {
|
| 21 |
+
const targetUrlString = req.url.substring(1);
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
+
if (!targetUrlString) {
|
|
|
|
| 24 |
res.status(400).send('Target URL is missing in the path.');
|
| 25 |
return;
|
| 26 |
}
|
| 27 |
+
|
| 28 |
+
// Проверка на валидность URL перед использованием
|
| 29 |
+
let targetUrl;
|
| 30 |
+
try {
|
| 31 |
+
targetUrl = new URL(targetUrlString);
|
| 32 |
+
} catch (e) {
|
| 33 |
+
res.status(400).send(`Invalid target URL provided in path: ${targetUrlString}`);
|
| 34 |
+
return;
|
| 35 |
+
}
|
| 36 |
|
| 37 |
+
const requestHeaders = {...req.headers};
|
| 38 |
+
delete requestHeaders['host']; // host будет установлен fetch на основе targetUrl
|
| 39 |
+
// content-length будет пересчитан fetch из req.body, если req.body - Buffer или строка
|
| 40 |
+
// req.body здесь - это Buffer из express.raw(), так что fetch установит Content-Length
|
| 41 |
+
delete requestHeaders['content-length'];
|
| 42 |
+
|
| 43 |
|
| 44 |
+
const response = await fetch(targetUrl.toString(), { // Используем targetUrl.toString()
|
| 45 |
method: req.method,
|
| 46 |
+
headers: requestHeaders,
|
|
|
|
| 47 |
body: (req.method !== 'GET' && req.method !== 'HEAD') ? req.body : undefined,
|
| 48 |
+
redirect: 'manual',
|
| 49 |
+
// ---- КЛЮЧЕВОЕ ИЗМЕНЕНИЕ ----
|
| 50 |
+
// Говорим node-fetch не раскодировать ответ автоматически.
|
| 51 |
+
// Прокси будет передавать сжатый поток как есть.
|
| 52 |
+
compress: false
|
| 53 |
+
// --------------------------
|
| 54 |
});
|
| 55 |
|
| 56 |
// Копируем все заголовки из ответа целевого сервера
|
| 57 |
+
// Теперь, когда node-fetch не раскодирует, мы можем безопасно копировать Content-Encoding и Content-Length
|
| 58 |
Object.entries(response.headers.raw()).forEach(([key, value]) => {
|
| 59 |
+
// Некоторые "hop-by-hop" заголовки не должны копироваться,
|
| 60 |
+
// но для большинства это безопасно, особенно Connection, который Node.js обработает.
|
| 61 |
+
// Transfer-Encoding также будет управляться Node.js/Express при пайпинге.
|
|
|
|
| 62 |
if (value) {
|
| 63 |
res.setHeader(key, value.length === 1 ? value[0] : value);
|
| 64 |
}
|
|
|
|
| 66 |
|
| 67 |
res.status(response.status);
|
| 68 |
|
| 69 |
+
// Потоковая передача тела ответа
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
if (response.body) {
|
| 71 |
response.body.pipe(res);
|
| 72 |
} else {
|
|
|
|
| 74 |
}
|
| 75 |
|
| 76 |
} catch (error) {
|
| 77 |
+
console.error(`[${new Date().toISOString()}] Proxy error for ${req.method} ${req.url}:`, error);
|
| 78 |
+
if (!res.headersSent) {
|
| 79 |
+
if (error.code === 'ENOTFOUND') {
|
| 80 |
+
res.status(404).send(`Target host not found: ${req.url.substring(1)}`);
|
| 81 |
+
} else if (error.message && error.message.includes('Invalid URL')) { // Проверка может быть улучшена
|
| 82 |
+
res.status(400).send(`Invalid target URL in path: ${req.url.substring(1)}`);
|
| 83 |
+
} else if (error.code === 'ECONNREFUSED') {
|
| 84 |
+
res.status(502).send(`Bad Gateway: Could not connect to target server at ${req.url.substring(1)}`);
|
| 85 |
+
} else if (error.code === 'ERR_INVALID_URL') { // от new URL(targetUrlString)
|
| 86 |
+
res.status(400).send(`Invalid target URL format in path: ${req.url.substring(1)}`);
|
| 87 |
+
}
|
| 88 |
+
else {
|
| 89 |
+
res.status(500).send('Proxy error occurred.');
|
| 90 |
+
}
|
| 91 |
+
} else {
|
| 92 |
+
// Если заголовки уже отправлены, но ошибка произошла при стриминге тела,
|
| 93 |
+
// мы можем только завершить соединение или попытаться записать ошибку в лог.
|
| 94 |
+
// response.body.pipe(res) уже могло начать отправку.
|
| 95 |
+
console.error(`[${new Date().toISOString()}] Proxy error after headers sent for ${req.method} ${req.url}.`);
|
| 96 |
+
res.end(); // Пытаемся корректно завершить ответ, если это возможно.
|
| 97 |
}
|
| 98 |
}
|
| 99 |
});
|
| 100 |
|
| 101 |
+
const PORT = process.env.PORT || 7860;
|
| 102 |
+
const HOST = '0.0.0.0';
|
| 103 |
+
|
| 104 |
+
app.listen(PORT, HOST, () => {
|
| 105 |
+
console.log(`Server running on http://${HOST}:${PORT}`);
|
| 106 |
+
console.log(`Proxying requests. Example: http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}/https://www.google.com`);
|
| 107 |
});
|