zyf-server
Version:
A modern HTTP static file server with Vue SSR directory listing, built for developers
245 lines • 7.91 kB
JavaScript
;
/**
* 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