Skip to content

Commit c4cc17c

Browse files
author
Taois
committed
feat: m3u8支持传入请求头
1 parent 86a629c commit c4cc17c

File tree

8 files changed

+511
-7
lines changed

8 files changed

+511
-7
lines changed

controllers/m3u8-proxy.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ export default (fastify, options, done) => {
127127
}
128128
});
129129

130-
// 如果没有 user-agent,设置默认值
130+
// 如果没有 user-agent,设置默认值(Windows 11 Chrome)
131131
if (!defaultHeaders['user-agent']) {
132-
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';
132+
defaultHeaders['user-agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
133133
}
134134

135135
return defaultHeaders;
@@ -829,7 +829,8 @@ export default (fastify, options, done) => {
829829
m3u8Content,
830830
targetUrl,
831831
proxyBaseUrl,
832-
request.query.auth
832+
request.query.auth,
833+
request.query.headers
833834
);
834835

835836
// 缓存处理后的内容
@@ -908,9 +909,15 @@ export default (fastify, options, done) => {
908909
* @param {string} baseUrl - 基础 URL
909910
* @param {string} proxyBaseUrl - 代理基础 URL
910911
* @param {string} authCode - 认证码
912+
* @param {string} headersParam - 自定义请求头参数
911913
* @returns {string} 处理后的 M3U8 内容
912914
*/
913-
function processM3u8ContentUnified(content, baseUrl, proxyBaseUrl, authCode) {
915+
function processM3u8ContentUnified(content, baseUrl, proxyBaseUrl, authCode, headersParam = null) {
916+
console.log(`[m3u8ProxyController] Processing M3U8 content with headers param: ${headersParam ? 'YES' : 'NO'}`);
917+
if (headersParam) {
918+
console.log(`[m3u8ProxyController] Headers param value: ${headersParam}`);
919+
}
920+
914921
const lines = content.split('\n');
915922
const processedLines = [];
916923

@@ -942,7 +949,12 @@ export default (fastify, options, done) => {
942949
const encodedUrl = encodeURIComponent(targetUrl);
943950

944951
// 生成统一代理链接
945-
const proxyUrl = `${proxyBaseUrl}/m3u8-proxy/proxy?url=${encodedUrl}&auth=${authCode}`;
952+
let proxyUrl = `${proxyBaseUrl}/m3u8-proxy/proxy?url=${encodedUrl}&auth=${authCode}`;
953+
954+
// 如果有自定义请求头,添加到代理链接中
955+
if (headersParam) {
956+
proxyUrl += `&headers=${encodeURIComponent(headersParam)}`;
957+
}
946958

947959
processedLines.push(proxyUrl);
948960
}

examples/test-headers-simple.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import fetch from 'node-fetch';
2+
3+
async function testHeaders() {
4+
const url = 'http://localhost:3002/m3u8-proxy/proxy?url=https%3A%2F%2Fvip.ffzy-play8.com%2F20250610%2F713568_ef2eb646%2Findex.m3u8&auth=drpys&headers=%7B%22User-Agent%22%3A%22Mozilla%2F5.0%20(Windows%20NT%2010.0%3B%20Win64%3B%20x64)%20AppleWebKit%2F537.36%22%7D';
5+
6+
try {
7+
const response = await fetch(url);
8+
const content = await response.text();
9+
10+
console.log('M3U8 Content:');
11+
console.log('============================================================');
12+
console.log(content);
13+
console.log('============================================================');
14+
15+
// 检查是否包含 headers 参数
16+
const hasHeaders = content.includes('&headers=');
17+
console.log(`\n包含 headers 参数: ${hasHeaders ? '✅ 是' : '❌ 否'}`);
18+
19+
if (hasHeaders) {
20+
console.log('✅ 嵌套链接成功包含自定义请求头参数!');
21+
} else {
22+
console.log('❌ 嵌套链接未包含自定义请求头参数');
23+
}
24+
25+
} catch (error) {
26+
console.error('测试失败:', error.message);
27+
}
28+
}
29+
30+
testHeaders();

examples/test-m3u8-headers.js

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
/**
2+
* M3U8 代理请求头测试脚本
3+
* 测试 Windows 11 User-Agent 和自定义请求头的传递
4+
*/
5+
6+
import fetch from 'node-fetch';
7+
8+
const PROXY_BASE = 'http://localhost:3002';
9+
const AUTH_CODE = 'drpys';
10+
const TEST_URL = 'https://vip.ffzy-play8.com/20250610/713568_ef2eb646/index.m3u8';
11+
12+
// Windows 11 Chrome User-Agent
13+
const WIN11_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
14+
15+
/**
16+
* 构建代理 URL
17+
*/
18+
function buildProxyUrl(targetUrl, customHeaders = null) {
19+
const encodedUrl = encodeURIComponent(targetUrl);
20+
let proxyUrl = `${PROXY_BASE}/m3u8-proxy/proxy?url=${encodedUrl}&auth=${AUTH_CODE}`;
21+
22+
if (customHeaders) {
23+
const encodedHeaders = encodeURIComponent(JSON.stringify(customHeaders));
24+
proxyUrl += `&headers=${encodedHeaders}`;
25+
}
26+
27+
return proxyUrl;
28+
}
29+
30+
/**
31+
* 测试默认 User-Agent(无自定义请求头)
32+
*/
33+
async function testDefaultUserAgent() {
34+
console.log('\n🎯 测试 1: 默认 User-Agent(无自定义请求头)');
35+
console.log('============================================================');
36+
37+
const proxyUrl = buildProxyUrl(TEST_URL);
38+
console.log(`📡 代理 URL: ${proxyUrl}`);
39+
40+
try {
41+
const response = await fetch(proxyUrl, { method: 'GET' });
42+
const content = await response.text();
43+
44+
console.log(`✅ 响应状态: ${response.status} ${response.statusText}`);
45+
console.log(`📋 Content-Type: ${response.headers.get('content-type')}`);
46+
console.log(`📄 内容长度: ${content.length} 字符`);
47+
48+
// 检查是否包含代理链接
49+
const hasProxyLinks = content.includes('/m3u8-proxy/proxy');
50+
console.log(`🔗 包含代理链接: ${hasProxyLinks ? '✅ 是' : '❌ 否'}`);
51+
52+
// 检查嵌套链接是否包含 headers 参数
53+
const hasHeadersParam = content.includes('&headers=');
54+
console.log(`📋 嵌套链接包含 headers 参数: ${hasHeadersParam ? '✅ 是' : '❌ 否'}`);
55+
56+
return response.status === 200;
57+
} catch (error) {
58+
console.error(`❌ 测试失败: ${error.message}`);
59+
return false;
60+
}
61+
}
62+
63+
/**
64+
* 测试自定义 Windows 11 User-Agent
65+
*/
66+
async function testCustomUserAgent() {
67+
console.log('\n🎯 测试 2: 自定义 Windows 11 User-Agent');
68+
console.log('============================================================');
69+
70+
const customHeaders = {
71+
'User-Agent': WIN11_USER_AGENT,
72+
'Accept': 'application/vnd.apple.mpegurl, application/x-mpegURL, application/octet-stream',
73+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
74+
'Referer': 'https://example.com/'
75+
};
76+
77+
const proxyUrl = buildProxyUrl(TEST_URL, customHeaders);
78+
console.log(`📡 代理 URL: ${proxyUrl}`);
79+
console.log(`🖥️ User-Agent: ${customHeaders['User-Agent']}`);
80+
81+
try {
82+
const response = await fetch(proxyUrl, { method: 'GET' });
83+
const content = await response.text();
84+
85+
console.log(`✅ 响应状态: ${response.status} ${response.statusText}`);
86+
console.log(`📋 Content-Type: ${response.headers.get('content-type')}`);
87+
console.log(`📄 内容长度: ${content.length} 字符`);
88+
89+
// 检查是否包含代理链接
90+
const hasProxyLinks = content.includes('/m3u8-proxy/proxy');
91+
console.log(`🔗 包含代理链接: ${hasProxyLinks ? '✅ 是' : '❌ 否'}`);
92+
93+
// 检查嵌套链接是否包含 headers 参数
94+
const hasHeadersParam = content.includes('&headers=');
95+
console.log(`📋 嵌套链接包含 headers 参数: ${hasHeadersParam ? '✅ 是' : '❌ 否'}`);
96+
97+
// 显示处理后的内容
98+
console.log('\n📄 处理后的 M3U8 内容:');
99+
console.log('------------------------------------------------------------');
100+
console.log(content);
101+
console.log('------------------------------------------------------------');
102+
103+
return response.status === 200 && hasHeadersParam;
104+
} catch (error) {
105+
console.error(`❌ 测试失败: ${error.message}`);
106+
return false;
107+
}
108+
}
109+
110+
/**
111+
* 测试 HEAD 请求与自定义请求头
112+
*/
113+
async function testHeadWithCustomHeaders() {
114+
console.log('\n🎯 测试 3: HEAD 请求与自定义请求头');
115+
console.log('============================================================');
116+
117+
const customHeaders = {
118+
'User-Agent': WIN11_USER_AGENT,
119+
'Accept': '*/*',
120+
'Accept-Encoding': 'gzip, deflate, br'
121+
};
122+
123+
const proxyUrl = buildProxyUrl(TEST_URL, customHeaders);
124+
console.log(`📡 代理 URL: ${proxyUrl}`);
125+
console.log(`🖥️ User-Agent: ${customHeaders['User-Agent']}`);
126+
127+
try {
128+
const response = await fetch(proxyUrl, { method: 'HEAD' });
129+
130+
console.log(`✅ 响应状态: ${response.status} ${response.statusText}`);
131+
console.log(`📋 Content-Type: ${response.headers.get('content-type')}`);
132+
console.log(`📏 Content-Length: ${response.headers.get('content-length') || '未设置'}`);
133+
console.log(`🔄 CORS 头: ${response.headers.get('access-control-allow-origin')}`);
134+
135+
return response.status === 200;
136+
} catch (error) {
137+
console.error(`❌ HEAD 请求失败: ${error.message}`);
138+
return false;
139+
}
140+
}
141+
142+
/**
143+
* 测试嵌套 M3U8 文件的请求头传递
144+
*/
145+
async function testNestedM3u8Headers() {
146+
console.log('\n🎯 测试 4: 嵌套 M3U8 文件的请求头传递');
147+
console.log('============================================================');
148+
149+
const customHeaders = {
150+
'User-Agent': WIN11_USER_AGENT,
151+
'Accept': 'application/vnd.apple.mpegurl',
152+
'X-Custom-Header': 'test-value-123'
153+
};
154+
155+
// 首先获取主 M3U8 文件
156+
const mainProxyUrl = buildProxyUrl(TEST_URL, customHeaders);
157+
console.log(`📡 主 M3U8 代理 URL: ${mainProxyUrl}`);
158+
159+
try {
160+
const mainResponse = await fetch(mainProxyUrl, { method: 'GET' });
161+
const mainContent = await mainResponse.text();
162+
163+
console.log(`✅ 主 M3U8 响应状态: ${mainResponse.status} ${mainResponse.statusText}`);
164+
165+
// 提取第一个嵌套的代理链接
166+
const lines = mainContent.split('\n');
167+
let nestedProxyUrl = null;
168+
169+
for (const line of lines) {
170+
if (line.trim().startsWith('http://localhost:3002/m3u8-proxy/proxy')) {
171+
nestedProxyUrl = line.trim();
172+
break;
173+
}
174+
}
175+
176+
if (!nestedProxyUrl) {
177+
console.log('❌ 未找到嵌套的代理链接');
178+
return false;
179+
}
180+
181+
console.log(`📡 嵌套代理 URL: ${nestedProxyUrl}`);
182+
183+
// 检查嵌套链接是否包含 headers 参数
184+
const hasHeadersParam = nestedProxyUrl.includes('&headers=');
185+
console.log(`📋 嵌套链接包含 headers 参数: ${hasHeadersParam ? '✅ 是' : '❌ 否'}`);
186+
187+
if (hasHeadersParam) {
188+
// 解码 headers 参数
189+
const urlObj = new URL(nestedProxyUrl);
190+
const headersParam = urlObj.searchParams.get('headers');
191+
if (headersParam) {
192+
try {
193+
const decodedHeaders = JSON.parse(decodeURIComponent(headersParam));
194+
console.log('📋 解码后的请求头:', JSON.stringify(decodedHeaders, null, 2));
195+
196+
// 验证自定义请求头是否正确传递
197+
const hasCustomHeader = decodedHeaders['X-Custom-Header'] === 'test-value-123';
198+
const hasUserAgent = decodedHeaders['User-Agent'] === WIN11_USER_AGENT;
199+
200+
console.log(`🔍 自定义请求头传递: ${hasCustomHeader ? '✅ 正确' : '❌ 错误'}`);
201+
console.log(`🖥️ User-Agent 传递: ${hasUserAgent ? '✅ 正确' : '❌ 错误'}`);
202+
203+
return hasCustomHeader && hasUserAgent;
204+
} catch (e) {
205+
console.log('❌ 解码 headers 参数失败:', e.message);
206+
return false;
207+
}
208+
}
209+
}
210+
211+
return hasHeadersParam;
212+
} catch (error) {
213+
console.error(`❌ 嵌套测试失败: ${error.message}`);
214+
return false;
215+
}
216+
}
217+
218+
/**
219+
* 运行所有测试
220+
*/
221+
async function runAllTests() {
222+
console.log('🚀 开始 M3U8 代理请求头测试');
223+
console.log(`🎬 测试 URL: ${TEST_URL}`);
224+
console.log(`🖥️ Windows 11 User-Agent: ${WIN11_USER_AGENT}`);
225+
226+
const results = {
227+
defaultUserAgent: await testDefaultUserAgent(),
228+
customUserAgent: await testCustomUserAgent(),
229+
headWithHeaders: await testHeadWithCustomHeaders(),
230+
nestedHeaders: await testNestedM3u8Headers()
231+
};
232+
233+
console.log('\n📊 测试结果总结');
234+
console.log('============================================================');
235+
console.log(`默认 User-Agent 测试: ${results.defaultUserAgent ? '✅ 通过' : '❌ 失败'}`);
236+
console.log(`自定义 User-Agent 测试: ${results.customUserAgent ? '✅ 通过' : '❌ 失败'}`);
237+
console.log(`HEAD 请求头测试: ${results.headWithHeaders ? '✅ 通过' : '❌ 失败'}`);
238+
console.log(`嵌套请求头传递测试: ${results.nestedHeaders ? '✅ 通过' : '❌ 失败'}`);
239+
240+
const allPassed = Object.values(results).every(result => result);
241+
console.log(`\n🎯 总体结果: ${allPassed ? '🎉 全部通过' : '⚠️ 部分失败'}`);
242+
243+
return allPassed;
244+
}
245+
246+
runAllTests().catch(console.error);

examples/test-m3u8-server.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* 简单的 M3U8 代理服务器测试
3+
* 用于测试指定的 M3U8 URL
4+
*/
5+
6+
import Fastify from 'fastify';
7+
import m3u8ProxyController from '../controllers/m3u8-proxy.js';
8+
9+
const fastify = Fastify({
10+
logger: true,
11+
disableRequestLogging: false
12+
});
13+
14+
// 注册 M3U8 代理控制器
15+
await fastify.register(m3u8ProxyController);
16+
17+
// 启动服务器
18+
const start = async () => {
19+
try {
20+
await fastify.listen({ port: 3002, host: '0.0.0.0' });
21+
console.log('🚀 M3U8 代理服务器已启动');
22+
console.log('📡 服务地址: http://0.0.0.0:3002');
23+
console.log('');
24+
console.log('📋 可用接口:');
25+
console.log(' GET /m3u8-proxy/health - 健康检查');
26+
console.log(' GET /m3u8-proxy/status - 服务状态');
27+
console.log(' GET /m3u8-proxy/proxy?url=<url>&auth=drpys - 统一代理接口');
28+
console.log(' HEAD /m3u8-proxy/proxy?url=<url>&auth=drpys - HEAD 请求测试');
29+
console.log('');
30+
} catch (err) {
31+
fastify.log.error(err);
32+
process.exit(1);
33+
}
34+
};
35+
36+
start();

0 commit comments

Comments
 (0)