44 * @module file-proxy-controller
55 */
66
7- import { ENV } from '../utils/env.js' ;
8- import https from 'https' ;
9- import http from 'http' ;
10- import { URL } from 'url' ;
7+ import {
8+ createHealthResponse ,
9+ createStatusResponse ,
10+ decodeParam ,
11+ forwardResponseHeaders ,
12+ getDefaultHeaders ,
13+ makeRemoteRequest ,
14+ PROXY_CONSTANTS ,
15+ setCorsHeaders ,
16+ verifyAuth
17+ } from '../utils/proxy-util.js' ;
1118
1219/**
1320 * 远程文件代理控制器插件
@@ -18,186 +25,23 @@ import {URL} from 'url';
1825export default ( fastify , options , done ) => {
1926 // 请求缓存
2027 const requestCache = new Map ( ) ;
21- // 缓存超时时间(5分钟)
22- const cacheTimeout = 5 * 60 * 1000 ;
23-
24- /**
25- * 验证身份认证
26- * @param {Object } request - Fastify请求对象
27- * @param {Object } reply - Fastify响应对象
28- * @returns {boolean } 验证是否通过
29- */
30- function verifyAuth ( request , reply ) {
31- const requiredAuth = ENV . get ( 'PROXY_AUTH' , 'drpys' ) ;
32- const providedAuth = request . query . auth ;
33-
34- if ( ! providedAuth || providedAuth !== requiredAuth ) {
35- reply . status ( 401 ) . send ( {
36- error : 'Unauthorized' ,
37- message : 'Missing or invalid auth parameter' ,
38- code : 401
39- } ) ;
40- return false ;
41- }
42- return true ;
43- }
44-
45- /**
46- * 解码参数 - 支持 base64 解码
47- * @param {string } param - 需要解码的参数
48- * @param {boolean } isJson - 是否为 JSON 格式
49- * @returns {string|Object } 解码后的参数
50- */
51- function decodeParam ( param , isJson = false ) {
52- if ( ! param ) return isJson ? { } : '' ;
53-
54- let decoded = param ;
55-
56- try {
57- // 首先尝试 URL 解码
58- decoded = decodeURIComponent ( param ) ;
59- } catch ( e ) {
60- // URL 解码失败,使用原始参数
61- decoded = param ;
62- }
63-
64- // 对于 URL 参数,如果不是 http 开头,尝试 base64 解码
65- if ( ! isJson && ! decoded . startsWith ( 'http://' ) && ! decoded . startsWith ( 'https://' ) ) {
66- try {
67- const base64Decoded = Buffer . from ( decoded , 'base64' ) . toString ( 'utf8' ) ;
68- if ( base64Decoded . startsWith ( 'http://' ) || base64Decoded . startsWith ( 'https://' ) ) {
69- decoded = base64Decoded ;
70- }
71- } catch ( e ) {
72- // base64 解码失败,保持原值
73- }
74- }
75-
76- // 对于 headers 参数,如果不是 JSON 格式,尝试 base64 解码
77- if ( isJson && ! decoded . startsWith ( '{' ) && ! decoded . endsWith ( '}' ) ) {
78- try {
79- const base64Decoded = Buffer . from ( decoded , 'base64' ) . toString ( 'utf8' ) ;
80- if ( base64Decoded . startsWith ( '{' ) && base64Decoded . endsWith ( '}' ) ) {
81- decoded = base64Decoded ;
82- }
83- } catch ( e ) {
84- // base64 解码失败,保持原值
85- }
86- }
87-
88- // 如果是 JSON 格式,尝试解析
89- if ( isJson ) {
90- try {
91- return JSON . parse ( decoded ) ;
92- } catch ( e ) {
93- console . warn ( 'Failed to parse headers as JSON:' , decoded ) ;
94- return { } ;
95- }
96- }
97-
98- return decoded ;
99- }
100-
101- /**
102- * 获取默认请求头
103- * @param {Object } request - Fastify 请求对象
104- * @returns {Object } 默认请求头
105- */
106- function getDefaultHeaders ( request ) {
107- const defaultHeaders = { } ;
108-
109- // 复制一些重要的请求头
110- const headersToForward = [
111- 'user-agent' ,
112- 'accept' ,
113- 'accept-language' ,
114- 'accept-encoding' ,
115- 'referer' ,
116- 'origin'
117- ] ;
118-
119- headersToForward . forEach ( header => {
120- if ( request . headers [ header ] ) {
121- defaultHeaders [ header ] = request . headers [ header ] ;
122- }
123- } ) ;
124-
125- // 如果没有 user-agent,设置默认值
126- if ( ! defaultHeaders [ 'user-agent' ] ) {
127- defaultHeaders [ 'user-agent' ] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' ;
128- }
129-
130- return defaultHeaders ;
131- }
132-
133- /**
134- * 发起远程请求
135- * @param {string } url - 远程文件 URL
136- * @param {Object } headers - 请求头
137- * @param {string } method - 请求方法
138- * @param {string } range - Range 头
139- * @returns {Promise } 请求结果
140- */
141- function makeRemoteRequest ( url , headers , method = 'GET' , range = null ) {
142- return new Promise ( ( resolve , reject ) => {
143- try {
144- const urlObj = new URL ( url ) ;
145- const isHttps = urlObj . protocol === 'https:' ;
146- const httpModule = isHttps ? https : http ;
147-
148- const requestHeaders = { ...headers } ;
149- if ( range ) {
150- requestHeaders [ 'range' ] = range ;
151- }
152-
153- const options = {
154- hostname : urlObj . hostname ,
155- port : urlObj . port || ( isHttps ? 443 : 80 ) ,
156- path : urlObj . pathname + urlObj . search ,
157- method : method ,
158- headers : requestHeaders ,
159- timeout : 30000 // 30秒超时
160- } ;
161-
162- const req = httpModule . request ( options , ( res ) => {
163- resolve ( {
164- statusCode : res . statusCode ,
165- headers : res . headers ,
166- stream : res
167- } ) ;
168- } ) ;
169-
170- req . on ( 'error' , ( error ) => {
171- reject ( new Error ( `Request failed: ${ error . message } ` ) ) ;
172- } ) ;
173-
174- req . on ( 'timeout' , ( ) => {
175- req . destroy ( ) ;
176- reject ( new Error ( 'Request timeout' ) ) ;
177- } ) ;
178-
179- req . end ( ) ;
180- } catch ( error ) {
181- reject ( new Error ( `Invalid URL or request setup: ${ error . message } ` ) ) ;
182- }
183- } ) ;
184- }
18528
18629 /**
18730 * 远程文件代理健康检查接口
18831 * GET /file-proxy/health - 检查远程文件代理服务状态
18932 */
19033 fastify . get ( '/file-proxy/health' , async ( request , reply ) => {
191- console . log ( `[fileProxyController] Health check request` ) ;
192-
193- return reply . send ( {
194- status : 'ok' ,
195- service : 'Remote File Proxy' ,
196- timestamp : new Date ( ) . toISOString ( ) ,
34+ // console.log(`[fileProxyController] Health check request`);
35+
36+ setCorsHeaders ( reply ) ;
37+
38+ const healthData = createHealthResponse ( 'Remote File Proxy' , {
19739 cache : {
19840 requests : requestCache . size
19941 }
20042 } ) ;
43+
44+ return reply . send ( healthData ) ;
20145 } ) ;
20246
20347 /**
@@ -215,7 +59,7 @@ export default (fastify, options, done) => {
21559
21660 const { url : urlParam , headers : headersParam } = request . query ;
21761
218- console . log ( `[fileProxyController] ${ request . method } request for URL: ${ urlParam } ` ) ;
62+ // console.log(`[fileProxyController] ${request.method} request for URL: ${urlParam}`);
21963
22064 // 验证必需参数
22165 if ( ! urlParam ) {
@@ -260,28 +104,9 @@ export default (fastify, options, done) => {
260104 // 设置响应状态码
261105 reply . status ( remoteResponse . statusCode ) ;
262106
263- // 转发重要的响应头
264- const headersToForward = [
265- 'content-type' ,
266- 'content-length' ,
267- 'content-range' ,
268- 'accept-ranges' ,
269- 'last-modified' ,
270- 'etag' ,
271- 'cache-control' ,
272- 'expires'
273- ] ;
274-
275- headersToForward . forEach ( header => {
276- if ( remoteResponse . headers [ header ] ) {
277- reply . header ( header , remoteResponse . headers [ header ] ) ;
278- }
279- } ) ;
280-
281- // 设置 CORS 头
282- reply . header ( 'Access-Control-Allow-Origin' , '*' ) ;
283- reply . header ( 'Access-Control-Allow-Methods' , 'GET, HEAD, OPTIONS' ) ;
284- reply . header ( 'Access-Control-Allow-Headers' , 'Range, Content-Type' ) ;
107+ // 转发响应头和设置CORS
108+ forwardResponseHeaders ( reply , remoteResponse . headers ) ;
109+ setCorsHeaders ( reply ) ;
285110
286111 // 对于 HEAD 请求,只返回头部信息
287112 if ( request . method === 'HEAD' ) {
@@ -317,7 +142,7 @@ export default (fastify, options, done) => {
317142
318143 const { url : urlParam , headers : headersParam } = request . query ;
319144
320- console . log ( `[fileProxyController] Info request for URL: ${ urlParam } ` ) ;
145+ // console.log(`[fileProxyController] Info request for URL: ${urlParam}`);
321146
322147 // 验证必需参数
323148 if ( ! urlParam ) {
@@ -379,85 +204,38 @@ export default (fastify, options, done) => {
379204 } ) ;
380205
381206 /**
382- * 缓存管理接口
383- * DELETE /file-proxy/cache - 清理缓存
207+ * 清理缓存路由
384208 */
385209 fastify . delete ( '/file-proxy/cache' , async ( request , reply ) => {
386- // 验证身份认证
387- if ( ! verifyAuth ( request , reply ) ) {
388- return ;
389- }
210+ if ( ! verifyAuth ( request , reply ) ) return ;
390211
391- console . log ( `[fileProxyController] Cache clear request` ) ;
212+ setCorsHeaders ( reply ) ;
392213
393- try {
394- // 非VERCEL环境可在设置中心控制此功能是否开启
395- if ( ! process . env . VERCEL ) {
396- if ( ! Number ( process . env . allow_file_cache_clear ) ) {
397- return reply . status ( 403 ) . send ( { error : 'Cache clear is not allowed by owner' } ) ;
398- }
399- }
400-
401- const cacheCount = requestCache . size ;
402-
403- // 清理缓存
404- requestCache . clear ( ) ;
405-
406- return reply . send ( {
407- success : true ,
408- message : 'Cache cleared successfully' ,
409- cleared : {
410- requests : cacheCount
411- }
412- } ) ;
413- } catch ( error ) {
414- console . error ( '[fileProxyController] Cache clear error:' , error ) ;
415- return reply . status ( 500 ) . send ( { error : error . message } ) ;
416- }
214+ const beforeSize = requestCache . size ;
215+ requestCache . clear ( ) ;
216+
217+ reply . send ( {
218+ status : 'success' ,
219+ message : 'Cache cleared' ,
220+ cleared : beforeSize ,
221+ timestamp : new Date ( ) . toISOString ( )
222+ } ) ;
417223 } ) ;
418224
419225 /**
420- * 远程文件代理状态接口
421- * GET /file-proxy/status - 获取代理服务状态
226+ * 状态路由
422227 */
423228 fastify . get ( '/file-proxy/status' , async ( request , reply ) => {
424- console . log ( `[fileProxyController] Status request` ) ;
425-
426- try {
427- return reply . send ( {
428- service : 'Remote File Proxy Controller' ,
429- version : '1.0.0' ,
430- status : 'running' ,
431- cache : {
432- requests : requestCache . size ,
433- timeout : cacheTimeout
434- } ,
435- features : [
436- 'Remote file proxying' ,
437- 'Base64 parameter decoding' ,
438- 'Range request support' ,
439- 'Custom headers support' ,
440- 'CORS support' ,
441- 'Authentication protection'
442- ] ,
443- endpoints : [
444- 'GET /file-proxy/health - Health check (no auth required)' ,
445- 'GET /file-proxy/proxy?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Proxy remote file' ,
446- 'HEAD /file-proxy/proxy?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Get remote file headers' ,
447- 'GET /file-proxy/info?url=<remote_url>&auth=<auth_code>&headers=<custom_headers> - Get remote file information' ,
448- 'DELETE /file-proxy/cache?auth=<auth_code> - Clear cache' ,
449- 'GET /file-proxy/status - Get service status (no auth required)'
450- ] ,
451- auth : {
452- required : true ,
453- parameter : 'auth' ,
454- description : 'Authentication code required for protected endpoints'
455- }
456- } ) ;
457- } catch ( error ) {
458- console . error ( '[fileProxyController] Status request error:' , error ) ;
459- return reply . status ( 500 ) . send ( { error : error . message } ) ;
460- }
229+ setCorsHeaders ( reply ) ;
230+
231+ const statusData = createStatusResponse ( 'file-proxy' , '1.0.0' , {
232+ cache : {
233+ size : requestCache . size ,
234+ timeout : PROXY_CONSTANTS . CACHE_TIMEOUT
235+ }
236+ } ) ;
237+
238+ reply . send ( statusData ) ;
461239 } ) ;
462240
463241 done ( ) ;
0 commit comments