1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00
fcast/receivers/common/web/NetworkService.ts

142 lines
6.3 KiB
TypeScript
Raw Permalink Normal View History

import { PlayMessage } from 'common/Packets';
import { streamingMediaTypes } from 'common/MimeTypes';
2025-06-10 14:23:06 -05:00
import { MediaCache } from './MediaCache';
2024-12-09 00:56:55 -06:00
import * as http from 'http';
import * as url from 'url';
import { AddressInfo } from 'modules/ws';
import { v4 as uuidv4 } from 'modules/uuid';
2025-05-01 10:37:21 -05:00
import { Logger, LoggerType } from 'common/Logger';
const logger = new Logger('NetworkService', LoggerType.BACKEND);
2024-12-09 00:56:55 -06:00
export class NetworkService {
static key: string = null;
static cert: string = null;
static proxyServer: http.Server;
static proxyServerAddress: AddressInfo;
2025-06-10 14:23:06 -05:00
static proxiedFiles: Map<string, PlayMessage> = new Map();
2024-12-09 00:56:55 -06:00
private static setupProxyServer(): Promise<void> {
return new Promise<void>((resolve, reject) => {
try {
2025-05-01 10:37:21 -05:00
logger.info(`Proxy server starting`);
2024-12-09 00:56:55 -06:00
const port = 0;
NetworkService.proxyServer = http.createServer((req, res) => {
2025-05-01 10:37:21 -05:00
logger.info(`Request received`);
2024-12-09 00:56:55 -06:00
const requestUrl = `http://${req.headers.host}${req.url}`;
const proxyInfo = NetworkService.proxiedFiles.get(requestUrl);
if (!proxyInfo) {
res.writeHead(404);
res.end('Not found');
return;
}
2025-06-10 14:23:06 -05:00
if (proxyInfo.url.startsWith('app://')) {
let start: number = 0;
let end: number = null;
const contentSize = MediaCache.getInstance().getObjectSize(proxyInfo.url);
if (req.headers.range) {
const range = req.headers.range.slice(6).split('-');
start = (range.length > 0) ? parseInt(range[0]) : 0;
end = (range.length > 1) ? parseInt(range[1]) : null;
}
logger.debug(`Fetching byte range from cache: start=${start}, end=${end}`);
const stream = MediaCache.getInstance().getObject(proxyInfo.url, start, end);
let responseCode = null;
let responseHeaders = null;
if (start != 0) {
responseCode = 206;
responseHeaders = {
'Accept-Ranges': 'bytes',
'Content-Length': contentSize - start,
'Content-Range': `bytes ${start}-${end ? end : contentSize - 1}/${contentSize}`,
'Content-Type': proxyInfo.container,
};
}
else {
responseCode = 200;
responseHeaders = {
'Accept-Ranges': 'bytes',
'Content-Length': contentSize,
'Content-Type': proxyInfo.container,
};
}
logger.debug(`Serving content ${proxyInfo.url} with response headers:`, responseHeaders);
res.writeHead(responseCode, responseHeaders);
stream.pipe(res);
}
else {
const omitHeaders = new Set([
'host',
'connection',
'keep-alive',
'proxy-authenticate',
'proxy-authorization',
'te',
'trailers',
'transfer-encoding',
'upgrade'
]);
const filteredHeaders = Object.fromEntries(Object.entries(req.headers)
.filter(([key]) => !omitHeaders.has(key.toLowerCase()))
.map(([key, value]) => [key, Array.isArray(value) ? value.join(', ') : value]));
const parsedUrl = url.parse(proxyInfo.url);
const options: http.RequestOptions = {
... parsedUrl,
method: req.method,
headers: { ...filteredHeaders, ...proxyInfo.headers }
};
const proxyReq = http.request(options, (proxyRes) => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res, { end: true });
});
req.pipe(proxyReq, { end: true });
proxyReq.on('error', (e) => {
logger.error(`Problem with request: ${e.message}`);
res.writeHead(500);
res.end();
});
}
2024-12-09 00:56:55 -06:00
});
NetworkService.proxyServer.on('error', e => {
reject(e);
});
NetworkService.proxyServer.listen(port, '127.0.0.1', () => {
NetworkService.proxyServerAddress = NetworkService.proxyServer.address() as AddressInfo;
2025-05-01 10:37:21 -05:00
logger.info(`Proxy server running at http://127.0.0.1:${NetworkService.proxyServerAddress.port}/`);
2024-12-09 00:56:55 -06:00
resolve();
});
} catch (e) {
reject(e);
}
});
}
static async proxyPlayIfRequired(message: PlayMessage): Promise<PlayMessage> {
2025-06-10 14:23:06 -05:00
if (message.url && (message.url.startsWith('app://') || (message.headers && !streamingMediaTypes.find(v => v === message.container.toLocaleLowerCase())))) {
return { ...message, url: await NetworkService.proxyFile(message) };
2024-12-09 00:56:55 -06:00
}
return message;
}
2025-06-10 14:23:06 -05:00
static async proxyFile(message: PlayMessage): Promise<string> {
2024-12-09 00:56:55 -06:00
if (!NetworkService.proxyServer) {
await NetworkService.setupProxyServer();
}
const proxiedUrl = `http://127.0.0.1:${NetworkService.proxyServerAddress.port}/${uuidv4()}`;
2025-06-10 14:23:06 -05:00
logger.info("Proxied url", { proxiedUrl, message });
NetworkService.proxiedFiles.set(proxiedUrl, message);
2024-12-09 00:56:55 -06:00
return proxiedUrl;
}
}