11import path from 'path' ;
22import { existsSync } from 'fs' ;
33import { base64Decode } from '../libs_drpy/crypto-util.js' ;
4- import '../utils/random-http-ua.js'
54import * as drpy from '../libs/drpyS.js' ;
6- // 创建 Agent 实例以复用 TCP 连接
7- import http from 'http' ;
8- import https from 'https' ;
9- import axios from 'axios' ;
105import { ENV } from "../utils/env.js" ;
116
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-
187export default ( fastify , options , done ) => {
198 // 动态加载模块并根据 query 执行不同逻辑
209 fastify . route ( {
@@ -30,8 +19,9 @@ export default (fastify, options, done) => {
3019 reply . status ( 404 ) . send ( { error : `Module ${ moduleName } not found` } ) ;
3120 return ;
3221 }
22+ const method = request . method . toUpperCase ( ) ;
3323 // 根据请求方法选择参数来源
34- const query = request . method === 'GET' ? request . query : request . body ;
24+ const query = method === 'GET' ? request . query : request . body ;
3525 const protocol = request . protocol ;
3626 const hostname = request . hostname ;
3727 const proxyUrl = `${ protocol } ://${ hostname } ${ request . url } ` . split ( '?' ) [ 0 ] . replace ( '/api/' , '/proxy/' ) + '/?do=js' ;
@@ -70,6 +60,9 @@ export default (fastify, options, done) => {
7060 }
7161
7262 if ( 'ac' in query && 'ids' in query ) {
63+ if ( method === 'POST' ) {
64+ fastify . log . info ( `[${ moduleName } ] 二级已接收post数据: ${ query . ids } ` ) ;
65+ }
7366 // 详情逻辑
7467 const result = await drpy . detail ( modulePath , env , query . ids . split ( ',' ) ) ;
7568 return reply . send ( result ) ;
@@ -259,257 +252,5 @@ export default (fastify, options, done) => {
259252 }
260253 } ) ;
261254
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-
287255 done ( ) ;
288256} ;
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