Skip to content

Commit a6b8cb3

Browse files
committed
update:研究流代理
1 parent 6909046 commit a6b8cb3

File tree

3 files changed

+128
-5
lines changed

3 files changed

+128
-5
lines changed

Diff for: controllers/api.js

+82-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@ import path from 'path';
22
import {existsSync} from 'fs';
33
import {base64Decode} from '../libs_drpy/crypto-util.js';
44
import * as drpy from '../libs/drpyS.js';
5+
// 创建 Agent 实例以复用 TCP 连接
6+
import http from 'http';
7+
import https from 'https';
8+
9+
// const AgentOption = { keepAlive: true, maxSockets: 100,timeout: 60000 }; // 最大连接数100,60秒定期清理空闲连接
10+
const AgentOption = {keepAlive: true};
11+
const httpAgent = new http.Agent(AgentOption);
12+
const httpsAgent = new https.Agent(AgentOption);
13+
514

615
export default (fastify, options, done) => {
716
// 动态加载模块并根据 query 执行不同逻辑
@@ -117,6 +126,7 @@ export default (fastify, options, done) => {
117126
}
118127
const proxyPath = request.params['*']; // 捕获整个路径
119128
fastify.log.info(`try proxy for ${moduleName} -> ${proxyPath}: ${JSON.stringify(query)}`);
129+
const rangeHeader = request.headers.range; // 获取客户端的 Range 请求头
120130
const protocol = request.protocol;
121131
const hostname = request.hostname;
122132
const proxyUrl = `${protocol}://${hostname}${request.url}`.split('?')[0].replace(proxyPath, '') + '?do=js';
@@ -133,8 +143,8 @@ export default (fastify, options, done) => {
133143
let content = backRespList[2] || '';
134144
const headers = backRespList.length > 3 ? backRespList[3] : null;
135145
const toBytes = backRespList.length > 4 ? backRespList[4] : null;
136-
// 如果需要转换为字节内容
137-
if (toBytes) {
146+
// 如果需要转换为字节内容(尝试base64转bytes)
147+
if (toBytes === 1) {
138148
try {
139149
if (content.includes('base64,')) {
140150
content = unescape(content.split("base64,")[1]);
@@ -144,6 +154,14 @@ export default (fastify, options, done) => {
144154
fastify.log.error(`Local Proxy toBytes error: ${e}`);
145155
}
146156
}
157+
// 流代理
158+
else if (toBytes === 2 && content.startsWith('http')) {
159+
const new_headers = {
160+
...(headers ? headers : {}),
161+
...(rangeHeader ? {Range: rangeHeader} : {}), // 添加 Range 请求头
162+
}
163+
return proxyStreamMedia(content, new_headers, reply); // 走 流式代理
164+
}
147165

148166
// 根据媒体类型来决定如何设置字符编码
149167
if (typeof content === 'string') {
@@ -233,3 +251,65 @@ export default (fastify, options, done) => {
233251
});
234252
done();
235253
};
254+
255+
// 媒体文件 流式代理
256+
function proxyStreamMedia(videoUrl, headers, reply) {
257+
console.log(`进入了流式代理: ${videoUrl} | headers: ${JSON.stringify(headers)}`);
258+
259+
const protocol = videoUrl.startsWith('https') ? https : http;
260+
const agent = videoUrl.startsWith('https') ? httpsAgent : httpAgent;
261+
262+
// 发起请求
263+
const proxyRequest = protocol.request(videoUrl, {headers, agent}, (videoResponse) => {
264+
console.log('videoResponse.statusCode:', videoResponse.statusCode);
265+
console.log('videoResponse.headers:', videoResponse.headers);
266+
267+
if (videoResponse.statusCode === 200 || videoResponse.statusCode === 206) {
268+
const resp_headers = {
269+
'Content-Type': videoResponse.headers['content-type'] || 'application/octet-stream',
270+
'Content-Length': videoResponse.headers['content-length'],
271+
...(videoResponse.headers['content-range'] ? {'Content-Range': videoResponse.headers['content-range']} : {}),
272+
};
273+
console.log('Response headers:', resp_headers);
274+
reply.headers(resp_headers).status(videoResponse.statusCode);
275+
276+
// 将响应流直接管道传输给客户端
277+
videoResponse.pipe(reply.raw);
278+
279+
videoResponse.on('data', (chunk) => {
280+
console.log('Data chunk received, size:', chunk.length);
281+
});
282+
283+
videoResponse.on('end', () => {
284+
console.log('Video data transmission complete.');
285+
});
286+
287+
videoResponse.on('error', (err) => {
288+
console.error('Error during video response:', err.message);
289+
reply.code(500).send({error: 'Error streaming video', details: err.message});
290+
});
291+
292+
reply.raw.on('finish', () => {
293+
console.log('Data fully sent to client');
294+
});
295+
296+
// 监听关闭事件,销毁视频响应流
297+
reply.raw.on('close', () => {
298+
console.log('Response stream closed.');
299+
videoResponse.destroy();
300+
});
301+
} else {
302+
console.error(`Unexpected status code: ${videoResponse.statusCode}`);
303+
reply.code(videoResponse.statusCode).send({error: 'Failed to fetch video'});
304+
}
305+
});
306+
307+
// 监听错误事件
308+
proxyRequest.on('error', (err) => {
309+
console.error('Proxy request error:', err.message);
310+
reply.code(500).send({error: 'Error fetching video', details: err.message});
311+
});
312+
313+
// 必须调用 .end() 才能发送请求
314+
proxyRequest.end();
315+
}

Diff for: docs/updateRecord.md

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ console.log('typeof getPublicIp2:', typeof getPublicIp2);
2323
```
2424

2525
6. drpyS源初始化增加30秒超时返回机制(但不会中断后台任务,请确保代码不要含有死循环等操作)
26+
7. 研究本地代理流但是没成功,代码保留了
2627

2728
### 20241225
2829

Diff for: js/设置中心.js

+45-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ var rule = {
99
return action_data;
1010
},
1111
host: 'http://empty',
12-
class_name: '夸克&UC&阿里&哔哩&青少年模式',
13-
class_url: 'quark&uc&ali&bili&adult',
12+
class_name: '夸克&UC&阿里&哔哩&青少年模式&测试',
13+
class_url: 'quark&uc&ali&bili&adult&test',
1414
url: '/fyclass',
1515
action: async function (action, value) {
1616
if (action === 'set-cookie') {
@@ -125,6 +125,7 @@ var rule = {
125125
'ali': urljoin(publicUrl, './images/icon_cookie/阿里.png'),
126126
'bili': urljoin(publicUrl, './images/icon_cookie/哔哩.png'),
127127
'adult': urljoin(publicUrl, './images/icon_cookie/chat.webp'),
128+
'test': urljoin(publicUrl, './icon.svg'),
128129
};
129130
let d = [];
130131
switch (MY_CATE) {
@@ -148,9 +149,50 @@ var rule = {
148149
d.push(genMultiInput('hide_adult', '设置青少年模式', '把值设置为1将会在全部接口隐藏18+源,其他值不过滤,跟随订阅', images.adult));
149150
d.push(getInput('get_hide_adult', '查看青少年模式', images.adult));
150151
break;
152+
case 'test':
153+
d.push({
154+
vod_id: "proxyStream",
155+
vod_name: "测试本地代理流",
156+
vod_pic: images.test,
157+
vod_desc: "流式代理mp4等视频"
158+
});
159+
break;
151160
}
152161
return d
153-
}
162+
},
163+
二级: async function (ids) {
164+
let {input, orId, getProxyUrl} = this;
165+
log(input, orId);
166+
if (orId === 'proxyStream') {
167+
let media_url = 'https://vdse.bdstatic.com//628ca08719cef5987ea2ae3c6f0d2386.mp4';
168+
return {
169+
vod_id: 'proxyStream',
170+
vod_name: '测试代理流',
171+
vod_play_from: 'drpyS本地流代理',
172+
vod_play_url: '测试播放流$' + getProxyUrl().replace('?do=js', media_url)
173+
}
174+
}
175+
},
176+
play_parse: true,
177+
lazy: async function () {
178+
let {input} = this;
179+
return {parse: 0, url: input}
180+
},
181+
proxy_rule: async function () {
182+
let {input, proxyPath} = this;
183+
const url = proxyPath;
184+
log('start proxy:', url);
185+
try {
186+
const headers = {
187+
'user-agent': PC_UA,
188+
}
189+
return [200, null, url, headers, 2]
190+
} catch (e) {
191+
log('proxy error:', e.message);
192+
return [500, 'text/plain', e.message]
193+
}
194+
},
195+
154196
};
155197

156198

0 commit comments

Comments
 (0)