Skip to content
On this page

Express静态文件服务

Express通过内置的express.static中间件提供静态文件服务功能,如CSS、JavaScript、图片等。这是Web应用开发中的基础功能。

基本静态文件服务

使用express.static中间件

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

// 提供public目录下的静态文件
app.use(express.static('public'));

项目结构示例

project/
├── app.js
├── public/
│   ├── images/
│   │   ├── logo.png
│   │   └── background.jpg
│   ├── css/
│   │   ├── style.css
│   │   └── responsive.css
│   ├── js/
│   │   ├── main.js
│   │   └── utils.js
│   └── favicon.ico

现在public目录下的所有文件都可以通过HTTP访问:

  • http://localhost:3000/images/logo.png
  • http://localhost:3000/css/style.css
  • http://localhost:3000/js/main.js

静态文件路径配置

自定义虚拟路径前缀

javascript
// 使用虚拟路径前缀,但实际文件仍在public目录
app.use('/static', express.static('public'));

// 现在文件可以通过以下路径访问:
// http://localhost:3000/static/images/logo.png
// http://localhost:3000/static/css/style.css

多个静态目录

javascript
// 提供多个静态目录
app.use(express.static('public'));
app.use(express.static('files'));
app.use(express.static('uploads'));

// 优先级按添加顺序:如果public和files中有同名文件,会使用public中的文件

高级配置选项

静态中间件配置选项

javascript
app.use(express.static('public', {
  // 设置缓存控制头
  maxAge: '1d',           // 设置缓存时间,1天
  
  // 设置ETag
  etag: true,             // 启用ETag (默认true)
  
  // 设置Last-Modified头
  lastModified: true,     // 启用Last-Modified (默认true)
  
  // 设置Accept-Ranges头
  acceptRanges: true,     // 启用范围请求 (默认true)
  
  // 设置缓存控制
  setHeaders: (res, path, stat) => {
    // 自定义响应头
    res.set('X-Content-Type-Options', 'nosniff');
    
    // 为特定文件类型设置特殊头
    if (path.endsWith('.js')) {
      res.set('Cache-Control', 'public, max-age=31536000'); // 1年缓存JS文件
    }
  },
  
  // 索引文件
  index: ['index.html', 'default.html'], // 指定索引文件
  
  // 是否重定向到带斜杠的路径
  redirect: true,          // 默认true
  
  // 隐藏以.开头的文件
  dotfiles: 'ignore'       // 'allow', 'deny', 'ignore'
}));

自定义缓存策略

javascript
// 为不同类型的文件设置不同的缓存策略
const path = require('path');

app.use(express.static('public', {
  setHeaders: (res, filepath) => {
    const extname = path.extname(filepath).toLowerCase();
    
    // 长期缓存静态资源
    if (extname === '.js' || extname === '.css' || extname === '.png' || extname === '.jpg') {
      res.setHeader('Cache-Control', 'public, max-age=31536000'); // 1年
    } else if (extname === '.html') {
      // HTML文件不缓存
      res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
    }
  }
}));

文件服务最佳实践

1. 安全考虑

javascript
app.use('/public', express.static('public', {
  // 防止访问隐藏文件
  dotfiles: 'ignore',
  
  // 自定义文件访问控制
  setHeaders: (res, path) => {
    // 防止某些文件类型被执行
    if (path.endsWith('.htaccess') || path.endsWith('.env')) {
      res.status(403).send('Forbidden');
      return;
    }
  }
}));

2. 性能优化

javascript
// 使用gzip压缩
const compression = require('compression');
app.use(compression());

// 静态文件缓存
app.use(express.static('public', {
  maxAge: '1y',           // 长期缓存
  etag: true,             // 启用ETag验证
  lastModified: true,     // 启用Last-Modified验证
  setHeaders: (res, path) => {
    // 防止敏感文件被缓存
    if (path.includes('private') || path.includes('temp')) {
      res.setHeader('Cache-Control', 'no-cache');
    }
  }
}));

高级静态文件服务

条件静态文件服务

javascript
// 根据条件提供不同目录的文件
app.use((req, res, next) => {
  if (req.path.startsWith('/admin')) {
    express.static('admin-public')(req, res, next);
  } else {
    express.static('public')(req, res, next);
  }
});

动态静态文件服务

javascript
// 根据用户角色提供不同访问权限
app.use('/private', (req, res, next) => {
  // 检查用户是否已认证
  if (!req.user || !req.user.isAuthenticated) {
    return res.status(403).send('Access denied');
  }
  
  // 如果已认证,提供静态文件
  express.static('private-files')(req, res, next);
});

静态文件与路由

静态文件与路由的优先级

javascript
// 静态文件中间件
app.use(express.static('public'));

// 路由 - 这些路由在静态文件之后定义
app.get('/users', (req, res) => {
  res.json({ message: 'Users API' });
});

// 如果public目录下有users.html文件,静态文件服务会优先匹配
// 所以/users请求会返回users.html而不是执行路由处理函数

避免冲突

javascript
// 将静态文件服务放在特定路径下以避免冲突
app.use('/assets', express.static('public'));

// 现在路由不会与静态文件冲突
app.get('/users', (req, res) => {
  res.json({ message: 'Users API' });
});

// 静态文件通过 /assets/ 访问

特殊文件处理

自定义favicon处理

javascript
const favicon = require('serve-favicon');
app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

自定义index文件

javascript
app.use(express.static('public', {
  index: 'welcome.html'  // 使用自定义索引文件
}));

// 或者禁用索引文件
app.use(express.static('public', {
  index: false
}));

静态文件上传和管理

文件上传服务

javascript
const multer = require('multer');
const path = require('path');

// 配置上传
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');  // 上传到uploads目录
  },
  filename: (req, file, cb) => {
    // 生成唯一文件名
    cb(null, Date.now() + '-' + file.originalname);
  }
});

const upload = multer({ storage });

// 上传路由
app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ 
    message: 'File uploaded successfully',
    filename: req.file.filename 
  });
});

// 使上传的文件可访问
app.use('/uploads', express.static('uploads'));

CDN和静态资源优化

静态资源版本控制

javascript
// 使用静态资源版本控制
app.use('/v1', express.static('public', {
  maxAge: '1y'
}));

// 或者通过查询参数实现版本控制
app.use(express.static('public', {
  setHeaders: (res, path) => {
    // 添加内容哈希作为版本
    const hash = require('crypto')
      .createHash('md5')
      .update(require('fs').readFileSync(path))
      .digest('hex')
      .substring(0, 8);
    
    res.setHeader('ETag', `"${hash}"`);
  }
}));

响应式图片服务

javascript
// 简单的响应式图片服务
app.get('/images/:filename', (req, res, next) => {
  const { filename } = req.params;
  const imagePath = path.join(__dirname, 'public', 'images', filename);
  
  // 检查设备类型并提供合适尺寸的图片
  const userAgent = req.get('User-Agent');
  let imageFile = imagePath;
  
  if (userAgent && userAgent.includes('Mobile')) {
    // 为移动设备提供小尺寸图片
    const mobileImagePath = imagePath.replace(/(\.[^.]+)$/, '-mobile$1');
    if (require('fs').existsSync(mobileImagePath)) {
      imageFile = mobileImagePath;
    }
  }
  
  res.sendFile(imageFile, (err) => {
    if (err) {
      next(); // 如果文件不存在,继续下一个中间件
    }
  });
});

错误处理

静态文件错误处理

javascript
// 404错误处理 - 应在所有中间件之后
app.use((req, res, next) => {
  res.status(404).sendFile(path.join(__dirname, 'public', '404.html'));
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

性能监控

静态文件访问统计

javascript
// 记录静态文件访问
app.use('/static', (req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.originalUrl} - ${duration}ms`);
  });
  
  next();
}, express.static('public'));

通过合理配置静态文件服务,可以提高Web应用的性能和用户体验,同时确保安全性和可维护性。