collect-system/packages/tus/src/handlers/PostHandler.ts

257 lines
8.7 KiB
TypeScript
Raw Normal View History

2025-01-06 08:45:23 +08:00
import debug from 'debug'
import { BaseHandler } from './BaseHandler'
import { validateHeader } from '../validators/HeaderValidator'
import type http from 'node:http'
import type { ServerOptions, WithRequired } from '../types'
import { DataStore, Uid, CancellationContext, ERRORS, Metadata, Upload, EVENTS } from '../utils'
const log = debug('tus-node-server:handlers:post')
/**
* PostHandler HTTP POST DataStore
* BaseHandler
*/
export class PostHandler extends BaseHandler {
// 重写 BaseHandler 中的 options 类型,确保在构造函数中设置了 namingFunction
declare options: WithRequired<ServerOptions, 'namingFunction'>
/**
* PostHandler
* @param store - DataStore
* @param options - namingFunction
* @throws namingFunction
*/
constructor(store: DataStore, options: ServerOptions) {
if (options.namingFunction && typeof options.namingFunction !== 'function') {
throw new Error("'namingFunction' must be a function")
}
if (!options.namingFunction) {
options.namingFunction = Uid.rand
}
super(store, options)
}
/**
* DataStore
* @param req - HTTP
* @param res - HTTP
* @param context -
* @returns HTTP
* @throws 'upload-concat' DataStore 'concatentation'
* @throws 'upload-length' 'upload-defer-length'
* @throws 'upload-metadata'
* @throws
*/
async send(
req: http.IncomingMessage,
res: http.ServerResponse,
context: CancellationContext
) {
if ('upload-concat' in req.headers && !this.store.hasExtension('concatentation')) {
throw ERRORS.UNSUPPORTED_CONCATENATION_EXTENSION
}
const upload_length = req.headers['upload-length'] as string | undefined
const upload_defer_length = req.headers['upload-defer-length'] as string | undefined
const upload_metadata = req.headers['upload-metadata'] as string | undefined
if (
upload_defer_length !== undefined && // 如果扩展不支持,则抛出错误
!this.store.hasExtension('creation-defer-length')
) {
throw ERRORS.UNSUPPORTED_CREATION_DEFER_LENGTH_EXTENSION
}
if ((upload_length === undefined) === (upload_defer_length === undefined)) {
throw ERRORS.INVALID_LENGTH
}
2025-01-06 18:30:16 +08:00
2025-01-06 08:45:23 +08:00
let metadata: ReturnType<(typeof Metadata)['parse']> | undefined
if ('upload-metadata' in req.headers) {
try {
metadata = Metadata.parse(upload_metadata)
} catch {
throw ERRORS.INVALID_METADATA
}
}
let id: string
try {
id = await this.options.namingFunction(req, metadata)
} catch (error) {
log('create: check your `namingFunction`. Error', error)
throw error
}
const maxFileSize = await this.getConfiguredMaxSize(req, id)
if (
upload_length &&
maxFileSize > 0 &&
Number.parseInt(upload_length, 10) > maxFileSize
) {
throw ERRORS.ERR_MAX_SIZE_EXCEEDED
}
if (this.options.onIncomingRequest) {
await this.options.onIncomingRequest(req, res, id)
}
const upload = new Upload({
id,
size: upload_length ? Number.parseInt(upload_length, 10) : undefined,
offset: 0,
metadata,
})
if (this.options.onUploadCreate) {
try {
const resOrObject = await this.options.onUploadCreate(req, res, upload)
// 向后兼容,将在下一个主要版本中移除
// 由于在测试中模拟了实例,因此无法使用 `instanceof` 进行检查
if (
typeof (resOrObject as http.ServerResponse).write === 'function' &&
typeof (resOrObject as http.ServerResponse).writeHead === 'function'
) {
res = resOrObject as http.ServerResponse
} else {
// 由于 TS 只理解 instanceof因此类型定义较为丑陋
type ExcludeServerResponse<T> = T extends http.ServerResponse ? never : T
const obj = resOrObject as ExcludeServerResponse<typeof resOrObject>
res = obj.res
if (obj.metadata) {
upload.metadata = obj.metadata
}
}
} catch (error: any) {
log(`onUploadCreate error: ${error.body}`)
throw error
}
}
const lock = await this.acquireLock(req, id, context)
let isFinal: boolean
let url: string
// 推荐的响应默认值
const responseData = {
status: 201,
headers: {} as Record<string, string | number>,
body: '',
}
try {
await this.store.create(upload)
url = this.generateUrl(req, upload.id)
this.emit(EVENTS.POST_CREATE, req, res, upload, url)
isFinal = upload.size === 0 && !upload.sizeIsDeferred
// 如果请求中包含 Content-Type 头,并且使用了 creation-with-upload 扩展
if (validateHeader('content-type', req.headers['content-type'])) {
const bodyMaxSize = await this.calculateMaxBodySize(req, upload, maxFileSize)
const newOffset = await this.writeToStore(req, upload, bodyMaxSize, context)
responseData.headers['Upload-Offset'] = newOffset.toString()
isFinal = newOffset === Number.parseInt(upload_length as string, 10)
upload.offset = newOffset
}
} catch (e) {
context.abort()
throw e
} finally {
await lock.unlock()
}
2025-01-06 18:30:16 +08:00
// 上传完成后的处理逻辑
2025-01-06 08:45:23 +08:00
if (isFinal && this.options.onUploadFinish) {
try {
2025-01-06 18:30:16 +08:00
// 调用自定义的上传完成回调函数,传入请求、响应和上传对象
// 允许用户自定义上传完成后的处理逻辑
2025-01-06 08:45:23 +08:00
const resOrObject = await this.options.onUploadFinish(req, res, upload)
2025-01-06 18:30:16 +08:00
// 兼容性处理:检查返回值是否为 HTTP 响应对象
// 通过检查对象是否具有 write 和 writeHead 方法来判断
2025-01-06 08:45:23 +08:00
if (
typeof (resOrObject as http.ServerResponse).write === 'function' &&
typeof (resOrObject as http.ServerResponse).writeHead === 'function'
) {
2025-01-06 18:30:16 +08:00
// 如果直接返回 HTTP 响应对象,直接覆盖原响应对象
2025-01-06 08:45:23 +08:00
res = resOrObject as http.ServerResponse
} else {
2025-01-06 18:30:16 +08:00
// 处理自定义返回对象的情况
// 使用复杂的类型定义排除 ServerResponse 类型
2025-01-06 08:45:23 +08:00
type ExcludeServerResponse<T> = T extends http.ServerResponse ? never : T
2025-01-06 18:30:16 +08:00
// 将返回对象转换为非 ServerResponse 类型
2025-01-06 08:45:23 +08:00
const obj = resOrObject as ExcludeServerResponse<typeof resOrObject>
2025-01-06 18:30:16 +08:00
// 更新响应对象
2025-01-06 08:45:23 +08:00
res = obj.res
2025-01-06 18:30:16 +08:00
// 根据返回对象更新响应状态码
2025-01-06 08:45:23 +08:00
if (obj.status_code) responseData.status = obj.status_code
2025-01-06 18:30:16 +08:00
// 更新响应体
2025-01-06 08:45:23 +08:00
if (obj.body) responseData.body = obj.body
2025-01-06 18:30:16 +08:00
// 合并响应头,允许覆盖默认头
2025-01-06 08:45:23 +08:00
if (obj.headers)
responseData.headers = Object.assign(obj.headers, responseData.headers)
}
} catch (error: any) {
2025-01-06 18:30:16 +08:00
// 记录上传完成回调中的错误
2025-01-06 08:45:23 +08:00
log(`onUploadFinish: ${error.body}`)
2025-01-06 18:30:16 +08:00
// 抛出错误,中断上传流程
2025-01-06 08:45:23 +08:00
throw error
}
}
2025-01-06 18:30:16 +08:00
2025-01-06 08:45:23 +08:00
// Upload-Expires 响应头指示未完成的上传何时过期。
// 如果在创建时已知过期时间,则必须在响应中包含 Upload-Expires 头
if (
this.store.hasExtension('expiration') &&
this.store.getExpiration() > 0 &&
upload.creation_date
) {
const created = await this.store.getUpload(upload.id)
if (created.offset !== Number.parseInt(upload_length as string, 10)) {
const creation = new Date(upload.creation_date)
// 值必须为 RFC 7231 日期时间格式
responseData.headers['Upload-Expires'] = new Date(
creation.getTime() + this.store.getExpiration()
).toUTCString()
}
}
// 仅在最终的 HTTP 状态码为 201 或 3xx 时附加 Location 头
if (
responseData.status === 201 ||
(responseData.status >= 300 && responseData.status < 400)
) {
responseData.headers.Location = url
}
const writtenRes = this.write(
res,
responseData.status,
responseData.headers,
responseData.body
)
if (isFinal) {
this.emit(EVENTS.POST_FINISH, req, writtenRes, upload)
}
return writtenRes
}
}