/** * WebDAV 文件代理服务器 * 提供 WebDAV 文件的 HTTP 直链访问功能 * * 使用方法: * 1. 启动服务器:node webdav-proxy.js * 2. 访问文件:http://localhost:3000/file?path=/path/to/file * 3. 支持 Range 请求,适合视频流媒体播放 */ import Fastify from 'fastify'; import { WebDAVClient } from '../utils/webdav.js'; import { readFileSync } from 'fs'; import path from 'path'; class WebDAVProxy { constructor(options = {}) { this.port = options.port || 3000; this.host = options.host || 'localhost'; this.webdavClients = new Map(); // 缓存 WebDAV 客户端 this.fileCache = new Map(); // 文件信息缓存 this.cacheTimeout = options.cacheTimeout || 5 * 60 * 1000; // 5分钟缓存 this.app = Fastify({ logger: false // 使用自定义日志 }); this.init(); } async init() { await this.setupMiddleware(); this.setupRoutes(); } async setupMiddleware() { // 手动添加 CORS 头 this.app.addHook('onRequest', async (request, reply) => { reply.header('Access-Control-Allow-Origin', '*'); reply.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); reply.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Range'); // 添加请求日志 console.log(`${new Date().toISOString()} - ${request.method} ${request.url}`); }); // 处理 OPTIONS 预检请求 this.app.options('*', async (request, reply) => { reply.code(200).send(); }); } setupRoutes() { // 健康检查 this.app.get('/health', async (request, reply) => { return { status: 'ok', timestamp: new Date().toISOString() }; }); // 文件直链访问 this.app.get('/file', this.handleFileRequest.bind(this)); // 文件信息获取 this.app.get('/info', this.handleInfoRequest.bind(this)); // 目录列表 this.app.get('/list', this.handleListRequest.bind(this)); // 配置 WebDAV 连接 this.app.post('/config', this.handleConfigRequest.bind(this)); // 错误处理 this.app.setErrorHandler(this.errorHandler.bind(this)); } /** * 获取或创建 WebDAV 客户端 */ getWebDAVClient(config) { const key = `${config.baseURL}:${config.username}`; if (!this.webdavClients.has(key)) { const client = new WebDAVClient(config); this.webdavClients.set(key, client); } return this.webdavClients.get(key); } /** * 处理文件请求 */ async handleFileRequest(request, reply) { try { const { path: filePath, config: configParam } = request.query; if (!filePath) { reply.code(400); return { error: 'Missing path parameter' }; } // 获取 WebDAV 配置 let config; if (configParam) { try { config = JSON.parse(decodeURIComponent(configParam)); } catch (e) { reply.code(400); return { error: 'Invalid config parameter' }; } } else { // 尝试从默认配置文件读取 config = this.loadDefaultConfig(); } if (!config) { reply.code(400); return { error: 'WebDAV config not provided' }; } const client = this.getWebDAVClient(config); // 检查缓存 const cacheKey = `${config.baseURL}:${filePath}`; const cached = this.fileCache.get(cacheKey); const now = Date.now(); let fileInfo; if (cached && (now - cached.timestamp) < this.cacheTimeout) { fileInfo = cached.info; } else { // 获取文件信息并缓存 fileInfo = await client.getInfo(filePath); this.fileCache.set(cacheKey, { info: fileInfo, timestamp: now }); } if (!fileInfo || fileInfo.isDirectory) { reply.code(404); return { error: 'File not found or is a directory' }; } // 处理 Range 请求 const range = request.headers.range; let streamOptions = {}; if (range) { const parts = range.replace(/bytes=/, "").split("-"); const start = parseInt(parts[0], 10); const end = parts[1] ? parseInt(parts[1], 10) : fileInfo.size - 1; if (start >= fileInfo.size || end >= fileInfo.size) { reply.code(416); reply.header('Content-Range', `bytes */${fileInfo.size}`); return; } streamOptions.headers = { 'Range': `bytes=${start}-${end}` }; reply.code(206); reply.header('Content-Range', `bytes ${start}-${end}/${fileInfo.size}`); reply.header('Accept-Ranges', 'bytes'); reply.header('Content-Length', end - start + 1); } else { reply.header('Content-Length', fileInfo.size); reply.header('Accept-Ranges', 'bytes'); } // 设置响应头 reply.header('Content-Type', fileInfo.contentType || 'application/octet-stream'); if (fileInfo.lastModified) { reply.header('Last-Modified', new Date(fileInfo.lastModified).toUTCString()); } if (fileInfo.etag) { reply.header('ETag', fileInfo.etag); } reply.header('Cache-Control', 'public, max-age=3600'); // 获取文件流 const { stream } = await client.getFileStream(filePath, streamOptions); // 返回流 return reply.send(stream); } catch (error) { console.error('File request error:', error); reply.code(500); return { error: error.message }; } } /** * 处理文件信息请求 */ async handleInfoRequest(request, reply) { try { const { path: filePath, config: configParam } = request.query; if (!filePath) { reply.code(400); return { error: 'Missing path parameter' }; } let config; if (configParam) { config = JSON.parse(decodeURIComponent(configParam)); } else { config = this.loadDefaultConfig(); } if (!config) { reply.code(400); return { error: 'WebDAV config not provided' }; } const client = this.getWebDAVClient(config); const fileInfo = await client.getInfo(filePath); return fileInfo; } catch (error) { console.error('Info request error:', error); reply.code(500); return { error: error.message }; } } /** * 处理目录列表请求 */ async handleListRequest(request, reply) { try { const { path: dirPath = '/', config: configParam } = request.query; let config; if (configParam) { config = JSON.parse(decodeURIComponent(configParam)); } else { config = this.loadDefaultConfig(); } if (!config) { reply.code(400); return { error: 'WebDAV config not provided' }; } const client = this.getWebDAVClient(config); const items = await client.listDirectory(dirPath); return items; } catch (error) { console.error('List request error:', error); reply.code(500); return { error: error.message }; } } /** * 处理配置请求 */ async handleConfigRequest(request, reply) { try { const config = request.body; // 验证配置 if (!config.baseURL || !config.username || !config.password) { reply.code(400); return { error: 'Missing required config fields' }; } // 测试连接 const client = this.getWebDAVClient(config); await client.testConnection(); return { success: true, message: 'WebDAV connection configured successfully' }; } catch (error) { console.error('Config request error:', error); reply.code(500); return { error: error.message }; } } /** * 加载默认配置 */ loadDefaultConfig() { try { const configPath = path.join(process.cwd(), 'json', 'webdav.json'); const configData = readFileSync(configPath, 'utf8'); const parsed = JSON.parse(configData); // 如果是数组,取第一个配置 if (Array.isArray(parsed)) { return parsed.length > 0 ? parsed[0] : null; } return parsed; } catch (error) { console.warn('Could not load default WebDAV config:', error.message); return null; } } /** * 错误处理中间件 */ async errorHandler(error, request, reply) { console.error('Unhandled error:', error); reply.code(500); return { error: 'Internal server error' }; } /** * 启动服务器 */ async start() { try { await this.app.listen({ port: this.port, host: this.host }); console.log(`WebDAV Proxy Server started on http://${this.host}:${this.port}`); console.log('Available endpoints:'); console.log(` GET /file?path= - Get file direct link`); console.log(` GET /info?path= - Get file information`); console.log(` GET /list?path= - List directory contents`); console.log(` POST /config - Configure WebDAV connection`); console.log(` GET /health - Health check`); } catch (error) { console.error('Failed to start server:', error); throw error; } } /** * 停止服务器 */ async stop() { try { await this.app.close(); console.log('WebDAV Proxy Server stopped'); } catch (error) { console.error('Error stopping server:', error); } } /** * 清理缓存 */ clearCache() { this.fileCache.clear(); this.webdavClients.clear(); console.log('Cache cleared'); } } // 如果直接运行此文件,启动服务器 if (import.meta.url === `file://${process.argv[1]}`) { const proxy = new WebDAVProxy({ port: process.env.WEBDAV_PROXY_PORT || 3000, host: process.env.WEBDAV_PROXY_HOST || 'localhost' }); proxy.start().catch(console.error); // 优雅关闭 process.on('SIGINT', async () => { console.log('\nShutting down gracefully...'); await proxy.stop(); process.exit(0); }); } export default WebDAVProxy; export { WebDAVProxy };