WalleGriffkinder commited on
Commit
87dbf53
·
verified ·
1 Parent(s): f8a9810

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +103 -40
index.js CHANGED
@@ -1,72 +1,121 @@
1
  import express from 'express';
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 на главной
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  app.get('/', (req, res) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  res.send('Hello World');
12
  });
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
  }
65
  });
66
 
 
 
 
 
 
 
 
 
67
  res.status(response.status);
68
 
69
- // Потоковая передача тела ответа
70
  if (response.body) {
71
  response.body.pipe(res);
72
  } else {
@@ -76,32 +125,46 @@ app.all('*', async (req, res) => {
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
  });
 
1
  import express from 'express';
2
+ import fetch, { Headers as FetchHeaders } from 'node-fetch';
3
 
4
  const app = express();
 
 
5
  app.use(express.raw({type: '*/*'}));
6
 
7
+ app.options('*', (req, res) => {
8
+ const origin = req.headers.origin;
9
+
10
+ if (origin) {
11
+ res.setHeader('Access-Control-Allow-Origin', origin);
12
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
13
+ } else {
14
+ res.setHeader('Access-Control-Allow-Origin', '*');
15
+ }
16
+
17
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD');
18
+
19
+ const requestedHeaders = req.headers['access-control-request-headers'];
20
+ if (requestedHeaders) {
21
+ res.setHeader('Access-Control-Allow-Headers', requestedHeaders);
22
+ } else {
23
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With, X-CSRF-Token, Accept, Origin');
24
+ }
25
+
26
+ res.setHeader('Access-Control-Max-Age', '86400');
27
+
28
+ if (origin) {
29
+ res.setHeader('Vary', 'Origin');
30
+ }
31
+
32
+ res.status(204).end();
33
+ });
34
+
35
+
36
  app.get('/', (req, res) => {
37
+ const origin = req.headers.origin;
38
+ if (origin) {
39
+ res.setHeader('Access-Control-Allow-Origin', origin);
40
+ res.setHeader('Vary', 'Origin');
41
+ if (req.method === 'OPTIONS') {
42
+ res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
43
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
44
+ res.status(204).end();
45
+ return;
46
+ }
47
+ } else {
48
+ res.setHeader('Access-Control-Allow-Origin', '*');
49
+ }
50
  res.send('Hello World');
51
  });
52
 
 
53
  app.all('*', async (req, res) => {
54
  if (req.url === '/') {
55
+ return;
56
  }
57
 
58
+ const clientRequestOrigin = req.headers.origin;
59
+
60
  try {
61
  const targetUrlString = req.url.substring(1);
62
 
63
  if (!targetUrlString) {
64
+ addCorsHeaders(res, clientRequestOrigin);
65
  res.status(400).send('Target URL is missing in the path.');
66
  return;
67
  }
68
 
 
69
  let targetUrl;
70
  try {
71
  targetUrl = new URL(targetUrlString);
72
  } catch (e) {
73
+ addCorsHeaders(res, clientRequestOrigin);
74
  res.status(400).send(`Invalid target URL provided in path: ${targetUrlString}`);
75
  return;
76
  }
77
 
78
  const requestHeaders = {...req.headers};
79
+ delete requestHeaders['host'];
 
 
80
  delete requestHeaders['content-length'];
81
 
82
+ const response = await fetch(targetUrl.toString(), {
 
83
  method: req.method,
84
  headers: requestHeaders,
85
  body: (req.method !== 'GET' && req.method !== 'HEAD') ? req.body : undefined,
86
  redirect: 'manual',
 
 
 
87
  compress: false
 
88
  });
89
 
90
+ response.headers.forEach((value, key) => {
91
+ const lowerKey = key.toLowerCase();
92
+ if (!lowerKey.startsWith('access-control-') &&
93
+ lowerKey !== 'strict-transport-security' &&
94
+ lowerKey !== 'content-security-policy' &&
95
+ lowerKey !== 'public-key-pins' &&
96
+ lowerKey !== 'transfer-encoding' &&
97
+ lowerKey !== 'connection' &&
98
+ lowerKey !== 'keep-alive' &&
99
+ lowerKey !== 'proxy-authenticate' &&
100
+ lowerKey !== 'proxy-authorization' &&
101
+ lowerKey !== 'te' &&
102
+ lowerKey !== 'trailers' &&
103
+ lowerKey !== 'upgrade'
104
+ ) {
105
+ res.setHeader(key, value);
106
  }
107
  });
108
 
109
+ addCorsHeaders(res, clientRequestOrigin);
110
+ const exposedHeaders = Array.from(response.headers.keys()).filter(key =>
111
+ !key.toLowerCase().startsWith('access-control-')
112
+ ).join(', ');
113
+ if (exposedHeaders) {
114
+ res.setHeader('Access-Control-Expose-Headers', exposedHeaders || '*');
115
+ }
116
+
117
  res.status(response.status);
118
 
 
119
  if (response.body) {
120
  response.body.pipe(res);
121
  } else {
 
125
  } catch (error) {
126
  console.error(`[${new Date().toISOString()}] Proxy error for ${req.method} ${req.url}:`, error);
127
  if (!res.headersSent) {
128
+ addCorsHeaders(res, clientRequestOrigin);
129
+ let statusCode = 500;
130
+ let message = 'Proxy error occurred.';
131
+
132
  if (error.code === 'ENOTFOUND') {
133
+ statusCode = 404;
134
+ message = `Target host not found: ${req.url.substring(1)}`;
135
+ } else if (error.message && error.message.includes('Invalid URL')) {
136
+ statusCode = 400;
137
+ message = `Invalid target URL in path: ${req.url.substring(1)}`;
138
  } else if (error.code === 'ECONNREFUSED') {
139
+ statusCode = 502;
140
+ message = `Bad Gateway: Could not connect to target server at ${req.url.substring(1)}`;
141
+ } else if (error.code === 'ERR_INVALID_URL') {
142
+ statusCode = 400;
143
+ message = `Invalid target URL format in path: ${req.url.substring(1)}`;
 
144
  }
145
+ res.status(statusCode).send(message);
146
  } else {
 
 
 
147
  console.error(`[${new Date().toISOString()}] Proxy error after headers sent for ${req.method} ${req.url}.`);
148
+ res.end();
149
  }
150
  }
151
  });
152
 
153
+ function addCorsHeaders(res, clientRequestOrigin) {
154
+ if (clientRequestOrigin) {
155
+ res.setHeader('Access-Control-Allow-Origin', clientRequestOrigin);
156
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
157
+ res.setHeader('Vary', 'Origin');
158
+ } else {
159
+ res.setHeader('Access-Control-Allow-Origin', '*');
160
+ }
161
+ }
162
+
163
  const PORT = process.env.PORT || 7860;
164
  const HOST = '0.0.0.0';
165
 
166
  app.listen(PORT, HOST, () => {
167
+ console.log(`Server running on http://${HOST}:${PORT} and acting as a CORS proxy.`);
168
+ console.log(`Handles OPTIONS preflight requests.`);
169
+ console.log(`Example: http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}/https://www.google.com`);
170
  });