@holix/static
v0.0.3
Published
Static file middleware for @holix/router with pattern matching and blacklist support
Maintainers
Readme
@holix/static
静态文件服务中间件,用于 @holix/router 的自定义协议路由。支持文件过滤、缓存控制、目录列表等功能。
特性
- ✅ 基于 glob 模式的黑名单/白名单文件过滤
- ✅ 智能缓存控制(ETag、Cache-Control)
- ✅ 目录列表自动生成
- ✅ 路径遍历攻击防护
- ✅ 符号链接控制
- ✅ 自定义 MIME 类型
- ✅ 404/错误回调处理
- ✅ 40+ 内置 MIME 类型
安装
pnpm add @holix/static基本使用
import path from 'node:path'
import { createRouter } from '@holix/router'
import { createStaticMiddleware } from '@holix/static'
const router = createRouter()
const staticDir = path.resolve(__dirname, './public')
// 创建静态文件中间件
const staticMiddleware = createStaticMiddleware({
root: staticDir,
prefix: '/_static', // 可选,URL 前缀
})
// 注册中间件
router.use(staticMiddleware)
// 现在可以访问:
// holix://app/_static/style.css
// holix://app/_static/images/logo.png配置选项
StaticMiddlewareOptions
interface StaticMiddlewareOptions {
/** 静态文件根目录(绝对路径) */
root: string
/** URL 路径前缀,例如 '/_static' 或 '/assets' */
prefix?: string
/** 是否启用缓存(默认: true) */
cache?: boolean
/** 缓存最大时长(秒),默认: 31536000 (1 年) */
maxAge?: number
/** 黑名单模式列表(支持 glob 语法) */
blacklist?: string[]
/** 白名单模式列表(支持 glob 语法,优先级高于黑名单) */
whitelist?: string[]
/** 是否启用目录列表(默认: false) */
index?: boolean
/** 索引文件名(默认: ['index.html']) */
indexFiles?: string[]
/** 是否跟随符号链接(默认: false) */
followSymlinks?: boolean
/** 自定义 MIME 类型映射 */
mimeTypes?: Record<string, string>
/** 404 处理函数 */
onNotFound?: (ctx: ExtendedRouteContext, filePath: string) => void
/** 错误处理函数 */
onError?: (ctx: ExtendedRouteContext, error: Error) => void
}使用示例
1. 基本静态文件服务
import { createStaticMiddleware } from '@holix/static'
const middleware = createStaticMiddleware({
root: '/path/to/public',
})2. 使用 URL 前缀
const middleware = createStaticMiddleware({
root: '/path/to/public',
prefix: '/_static',
})
// 访问: holix://app/_static/style.css
// 映射到: /path/to/public/style.css3. 文件过滤 - 黑名单
const middleware = createStaticMiddleware({
root: '/path/to/public',
blacklist: [
'**/.git/**', // 屏蔽 .git 目录
'**/.env*', // 屏蔽 .env 文件
'**/config.json', // 屏蔽配置文件
],
})默认黑名单 (自动包含):
**/.git/****/.svn/****/node_modules/****/.env***/package-lock.json**/pnpm-lock.yaml**/yarn.lock
4. 文件过滤 - 白名单
白名单优先级高于黑名单。只有匹配白名单的文件才允许访问。
const middleware = createStaticMiddleware({
root: '/path/to/public',
whitelist: [
'**/*.js', // 只允许 JS 文件
'**/*.css', // 只允许 CSS 文件
'**/*.png', // 只允许 PNG 图片
'**/*.jpg', // 只允许 JPG 图片
],
})5. 启用目录列表
const middleware = createStaticMiddleware({
root: '/path/to/public',
index: true, // 启用目录列表
indexFiles: ['index.html', 'index.htm'], // 优先查找的索引文件
})6. 禁用缓存(开发模式)
const middleware = createStaticMiddleware({
root: '/path/to/public',
cache: false, // 禁用缓存
})7. 自定义缓存时长
const middleware = createStaticMiddleware({
root: '/path/to/public',
cache: true,
maxAge: 86400, // 缓存 1 天 (单位: 秒)
})8. 自定义 MIME 类型
const middleware = createStaticMiddleware({
root: '/path/to/public',
mimeTypes: {
'.md': 'text/markdown',
'.ts': 'text/typescript',
},
})9. 错误处理回调
const middleware = createStaticMiddleware({
root: '/path/to/public',
onNotFound: (ctx, filePath) => {
console.log('File not found:', filePath)
ctx.res.statusCode = 404
ctx.res.setHeader('Content-Type', 'application/json')
ctx.res.end(JSON.stringify({ error: 'File not found' }))
},
onError: (ctx, error) => {
console.error('Static file error:', error)
ctx.res.statusCode = 500
ctx.res.end('Internal Server Error')
},
})Glob 模式语法
使用 micromatch 进行模式匹配:
// 基本通配符
'*.js' // 匹配任何 .js 文件
'script.js' // 精确匹配
// 目录通配符
'**/*.css' // 匹配所有子目录中的 .css 文件
'src/**/*.ts' // 匹配 src 下所有 .ts 文件
// 字符集
'file[0-9].txt' // 匹配 file0.txt, file1.txt, ...
'*.{js,ts}' // 匹配 .js 或 .ts 文件
// 否定模式
'!test/**' // 排除 test 目录缓存策略
1. ETag 缓存
中间件自动为每个文件生成 ETag(基于文件修改时间和大小):
ETag: "5f7b8c9d-1234"客户端下次请求时携带 If-None-Match,如果 ETag 匹配则返回 304 Not Modified。
2. 不可变缓存
对于包含 hash 的文件(如 bundle-abc123.js),自动设置不可变缓存:
Cache-Control: public, max-age=31536000, immutable3. 普通缓存
其他文件使用标准缓存:
Cache-Control: public, max-age=31536000安全特性
1. 路径遍历防护
自动检测并阻止路径遍历攻击:
GET /../../../etc/passwd ❌ 返回 400
GET /%2e%2e%2f%2e%2e%2f ❌ 返回 4002. 符号链接控制
默认不跟随符号链接,防止访问根目录外的文件:
createStaticMiddleware({
root: '/var/www',
followSymlinks: false, // 默认值
})3. 默认黑名单
自动屏蔽敏感文件和目录(.git, .env, node_modules 等)。
内置 MIME 类型
支持 40+ 常见 MIME 类型:
| 扩展名 | MIME 类型 |
|--------|-----------|
| .html, .htm | text/html |
| .css | text/css |
| .js, .mjs | application/javascript |
| .json | application/json |
| .png | image/png |
| .jpg, .jpeg | image/jpeg |
| .svg | image/svg+xml |
| .woff2 | font/woff2 |
| .mp4 | video/mp4 |
| ... | ... |
完整列表请参考源码中的 DEFAULT_MIME_TYPES。
最佳实践
开发环境
createStaticMiddleware({
root: './public',
cache: false, // 禁用缓存,便于调试
index: true, // 启用目录列表
})生产环境
createStaticMiddleware({
root: './dist',
cache: true, // 启用缓存
maxAge: 31536000, // 1 年缓存
blacklist: [ // 严格的黑名单
'**/.git/**',
'**/.env*',
'**/config/**',
],
})Electron 应用
import path from 'node:path'
import { app } from 'electron'
const staticDir = app.isPackaged
? path.join(process.resourcesPath, 'public')
: path.join(__dirname, '../public')
createStaticMiddleware({
root: staticDir,
prefix: '/_assets',
cache: true,
})与 router-ssr 集成
配合 @holix/router-ssr 使用,服务打包后的 chunk 文件:
import { createRouter } from '@holix/router'
import { createSSRMiddleware } from '@holix/router-ssr'
import { createStaticMiddleware } from '@holix/static'
import path from 'node:path'
const router = createRouter()
// 1. 静态文件中间件(服务 _chunks)
router.use(createStaticMiddleware({
root: path.resolve(__dirname, './dist'),
cache: true,
}))
// 2. SSR 中间件(处理路由)
router.use(createSSRMiddleware({
routes: [...],
framework: 'vue',
}))访问流程:
holix://app/about→ SSR 渲染 HTMLholix://app/_chunks/about-C5TTlyQe.js→ 静态文件服务
工具函数
包提供了一些实用工具函数:
import {
formatFileSize,
getExtension,
isHiddenFile,
isSafePath,
matchPatterns,
} from '@holix/static'
// 匹配 glob 模式
matchPatterns('src/index.ts', ['**/*.ts']) // true
// 检测路径安全性
isSafePath('../../../etc/passwd') // false
// 格式化文件大小
formatFileSize(1024) // '1.00 KB'
// 获取扩展名
getExtension('script.js') // '.js'
// 检测隐藏文件
isHiddenFile('.gitignore') // trueAPI 参考
createStaticMiddleware(options)
创建静态文件中间件。
- 参数:
StaticMiddlewareOptions - 返回:
StaticMiddleware函数
matchPatterns(path, patterns)
检查路径是否匹配 glob 模式列表。
- 参数:
path: 要检查的路径patterns: glob 模式数组
- 返回:
boolean
isSafePath(path)
检查路径是否安全(无路径遍历)。
- 参数:
path- 要检查的路径 - 返回:
boolean
formatFileSize(bytes)
格式化文件大小为人类可读格式。
- 参数:
bytes- 字节数 - 返回:
string(例如:'1.50 MB')
getExtension(filename)
获取文件扩展名(小写)。
- 参数:
filename- 文件名 - 返回:
string(例如:'.js')
isHiddenFile(filename)
检查文件是否为隐藏文件(以 . 开头)。
- 参数:
filename- 文件名 - 返回:
boolean
License
MIT
