Appearance
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.pnghttp://localhost:3000/css/style.csshttp://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应用的性能和用户体验,同时确保安全性和可维护性。