fenghuo/packages/tus/src/handlers/GetHandler.ts

190 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 文件模块GetHandler.ts
* 功能描述负责处理HTTP GET请求提供文件下载和流式传输功能
* 使用场景适用于需要实现文件下载、流媒体播放等功能的Web服务
*/
import stream from 'node:stream'
import { BaseHandler } from './BaseHandler'
import type http from 'node:http'
import type { RouteHandler } from '../types'
import { ERRORS, Upload } from '../utils'
/**
* GetHandler类
* 核心功能处理GET请求支持自定义路径处理和文件流传输
* 设计模式:基于策略模式实现路径处理函数的动态注册
* 使用示例:
* const handler = new GetHandler()
* handler.registerPath('/custom', customHandler)
*/
export class GetHandler extends BaseHandler {
// 使用Map存储路径与处理函数的映射关系提供O(1)的查找时间复杂度
paths: Map<string,RouteHandler> = new Map()
/**
* 正则表达式用于验证MIME类型是否符合RFC1341规范
* 支持带参数的MIME类型text/plain; charset=utf-8
* 时间复杂度O(n)n为字符串长度
* 优化建议:可考虑预编译正则表达式以提高性能
*/
reMimeType =
// biome-ignore lint/suspicious/noControlCharactersInRegex: it's fine
/^(?:application|audio|example|font|haptics|image|message|model|multipart|text|video|x-(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+))\/([0-9A-Za-z!#$%&'*+.^_`|~-]+)((?:[ ]*;[ ]*[0-9A-Za-z!#$%&'*+.^_`|~-]+=(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+|"(?:[^"\\]|\.)*"))*)$/
/**
* 允许浏览器内联渲染的MIME类型白名单
* 使用Set数据结构提供O(1)的查找时间复杂度
* 优化建议:可根据实际业务需求动态调整白名单
*/
mimeInlineBrowserWhitelist = new Set([
'text/plain',
'image/png',
'image/jpeg',
'image/gif',
'image/bmp',
'image/webp',
'audio/wave',
'audio/wav',
'audio/x-wav',
'audio/x-pn-wav',
'audio/webm',
'audio/ogg',
'video/mp4',
'video/webm',
'video/ogg',
'application/ogg',
])
/**
* 注册路径处理函数
* 功能描述:将路径与处理函数进行绑定
* 输入参数:
* - path: 请求路径
* - handler: 处理函数
* 时间复杂度O(1)
* 优化建议:可添加路径冲突检测机制
*/
registerPath(path: string, handler: RouteHandler): void {
this.paths.set(path, handler)
}
/**
* 发送文件流
* 功能描述处理GET请求返回文件流或执行自定义处理
* 输入参数:
* - req: HTTP请求对象
* - res: HTTP响应对象
* 返回值可写流或void
* 异常处理抛出FILE_NOT_FOUND错误
* 时间复杂度O(n)n为文件大小
* 优化建议:可添加流控机制防止内存溢出
*/
async send(
req: http.IncomingMessage,
res: http.ServerResponse
// biome-ignore lint/suspicious/noConfusingVoidType: it's fine
): Promise<stream.Writable | void> {
// 检查是否注册了自定义路径处理
if (this.paths.has(req.url as string)) {
const handler = this.paths.get(req.url as string) as RouteHandler
return handler(req, res)
}
// 检查数据存储是否支持读取操作
if (!('read' in this.store)) {
throw ERRORS.FILE_NOT_FOUND
}
// 从请求中提取文件ID
const id = this.getFileIdFromRequest(req)
if (!id) {
throw ERRORS.FILE_NOT_FOUND
}
// 执行自定义请求处理回调
if (this.options.onIncomingRequest) {
await this.options.onIncomingRequest(req, res, id)
}
// 获取文件上传状态
const stats = await this.store.getUpload(id)
// 验证文件是否完整
if (!stats || stats.offset !== stats.size) {
throw ERRORS.FILE_NOT_FOUND
}
// 处理内容类型和内容处置头
const { contentType, contentDisposition } = this.filterContentType(stats)
// 创建文件读取流
// @ts-expect-error exists if supported
const file_stream = await this.store.read(id)
const headers = {
'Content-Length': stats.offset,
'Content-Type': contentType,
'Content-Disposition': contentDisposition,
}
res.writeHead(200, headers)
// 使用流管道传输数据
return stream.pipeline(file_stream, res, () => {
// 忽略流传输错误
})
}
/**
* 过滤内容类型
* 功能描述根据文件类型生成Content-Type和Content-Disposition头
* 输入参数:
* - stats: 文件上传状态对象
* 返回值包含contentType和contentDisposition的对象
* 时间复杂度O(1)
* 优化建议可添加更多MIME类型验证规则
*/
filterContentType(stats: Upload): {
contentType: string
contentDisposition: string
} {
let contentType: string
let contentDisposition: string
// 从元数据中提取文件类型和名称
const { filetype, filename } = stats.metadata ?? {}
// 验证文件类型格式
if (filetype && this.reMimeType.test(filetype)) {
contentType = filetype
// 检查是否在白名单中
if (this.mimeInlineBrowserWhitelist.has(filetype)) {
contentDisposition = 'inline'
} else {
contentDisposition = 'attachment'
}
} else {
// 使用默认类型并强制下载
contentType = 'application/octet-stream'
contentDisposition = 'attachment'
}
// 添加文件名到内容处置头
if (filename) {
contentDisposition += `; filename=${this.quote(filename)}`
}
return {
contentType,
contentDisposition,
}
}
/**
* 字符串转义
* 功能描述:将字符串转换为带引号的字符串字面量
* 输入参数:
* - value: 需要转义的字符串
* 返回值:转义后的字符串
* 时间复杂度O(n)n为字符串长度
* 优化建议:可考虑使用正则表达式优化替换操作
*/
quote(value: string) {
return `"${value.replace(/"/g, '\\"')}"`
}
}