1
1
import path from 'path' ;
2
2
import { existsSync } from 'fs' ;
3
3
import { base64Decode } from '../libs_drpy/crypto-util.js' ;
4
- import '../utils/random-http-ua.js'
5
4
import * as drpy from '../libs/drpyS.js' ;
6
- // 创建 Agent 实例以复用 TCP 连接
7
- import http from 'http' ;
8
- import https from 'https' ;
9
- import axios from 'axios' ;
10
5
import { ENV } from "../utils/env.js" ;
11
6
12
- // const AgentOption = { keepAlive: true, maxSockets: 100,timeout: 60000 }; // 最大连接数100,60秒定期清理空闲连接
13
- const AgentOption = { keepAlive : true } ;
14
- const httpAgent = new http . Agent ( AgentOption ) ;
15
- const httpsAgent = new https . Agent ( AgentOption ) ;
16
-
17
-
18
7
export default ( fastify , options , done ) => {
19
8
// 动态加载模块并根据 query 执行不同逻辑
20
9
fastify . route ( {
@@ -30,8 +19,9 @@ export default (fastify, options, done) => {
30
19
reply . status ( 404 ) . send ( { error : `Module ${ moduleName } not found` } ) ;
31
20
return ;
32
21
}
22
+ const method = request . method . toUpperCase ( ) ;
33
23
// 根据请求方法选择参数来源
34
- const query = request . method === 'GET' ? request . query : request . body ;
24
+ const query = method === 'GET' ? request . query : request . body ;
35
25
const protocol = request . protocol ;
36
26
const hostname = request . hostname ;
37
27
const proxyUrl = `${ protocol } ://${ hostname } ${ request . url } ` . split ( '?' ) [ 0 ] . replace ( '/api/' , '/proxy/' ) + '/?do=js' ;
@@ -70,6 +60,9 @@ export default (fastify, options, done) => {
70
60
}
71
61
72
62
if ( 'ac' in query && 'ids' in query ) {
63
+ if ( method === 'POST' ) {
64
+ fastify . log . info ( `[${ moduleName } ] 二级已接收post数据: ${ query . ids } ` ) ;
65
+ }
73
66
// 详情逻辑
74
67
const result = await drpy . detail ( modulePath , env , query . ids . split ( ',' ) ) ;
75
68
return reply . send ( result ) ;
@@ -259,257 +252,5 @@ export default (fastify, options, done) => {
259
252
}
260
253
} ) ;
261
254
262
-
263
- // 用法同 https://github.com/Zhu-zi-a/mediaProxy
264
- fastify . all ( '/mediaProxy' , async ( request , reply ) => {
265
- const { thread = 1 , form = 'urlcode' , url, header, size = '128K' , randUa = 0 } = request . query ;
266
-
267
- // Check if the URL parameter is missing
268
- if ( ! url ) {
269
- return reply . code ( 400 ) . send ( { error : 'Missing required parameter: url' } ) ;
270
- }
271
-
272
- try {
273
- // Decode URL and headers based on the form type
274
- const decodedUrl = form === 'base64' ? base64Decode ( url ) : decodeURIComponent ( url ) ;
275
- const decodedHeader = header
276
- ? JSON . parse ( form === 'base64' ? base64Decode ( header ) : decodeURIComponent ( header ) )
277
- : { } ;
278
-
279
- // Call the proxy function, passing the decoded URL and headers
280
- return await proxyStreamMediaMulti ( decodedUrl , decodedHeader , request , reply , thread , size , randUa ) ;
281
- } catch ( error ) {
282
- fastify . log . error ( error ) ;
283
- reply . code ( 500 ) . send ( { error : error . message } ) ;
284
- }
285
- } ) ;
286
-
287
255
done ( ) ;
288
256
} ;
289
-
290
- // 媒体文件 流式代理
291
- function proxyStreamMedia ( videoUrl , headers , reply ) {
292
- console . log ( `进入了流式代理: ${ videoUrl } | headers: ${ JSON . stringify ( headers ) } ` ) ;
293
-
294
- const protocol = videoUrl . startsWith ( 'https' ) ? https : http ;
295
- const agent = videoUrl . startsWith ( 'https' ) ? httpsAgent : httpAgent ;
296
-
297
- // 发起请求
298
- const proxyRequest = protocol . request ( videoUrl , { headers, agent} , ( videoResponse ) => {
299
- console . log ( 'videoResponse.statusCode:' , videoResponse . statusCode ) ;
300
- console . log ( 'videoResponse.headers:' , videoResponse . headers ) ;
301
-
302
- if ( videoResponse . statusCode === 200 || videoResponse . statusCode === 206 ) {
303
- const resp_headers = {
304
- 'Content-Type' : videoResponse . headers [ 'content-type' ] || 'application/octet-stream' ,
305
- 'Content-Length' : videoResponse . headers [ 'content-length' ] ,
306
- ...( videoResponse . headers [ 'content-range' ] ? { 'Content-Range' : videoResponse . headers [ 'content-range' ] } : { } ) ,
307
- } ;
308
- console . log ( 'Response headers:' , resp_headers ) ;
309
- reply . headers ( resp_headers ) . status ( videoResponse . statusCode ) ;
310
-
311
- // 将响应流直接管道传输给客户端
312
- videoResponse . pipe ( reply . raw ) ;
313
-
314
- videoResponse . on ( 'data' , ( chunk ) => {
315
- console . log ( 'Data chunk received, size:' , chunk . length ) ;
316
- } ) ;
317
-
318
- videoResponse . on ( 'end' , ( ) => {
319
- console . log ( 'Video data transmission complete.' ) ;
320
- } ) ;
321
-
322
- videoResponse . on ( 'error' , ( err ) => {
323
- console . error ( 'Error during video response:' , err . message ) ;
324
- reply . code ( 500 ) . send ( { error : 'Error streaming video' , details : err . message } ) ;
325
- } ) ;
326
-
327
- reply . raw . on ( 'finish' , ( ) => {
328
- console . log ( 'Data fully sent to client' ) ;
329
- } ) ;
330
-
331
- // 监听关闭事件,销毁视频响应流
332
- reply . raw . on ( 'close' , ( ) => {
333
- console . log ( 'Response stream closed.' ) ;
334
- videoResponse . destroy ( ) ;
335
- } ) ;
336
- } else {
337
- console . error ( `Unexpected status code: ${ videoResponse . statusCode } ` ) ;
338
- reply . code ( videoResponse . statusCode ) . send ( { error : 'Failed to fetch video' } ) ;
339
- }
340
- } ) ;
341
-
342
- // 监听错误事件
343
- proxyRequest . on ( 'error' , ( err ) => {
344
- console . error ( 'Proxy request error:' , err . message ) ;
345
- reply . code ( 500 ) . send ( { error : 'Error fetching video' , details : err . message } ) ;
346
- } ) ;
347
-
348
- // 必须调用 .end() 才能发送请求
349
- proxyRequest . end ( ) ;
350
- }
351
-
352
-
353
- // Helper function for range-based chunk downloading
354
- async function fetchStream ( url , headers , start , end , randUa ) {
355
- try {
356
- const response = await axios . get ( url , {
357
- headers : {
358
- ...headers ,
359
- ...randUa ? { 'User-Agent' : randomUa . generateUa ( 1 , { device : [ 'pc' ] } ) } : { } ,
360
- Range : `bytes=${ start } -${ end } ` ,
361
- } ,
362
- responseType : 'stream' ,
363
- } ) ;
364
- return { stream : response . data , headers : response . headers } ;
365
- } catch ( error ) {
366
- throw new Error ( `Failed to fetch range ${ start } -${ end } : ${ error . message } ` ) ;
367
- }
368
- }
369
-
370
- async function proxyStreamMediaMulti ( mediaUrl , reqHeaders , request , reply , thread , size , randUa = 0 ) {
371
- try {
372
- // console.log('mediaUrl:', mediaUrl);
373
- // console.log('reqHeaders:', reqHeaders);
374
- // console.log('randUa:', randUa);
375
- let initialHeaders ;
376
- let contentLength ;
377
-
378
- // First attempt with HEAD request to get the full content length
379
- /*
380
- try {
381
- const response = await axios.head(mediaUrl, {
382
- headers: Object.assign({}, reqHeaders, {'User-Agent': randomUa.generateUa()})
383
- });
384
- initialHeaders = response.headers;
385
- contentLength = parseInt(initialHeaders['content-length'], 10);
386
-
387
- } catch (error) {
388
- // If HEAD fails, fallback to GET request without Range to get the full content length
389
- const response = await axios.get(mediaUrl, {
390
- headers: Object.assign({}, reqHeaders, {'User-Agent': randomUa.generateUa()}),
391
- responseType: 'stream'
392
- });
393
- initialHeaders = response.headers;
394
- contentLength = parseInt(initialHeaders['content-length'], 10);
395
- // 立即销毁流,防止下载文件内容
396
- response.data.destroy();
397
- }
398
- */
399
- const randHeaders = randUa ? Object . assign ( { } , reqHeaders , { 'User-Agent' : randomUa . generateUa ( 1 , { device : [ 'pc' ] } ) } ) : reqHeaders ;
400
- // console.log('randHeaders:', randHeaders);
401
- const response = await axios . get ( mediaUrl , {
402
- headers : randHeaders ,
403
- // headers: reqHeaders,
404
- responseType : 'stream'
405
- } ) ;
406
- initialHeaders = response . headers ;
407
- contentLength = parseInt ( initialHeaders [ 'content-length' ] , 10 ) ;
408
- // 立即销毁流,防止下载文件内容
409
- response . data . destroy ( ) ;
410
- // console.log('contentLength:', contentLength);
411
-
412
- // Ensure that we got a valid content length
413
- if ( ! contentLength ) {
414
- throw new Error ( 'Failed to get the total content length.' ) ;
415
- }
416
-
417
- // Set response headers based on the target URL headers, excluding certain ones
418
- Object . entries ( initialHeaders ) . forEach ( ( [ key , value ] ) => {
419
- if ( ! [ 'transfer-encoding' , 'content-length' ] . includes ( key . toLowerCase ( ) ) ) {
420
- reply . raw . setHeader ( key , value ) ;
421
- }
422
- } ) ;
423
-
424
- reply . raw . setHeader ( 'Accept-Ranges' , 'bytes' ) ;
425
-
426
- // Parse the range from the request or default to 'bytes=0-'
427
- const range = request . headers . range || 'bytes=0-' ;
428
- const [ startStr , endStr ] = range . replace ( / b y t e s = / , '' ) . split ( '-' ) ;
429
- let start = parseInt ( startStr , 10 ) ;
430
- let end = endStr ? parseInt ( endStr , 10 ) : contentLength - 1 ;
431
-
432
- // Ensure that the range is within the file's length
433
- if ( start < 0 ) start = 0 ;
434
- if ( end >= contentLength ) end = contentLength - 1 ;
435
-
436
- if ( start >= end ) {
437
- reply . code ( 416 ) . header ( 'Content-Range' , `bytes */${ contentLength } ` ) . send ( ) ;
438
- return ;
439
- }
440
-
441
- // Set Content-Range and Content-Length headers before any data is sent
442
- reply . raw . setHeader ( 'Content-Range' , `bytes ${ start } -${ end } /${ contentLength } ` ) ;
443
- reply . raw . setHeader ( 'Content-Length' , end - start + 1 ) ;
444
- reply . raw . writeHead ( 206 ) ; // Ensure headers are sent before streaming
445
-
446
- // Calculate the chunk size based on the provided size parameter (e.g., '128K', '1M')
447
- const chunkSize = sizeToBytes ( size ) ;
448
-
449
- // Calculate the total number of chunks
450
- const totalChunks = Math . ceil ( ( end - start + 1 ) / chunkSize ) ;
451
-
452
- // Limit the number of concurrent threads to the provided 'thread' value
453
- const threadCount = Math . min ( thread , totalChunks ) ;
454
-
455
- // Split the range into multiple sub-ranges based on the number of threads
456
- const ranges = Array . from ( { length : threadCount } , ( _ , i ) => {
457
- const subStart = start + i * ( end - start + 1 ) / threadCount ;
458
- const subEnd = Math . min ( subStart + ( end - start + 1 ) / threadCount - 1 , end ) ;
459
- return { start : Math . floor ( subStart ) , end : Math . floor ( subEnd ) } ;
460
- } ) ;
461
-
462
- // Fetch the streams concurrently for the calculated ranges
463
- const fetchChunks = ranges . map ( range => fetchStream ( mediaUrl , randHeaders , range . start , range . end , randUa ) ) ;
464
- const streams = await Promise . all ( fetchChunks ) ;
465
-
466
- // Send the chunks to the client in order
467
- let cnt = 0 ;
468
- for ( const { stream} of streams ) {
469
- cnt += 1 ;
470
- // Handle streaming and stop when client disconnects
471
- const onAbort = ( ) => {
472
- console . log ( 'Client aborted the connection' ) ;
473
- stream . destroy ( ) ; // Destroy the stream if client disconnects
474
- } ;
475
-
476
- // Listen to the 'aborted' event on the request object
477
- request . raw . on ( 'aborted' , onAbort ) ;
478
-
479
- try {
480
- // console.log(`第${cnt}段流代理开始运行...`);
481
- for await ( const chunk of stream ) {
482
- if ( request . raw . aborted ) {
483
- // console.log(`第${cnt}段流代理结束运行`);
484
- break ; // Stop streaming if the client aborted
485
- }
486
- reply . raw . write ( chunk ) ;
487
- }
488
- } catch ( error ) {
489
- console . error ( '[proxyStreamMediaMulti] error during streaming:' , error . message ) ;
490
- } finally {
491
- request . raw . removeListener ( 'aborted' , onAbort ) ; // Clean up event listener
492
- }
493
- }
494
-
495
- reply . raw . end ( ) ; // End the response once the streaming is done
496
-
497
- } catch ( error ) {
498
- console . error ( '[proxyStreamMediaMulti] error:' , error . message ) ;
499
- if ( ! reply . sent ) {
500
- reply . code ( 500 ) . send ( { error : error . message } ) ;
501
- }
502
- }
503
- }
504
-
505
- // Helper function to convert size string (e.g., '128K', '1M') to bytes
506
- function sizeToBytes ( size ) {
507
- const sizeMap = {
508
- K : 1024 ,
509
- M : 1024 * 1024 ,
510
- G : 1024 * 1024 * 1024
511
- } ;
512
- const unit = size [ size . length - 1 ] . toUpperCase ( ) ;
513
- const number = parseInt ( size , 10 ) ;
514
- return number * ( sizeMap [ unit ] || 1 ) ;
515
- }
0 commit comments