位置: 文档库 > JavaScript > 如何使用node.js创建http文件服务器

如何使用node.js创建http文件服务器

NovaNebula 上传于 2024-04-26 23:13

如何使用Node.js创建HTTP文件服务器

Node.js作为基于Chrome V8引擎的JavaScript运行时环境,以其非阻塞I/O和事件驱动特性成为构建高性能网络应用的理想选择。本文将详细介绍如何利用Node.js内置的HTTP模块和文件系统模块(fs)创建功能完整的文件服务器,涵盖基础实现、进阶功能、安全优化及性能调优等核心内容。

一、基础HTTP服务器搭建

Node.js的http模块提供了创建Web服务器的底层能力。以下代码展示如何启动一个监听8080端口的HTTP服务器:

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
});

server.listen(8080, () => {
  console.log('Server running at http://localhost:8080/');
});

这段代码创建了最基本的HTTP服务器,但尚未实现文件服务功能。我们需要结合fs模块读取文件系统内容。

二、文件服务核心实现

完整的文件服务器需要处理路径解析、文件读取和MIME类型映射。以下是分步骤实现:

1. 路径处理与安全校验

使用path模块规范化请求路径,防止目录遍历攻击:

const path = require('path');
const fs = require('fs');

function getSafePath(reqUrl, rootDir = './public') {
  const parsedUrl = new URL(reqUrl, `http://${req.headers.host}`);
  let filepath = decodeURIComponent(parsedUrl.pathname);
  
  // 规范化路径并限制在根目录下
  filepath = path.normalize(path.join(rootDir, filepath));
  if (!filepath.startsWith(path.join(process.cwd(), rootDir))) {
    throw new Error('Access denied');
  }
  return filepath;
}

2. MIME类型映射

建立文件扩展名到MIME类型的映射表:

const mimeTypes = {
  '.html': 'text/html',
  '.js': 'text/javascript',
  '.css': 'text/css',
  '.json': 'application/json',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.gif': 'image/gif',
  '.svg': 'image/svg+xml',
  '.wav': 'audio/wav',
  '.mp4': 'video/mp4',
  '.woff': 'application/font-woff',
  '.ttf': 'application/font-ttf',
  '.eot': 'application/vnd.ms-fontobject',
  '.otf': 'application/font-otf',
  '.wasm': 'application/wasm'
};

3. 完整文件服务逻辑

结合上述组件实现文件读取和响应:

function serveFile(req, res, filepath) {
  fs.stat(filepath, (err, stats) => {
    if (err) {
      if (err.code === 'ENOENT') {
        res.writeHead(404);
        return res.end('404 Not Found');
      }
      res.writeHead(500);
      return res.end('Internal Server Error');
    }

    if (stats.isDirectory()) {
      // 如果是目录,默认返回index.html或列出文件
      const indexPath = path.join(filepath, 'index.html');
      fs.access(indexPath, fs.constants.F_OK, (err) => {
        if (!err) {
          serveFile(req, res, indexPath);
        } else {
          listDirectory(req, res, filepath);
        }
      });
      return;
    }

    // 获取文件扩展名对应的MIME类型
    const ext = path.parse(filepath).ext;
    const contentType = mimeTypes[ext] || 'application/octet-stream';

    // 创建可读流处理大文件
    const readStream = fs.createReadStream(filepath);
    res.writeHead(200, { 'Content-Type': contentType });
    readStream.pipe(res);

    readStream.on('error', (err) => {
      res.writeHead(500);
      res.end('File Reading Error');
    });
  });
}

function listDirectory(req, res, dirpath) {
  fs.readdir(dirpath, (err, files) => {
    if (err) {
      res.writeHead(500);
      return res.end('Directory Listing Error');
    }

    let html = '

Directory Listing

    '; files.forEach(file => { const stat = fs.statSync(path.join(dirpath, file)); const isDir = stat.isDirectory() ? '/' : ''; html += `
  • ${file}${isDir}
  • `; }); html += '
'; res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); }); }

三、完整服务器实现

将上述组件整合为完整服务器:

const http = require('http');
const path = require('path');
const fs = require('fs');

const PORT = 8080;
const ROOT_DIR = './public';

const mimeTypes = { /* 同上 */ };

const server = http.createServer((req, res) => {
  try {
    const filepath = getSafePath(req.url, ROOT_DIR);
    serveFile(req, res, filepath);
  } catch (err) {
    res.writeHead(403);
    res.end('Forbidden');
  }
});

server.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}/`);
});

四、进阶功能实现

1. 缓存控制

通过Last-Modified和ETag实现缓存:

function serveFileWithCache(req, res, filepath) {
  fs.stat(filepath, (err, stats) => {
    // ...原有错误处理...

    const lastModified = stats.mtime.toUTCString();
    const etag = stats.size + '-' + Number(stats.mtime);

    // 检查客户端缓存
    const ifModifiedSince = req.headers['if-modified-since'];
    const ifNoneMatch = req.headers['if-none-match'];

    if (ifModifiedSince && ifNoneMatch) {
      if (ifModifiedSince === lastModified && ifNoneMatch === etag) {
        res.writeHead(304);
        return res.end();
      }
    }

    // 设置缓存头
    res.setHeader('Last-Modified', lastModified);
    res.setHeader('ETag', etag);
    res.setHeader('Cache-Control', 'public, max-age=86400'); // 24小时缓存

    // ...原有文件服务逻辑...
  });
}

2. 压缩支持

使用zlib模块实现Gzip压缩:

const zlib = require('zlib');

function serveCompressed(req, res, filepath) {
  const acceptEncoding = req.headers['accept-encoding'] || '';
  const canGzip = acceptEncoding.includes('gzip');

  fs.readFile(filepath, (err, data) => {
    if (err) { /* 错误处理 */ }

    const ext = path.parse(filepath).ext;
    const contentType = mimeTypes[ext] || 'application/octet-stream';

    if (canGzip) {
      res.writeHead(200, {
        'Content-Type': contentType,
        'Content-Encoding': 'gzip',
        'Vary': 'Accept-Encoding'
      });
      zlib.gzip(data, (err, compressed) => {
        if (err) { /* 错误处理 */ }
        res.end(compressed);
      });
    } else {
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(data);
    }
  });
}

3. 范围请求支持

实现视频/大文件的分块传输:

function serveRange(req, res, filepath) {
  const range = req.headers.range;
  if (!range) {
    return serveFile(req, res, filepath); // 无range头则正常传输
  }

  fs.stat(filepath, (err, stats) => {
    if (err) { /* 错误处理 */ }

    const fileSize = stats.size;
    const [start, end] = range.replace(/bytes=/, '').split('-');
    const realStart = parseInt(start, 10);
    const realEnd = end ? parseInt(end, 10) : fileSize - 1;
    const chunkSize = (realEnd - realStart) + 1;

    const ext = path.parse(filepath).ext;
    const contentType = mimeTypes[ext] || 'application/octet-stream';

    res.writeHead(206, {
      'Content-Range': `bytes ${realStart}-${realEnd}/${fileSize}`,
      'Accept-Ranges': 'bytes',
      'Content-Length': chunkSize,
      'Content-Type': contentType
    });

    const stream = fs.createReadStream(filepath, { start: realStart, end: realEnd });
    stream.pipe(res);
  });
}

五、安全优化

1. 请求限制

防止DDoS攻击和过大请求:

const MAX_BODY_SIZE = 1024 * 1024; // 1MB

server.on('request', (req, res) => {
  let body = [];
  req.on('data', chunk => {
    body.push(chunk);
    if (Buffer.concat(body).length > MAX_BODY_SIZE) {
      req.destroy(); // 终止过大请求
      res.writeHead(413);
      return res.end('Payload Too Large');
    }
  });
  // ...原有处理逻辑...
});

2. HTTPS支持

使用SSL/TLS加密通信:

const https = require('https');
const fs = require('fs');

const options = {
  key: fs.readFileSync('server.key'),
  cert: fs.readFileSync('server.cert')
};

https.createServer(options, (req, res) => {
  // 原有HTTP处理逻辑
}).listen(443);

3. CORS配置

跨域资源共享设置:

function setCORSHeaders(res, allowedOrigins = ['*']) {
  const origin = req.headers.origin;
  if (allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
    res.setHeader('Access-Control-Allow-Origin', origin || '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  }
}

六、性能优化

1. 集群模式

利用多核CPU:

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`Master ${process.pid} is running`);
  for (let i = 0; i  {
    console.log(`Worker ${worker.process.pid} died`);
    cluster.fork();
  });
} else {
  // 工作进程代码(原服务器代码)
  const server = http.createServer(/*...*/);
  server.listen(8080);
}

2. 静态文件中间件

使用express.static简化实现:

const express = require('express');
const app = express();

app.use(express.static('public', {
  dotfiles: 'ignore',
  etag: true,
  lastModified: true,
  maxAge: '1d',
  redirect: false
}));

app.listen(8080);

七、完整示例项目结构

project/
├── public/            # 静态文件目录
│   ├── index.html
│   ├── style.css
│   └── script.js
├── server.js          # 主服务器文件
├── certs/             # SSL证书
│   ├── server.key
│   └── server.cert
└── package.json

八、部署建议

1. 使用PM2进行进程管理:

npm install -g pm2
pm2 start server.js --name "file-server" -i max

2. 配置Nginx反向代理:

server {
  listen 80;
  server_name example.com;
  
  location / {
    proxy_pass http://localhost:8080;
    proxy_set_header Host $host;
  }
}

关键词:Node.js、HTTP服务器、文件服务、MIME类型路径安全缓存控制压缩传输范围请求、集群模式、静态文件中间件

简介:本文系统讲解了使用Node.js创建HTTP文件服务器的完整方案,涵盖基础实现、安全防护、性能优化等核心模块,包含路径处理、MIME类型映射、缓存控制、Gzip压缩、范围请求等关键技术,并提供HTTPS、CORS、集群模式等进阶配置方法,最终给出完整的项目结构和部署建议。