-
Notifications
You must be signed in to change notification settings - Fork 26
/
proxyWeb.js
257 lines (227 loc) · 8.86 KB
/
proxyWeb.js
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
/*
* @Author: Sky.Sun
* @Date: 2018-07-11 15:09:50
* @Last Modified by: Sky.Sun
* @Last Modified time: 2019-05-14 10:55:01
*/
const httpProxy = require('http-proxy');
const cacheClient = require('./cache').cacheClient;
const configPath = require('./getConfigPath')();
const config = require(configPath);
const settings = require('./settings');
const debugMode = require('./debugMode');
const serverlog = require('serverlog-node');
const logger = serverlog.getLogger('node-proxy-webui');
const proxy = httpProxy.createProxyServer({
xfwd: true,
secure: false,
preserveHeaderKeyCase: true,
proxyTimeout: config.proxyTimeout
});
/**
* 代理转发的错误处理
*/
proxy.on('error', (err, req, res) => {
if (req) {
logger.error(`Proxy Server Error! URL: ${req.protocol}://${req.get('Host')}${req.originalUrl} Error: ${err.message}`);
if (req.query[debugMode.debugParam] === 'true') {
const html = debugMode.getDebugHtml('Internal Server Error', debugMode.getLogArray(res));
res.send(html);
} else {
res.sendStatus(500);
}
} else {
logger.error(`Proxy Server Error! Error: ${err.message}`);
}
});
/**
* 接收到转发服务器响应的后续处理
*/
proxy.on('proxyRes', (proxyRes, req, res) => {
const serverLog = res.get('X-Server-Log');
// URL 中存在调试参数
if (req.query[debugMode.debugParam] === 'true') {
// 判断是否是 html 类型响应,或者 404/500
const mimeType = proxyRes.headers['Content-Type'] || proxyRes.headers['content-type'] || '';
if (mimeType.includes('text/html') || ((proxyRes.statusCode === 404 || proxyRes.statusCode === 500) && mimeType.includes('text/plain'))) {
// 修正响应头为200,否则 nginx 会处理 404/500 错误而丢弃响应内容
proxyRes.statusCode = 200;
// 尝试获取所有后端日志
let logs = [];
if (serverLog) {
let logData = JSON.parse(decodeURIComponent(serverLog));
const currentHeader = proxyRes.headers['X-Server-Log'] || proxyRes.headers['x-server-log'];
if (currentHeader) {
logs = JSON.parse(decodeURIComponent(currentHeader));
logs = logData.concat(logs);
} else {
logs = logData;
}
}
// 保存原始方法
const _writeHead = res.writeHead;
let _writeHeadArgs;
const _end = res.end;
let body = '';
proxyRes.on('data', (data) => {
data = data.toString();
body += data;
});
// 重写内置方法
res.writeHead = (...writeHeadArgs) => {
_writeHeadArgs = writeHeadArgs;
};
res.write = () => { };
res.end = (...endArgs) => {
const output = debugMode.getDebugHtml(body, logs);
// 一定要重新设置 Content-Length,且不能使用 output.length,因为可能包含中文
if (proxyRes.headers && proxyRes.headers['content-length']) {
res.setHeader('content-length', Buffer.byteLength(output));
}
res.setHeader('content-type', 'text/html; charset=utf-8');
res.setHeader('transfer-encoding', '');
res.setHeader('cache-control', 'no-cache');
_writeHead.apply(res, _writeHeadArgs);
if (body.length) {
_end.apply(res, [output]);
} else {
_end.apply(res, endArgs);
}
}
}
}
// 如果 proxy 存在日志头且有数据,且客户端安装了 ServerLog 并开启了日志监听
if (serverLog && req.headers['request-server-log'] === 'enabled') {
let logData = JSON.parse(decodeURIComponent(serverLog));
/**
* 转发到的服务器(如 node_pro)返回的日志头
* 这里要兼容下大小写
*/
const currentHeader = proxyRes.headers['X-Server-Log'] || proxyRes.headers['x-server-log'];
let updateHeader = '';
if (currentHeader) {
updateHeader = JSON.parse(decodeURIComponent(currentHeader));
updateHeader = logData.concat(updateHeader);
updateHeader = encodeURIComponent(JSON.stringify(updateHeader));
} else {
updateHeader = encodeURIComponent(JSON.stringify(logData));
}
proxyRes.headers['X-Server-Log'] = updateHeader;
}
// 如果 redisKey 存在,且返回状态码为 200,且内容类型为 html,且接口调用正常,才设置缓存
const mimeType = proxyRes.headers['Content-Type'] || proxyRes.headers['content-type'] || '';
const hasServiceError = proxyRes.headers['X-Service-Error'] || proxyRes.headers['x-service-error'];
if (req.redisKey && proxyRes.statusCode === 200 && mimeType.includes('text/html')) {
if (hasServiceError) {
logger.warn('服务端存在接口调用异常,本次数据将不会缓存!');
} else if (req.query[debugMode.debugParam] === 'true') {
logger.warn('调试模式不设置缓存!');
} else {
let resBody = '';
proxyRes.on('data', chunk => {
resBody += chunk.toString();
});
proxyRes.on('end', () => {
cacheClient.set(req.redisKey, resBody, 'EX', req.redisExpired, err => {
if (err) {
logger.error('Redis Set Error:', err.message);
}
});
})
}
}
});
/**
* 生成随机数
*
* @param {Number} min 随机数下限
* @param {Number} max 随机数上限
* @returns
*/
function rand(min, max) {
return Math.random() * (max - min) + min;
}
/**
* 按权重随机选取一项
*
* @param {Array} list 要随机的数组
* @param {Array} weight 权重数组
* @returns
*/
function getRandomItem(list, weight) {
// 必须是非空且数量一致的数组
if (Array.isArray(list) && Array.isArray(weight) && list.length > 0 && list.length === weight.length) {
// 权重数组必须都是数字
if (weight.every(t => typeof t === 'number')) {
const totalWeight = weight.reduce((prev, cur) => {
return prev + cur;
});
const randomNum = rand(0, totalWeight);
let weightSum = 0;
for (let i = 0; i < list.length; i++) {
weightSum += weight[i];
if (randomNum <= weightSum) {
return list[i];
}
}
} else {
return null;
}
} else {
return null;
}
}
/**
* 根据配置的服务器权重分配服务器转发
*
* @param {object} { req, res, serverId, serverName, logMsg }
* @returns
*/
function proxyWeb({ req, res, serverId, serverName, logMsg }) {
const servers = settings.getServers();
let server;
if (serverId) {
server = servers.find(t => t.id === serverId);
} else if (serverName) {
server = servers.find(t => t.name === serverName);
}
if (server) {
logMsg += `转发至${server.name}`;
const addresses = server.addresses;
if (addresses && Array.isArray(addresses) && addresses.length > 0) {
const list = addresses.map(t => t.address);
const weight = addresses.map(t => Number(t.weight));
const proxyAddress = getRandomItem(list, weight);
// 如果没有获取到随机服务器,说明配置异常
if (!proxyAddress) {
logMsg += ` --> 错误:list或weight数据格式不正确!list: ${JSON.stringify(list)} weight: ${JSON.stringify(weight)}`
logger.error(logMsg);
res.sendStatus(500);
return;
}
// proxy 参数
const option = {
target: proxyAddress
};
// URL 中存在调试参数,设置为允许修改响应
if (req.query[debugMode.debugParam] === 'true') {
option.selfHandleResponse = true;
}
logMsg += ` (host: ${proxyAddress})`;
setImmediate(() => {
logger.info(logMsg);
})
// 进行转发
proxy.web(req, res, option);
} else {
logMsg += ` --> 错误:服务器${serverName}配置异常!servers: ${JSON.stringify(servers)}`;
logger.error(logMsg);
res.sendStatus(500);
}
} else {
logMsg += `错误:未找到服务器!serverId: ${serverId}, serverName: ${serverName}, servers: ${JSON.stringify(servers)}`;
logger.error(logMsg);
res.sendStatus(500);
}
}
module.exports = proxyWeb;