UNPKG

zyf-server

Version:

A modern HTTP static file server with Vue SSR directory listing, built for developers

245 lines 7.91 kB
"use strict"; /** * HTTP静态文件服务器核心类 * 采用现代化的TypeScript面向对象设计 */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Server = void 0; const http_1 = require("http"); const path_1 = require("path"); const chalk_1 = __importDefault(require("chalk")); const open_1 = __importDefault(require("open")); const FileService_1 = require("./FileService"); const TemplateEngine_1 = require("./TemplateEngine"); // import { CompressionService } from './CompressionService'; const CacheManager_1 = require("./CacheManager"); const Logger_1 = require("../utils/Logger"); /** * HTTP静态文件服务器 */ class Server { httpServer; config; fileService; templateEngine; // private readonly _compressionService: CompressionService; cacheManager; logger; /** * 构造函数 * @param config 服务器配置 */ constructor(config) { this.config = Object.freeze({ ...config }); this.logger = new Logger_1.Logger('Server'); // 初始化服务 this.fileService = new FileService_1.FileService(); this.templateEngine = new TemplateEngine_1.TemplateEngine(); // this._compressionService = new CompressionService(); this.cacheManager = new CacheManager_1.CacheManager(this.config.cache); // 创建HTTP服务器 this.httpServer = (0, http_1.createServer)(this.handleRequest.bind(this)); this.setupErrorHandlers(); } /** * 启动服务器 */ async start() { return new Promise((resolve, reject) => { this.httpServer.listen(this.config.port, this.config.host, () => { const url = `http://${this.config.host}:${chalk_1.default.green(this.config.port)}`; this.logger.info(`Server started: ${url}`); // 自动打开浏览器 if (this.config.open) { this.openBrowser(url); } resolve(); }); this.httpServer.on('error', (error) => { if (error.code === 'EADDRINUSE') { const message = `Port ${this.config.port} is already in use`; this.logger.error(message); reject(new Error(message)); } else { reject(error); } }); }); } /** * 停止服务器 */ async stop() { return new Promise((resolve) => { this.httpServer.close(() => { this.logger.info('Server stopped'); resolve(); }); }); } /** * 处理HTTP请求 */ async handleRequest(req, res) { try { await this.processRequest(req, res); } catch (error) { this.handleError(error, req, res); } } /** * 处理请求逻辑 */ async processRequest(req, res) { const url = new URL(req.url, `http://${req.headers.host}`); const pathname = decodeURIComponent(url.pathname); const fullPath = (0, path_1.join)(this.config.dir, pathname); this.logger.debug(`${req.method} ${pathname}`); // 检查文件是否存在 const exists = await this.fileService.exists(fullPath); if (!exists) { this.sendNotFound(res); return; } const stats = await this.fileService.stat(fullPath); if (stats.isDirectory()) { await this.handleDirectory(fullPath, pathname, req, res); } else { await this.handleFile(fullPath, req, res, stats); } } /** * 处理目录请求 */ async handleDirectory(fullPath, pathname, req, res) { // 尝试查找index.html const indexPath = (0, path_1.join)(fullPath, 'index.html'); const hasIndex = await this.fileService.exists(indexPath); if (hasIndex) { const indexStats = await this.fileService.stat(indexPath); await this.handleFile(indexPath, req, res, indexStats); } else { await this.renderDirectoryListing(fullPath, pathname, req, res); } } /** * 处理文件请求 */ async handleFile(filePath, req, res, stats) { // 检查缓存 if (this.cacheManager.isNotModified(req, stats)) { res.statusCode = 304; res.end(); return; } // 设置缓存头 this.cacheManager.setHeaders(res, stats); // 处理Range请求 const range = this.parseRangeHeader(req.headers.range, stats.size); if (range) { res.statusCode = 206; res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${stats.size}`); res.setHeader('Accept-Ranges', 'bytes'); res.setHeader('Content-Length', range.end - range.start + 1); } else { res.setHeader('Content-Length', stats.size); } // 设置MIME类型 const mimeType = this.fileService.getMimeType(filePath); res.setHeader('Content-Type', `${mimeType}; charset=utf-8`); // 发送文件 await this.fileService.sendFile(filePath, req, res, range); } /** * 渲染目录列表 */ async renderDirectoryListing(dirPath, pathname, _req, res) { const items = await this.fileService.readDirectory(dirPath); // 格式化目录项 const dirs = items.map(item => ({ name: item.name, path: (0, path_1.join)(pathname, item.name) + (item.isDirectory ? '/' : ''), isDirectory: item.isDirectory, size: item.size, mtime: item.mtime })); // 使用Vue SSR渲染 const html = await this.templateEngine.renderVueSSR({ dirs }); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.end(html); } /** * 解析Range头 */ parseRangeHeader(rangeHeader, fileSize) { if (!rangeHeader) return null; const match = rangeHeader.match(/bytes=(\d*)-(\d*)/); if (!match) return null; const start = match[1] ? parseInt(match[1], 10) : 0; const end = match[2] ? parseInt(match[2], 10) : fileSize - 1; return { start, end }; } /** * 发送404错误 */ sendNotFound(res) { res.statusCode = 404; res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.end('404 Not Found'); } /** * 错误处理 */ handleError(error, _req, res) { this.logger.error('Request error:', error); if (!res.headersSent) { res.statusCode = 500; res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.end('500 Internal Server Error'); } } /** * 设置错误处理器 */ setupErrorHandlers() { this.httpServer.on('clientError', (err, socket) => { this.logger.error('Client error:', err); socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }); } /** * 打开浏览器 */ async openBrowser(url) { try { await (0, open_1.default)(url, { app: { name: 'google chrome' } }); } catch (error) { this.logger.warn('Failed to open browser:', error); } } /** * 获取服务器配置 */ getConfig() { return this.config; } /** * 获取服务器状态 */ isListening() { return this.httpServer.listening; } } exports.Server = Server; //# sourceMappingURL=Server.js.map