-
Notifications
You must be signed in to change notification settings - Fork 283
Expand file tree
/
Copy pathfile-proxy.js
More file actions
242 lines (197 loc) · 8.26 KB
/
file-proxy.js
File metadata and controls
242 lines (197 loc) · 8.26 KB
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
/**
* 远程文件代理控制器模块
* 提供远程文件的 HTTP 直链代理访问功能
* @module file-proxy-controller
*/
import {
createHealthResponse,
createStatusResponse,
decodeParam,
forwardResponseHeaders,
getDefaultHeaders,
makeRemoteRequest,
PROXY_CONSTANTS,
setCorsHeaders,
verifyAuth
} from '../utils/proxy-util.js';
/**
* 远程文件代理控制器插件
* @param {Object} fastify - Fastify实例
* @param {Object} options - 插件选项
* @param {Function} done - 完成回调
*/
export default (fastify, options, done) => {
// 请求缓存
const requestCache = new Map();
/**
* 远程文件代理健康检查接口
* GET /file-proxy/health - 检查远程文件代理服务状态
*/
fastify.get('/file-proxy/health', async (request, reply) => {
// console.log(`[fileProxyController] Health check request`);
setCorsHeaders(reply);
const healthData = createHealthResponse('Remote File Proxy', {
cache: {
requests: requestCache.size
}
});
return reply.send(healthData);
});
/**
* 远程文件代理接口
* GET/HEAD /file-proxy/proxy - 代理远程文件
*/
fastify.route({
method: ['GET', 'HEAD'],
url: '/file-proxy/proxy',
handler: async (request, reply) => {
// 验证身份认证
if (!verifyAuth(request, reply)) {
return;
}
const { url: urlParam, headers: headersParam } = request.query;
// console.log(`[fileProxyController] ${request.method} request for URL: ${urlParam}`);
// 验证必需参数
if (!urlParam) {
return reply.status(400).send({ error: 'Missing required parameter: url' });
}
try {
// 解码 URL 参数
const targetUrl = decodeParam(urlParam, false);
// 验证 URL 格式
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
return reply.status(400).send({ error: 'Invalid URL: must start with http:// or https://' });
}
// 解码 headers 参数
const customHeaders = decodeParam(headersParam, true);
// 合并默认请求头和自定义请求头
const defaultHeaders = getDefaultHeaders(request);
const requestHeaders = { ...defaultHeaders, ...customHeaders };
// 处理 Range 请求
const range = request.headers.range;
try {
// 发起远程请求
const remoteResponse = await makeRemoteRequest(
targetUrl,
requestHeaders,
request.method,
range
);
// 检查远程响应状态
if (remoteResponse.statusCode >= 400) {
return reply.status(remoteResponse.statusCode).send({
error: `Remote server error: ${remoteResponse.statusCode}`
});
}
// 设置响应状态码
reply.status(remoteResponse.statusCode);
// 转发响应头和设置CORS
forwardResponseHeaders(reply, remoteResponse.headers);
setCorsHeaders(reply);
// 对于 HEAD 请求,只返回头部信息
if (request.method === 'HEAD') {
return reply.send();
}
// 对于 GET 请求,返回文件流
return reply.send(remoteResponse.stream);
} catch (requestError) {
console.error('[fileProxyController] Remote request error:', requestError);
return reply.status(502).send({
error: `Failed to fetch remote file: ${requestError.message}`
});
}
} catch (error) {
console.error('[fileProxyController] Request processing error:', error);
return reply.status(500).send({ error: error.message });
}
}
});
/**
* 远程文件信息获取接口
* GET /file-proxy/info - 获取远程文件信息(HEAD 请求)
*/
fastify.get('/file-proxy/info', async (request, reply) => {
// 验证身份认证
if (!verifyAuth(request, reply)) {
return;
}
const { url: urlParam, headers: headersParam } = request.query;
// console.log(`[fileProxyController] Info request for URL: ${urlParam}`);
// 验证必需参数
if (!urlParam) {
return reply.status(400).send({ error: 'Missing required parameter: url' });
}
try {
// 解码 URL 参数
const targetUrl = decodeParam(urlParam, false);
// 验证 URL 格式
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
return reply.status(400).send({ error: 'Invalid URL: must start with http:// or https://' });
}
// 解码 headers 参数
const customHeaders = decodeParam(headersParam, true);
// 合并默认请求头和自定义请求头
const defaultHeaders = getDefaultHeaders(request);
const requestHeaders = { ...defaultHeaders, ...customHeaders };
try {
// 发起 HEAD 请求获取文件信息
const remoteResponse = await makeRemoteRequest(targetUrl, requestHeaders, 'HEAD');
if (remoteResponse.statusCode >= 400) {
return reply.status(remoteResponse.statusCode).send({
error: `Remote server error: ${remoteResponse.statusCode}`
});
}
// 提取文件信息
const fileInfo = {
url: targetUrl,
statusCode: remoteResponse.statusCode,
contentType: remoteResponse.headers['content-type'] || 'application/octet-stream',
contentLength: remoteResponse.headers['content-length'] ? parseInt(remoteResponse.headers['content-length']) : null,
lastModified: remoteResponse.headers['last-modified'] || null,
etag: remoteResponse.headers['etag'] || null,
acceptRanges: remoteResponse.headers['accept-ranges'] || null,
cacheControl: remoteResponse.headers['cache-control'] || null,
expires: remoteResponse.headers['expires'] || null
};
return reply.send(fileInfo);
} catch (requestError) {
console.error('[fileProxyController] Remote info request error:', requestError);
return reply.status(502).send({
error: `Failed to get remote file info: ${requestError.message}`
});
}
} catch (error) {
console.error('[fileProxyController] Info request processing error:', error);
return reply.status(500).send({ error: error.message });
}
});
/**
* 清理缓存路由
*/
fastify.delete('/file-proxy/cache', async (request, reply) => {
if (!verifyAuth(request, reply)) return;
setCorsHeaders(reply);
const beforeSize = requestCache.size;
requestCache.clear();
reply.send({
status: 'success',
message: 'Cache cleared',
cleared: beforeSize,
timestamp: new Date().toISOString()
});
});
/**
* 状态路由
*/
fastify.get('/file-proxy/status', async (request, reply) => {
setCorsHeaders(reply);
const statusData = createStatusResponse('file-proxy', '1.0.0', {
cache: {
size: requestCache.size,
timeout: PROXY_CONSTANTS.CACHE_TIMEOUT
}
});
reply.send(statusData);
});
done();
};