import { IRoute, RouteFilter, RouteItem } from './interface' import { queryparams, pathname_fixer, fixFunctionName, safeJsonParse } from '../utils/misc' import { APIContext, F2EConfigResult } from '../interface' import { createResponseHelper } from '../utils/resp' import { logger } from '../utils' import { Transform } from 'node:stream' export class Route implements IRoute { routes: RouteItem[] = [] route_map = new Map() options: F2EConfigResult respUtils: ReturnType filter?: RouteFilter constructor (options: F2EConfigResult, filter?: RouteFilter) { this.options = options this.respUtils = createResponseHelper(options) this.filter = filter } private find (path: string, method = '*') { return this.routes.find(r => { if (r.method?.toUpperCase() === method.toUpperCase() || r.method === '*') { return typeof r.path === 'string' ? r.path === path : r.path.test(path) } else { return false } }) } on = (path: string | RegExp, handler: RouteItem['handler'], ext?: Omit) => { this.routes.push({ path: typeof path === 'string' ? pathname_fixer(path) : path, handler, method: '*', ...(ext || {}), }) } match = (path: string, method = '*') => { let item = this.route_map.get(path + ':' + method) || this.route_map.get(path + ':*') if (item) { return item } item = this.find(path, method) if (item) { if (typeof item.path === 'string') { this.route_map.set(path + ':' + item.method, item) } return item } return undefined }; execute = async (pathname: string, ctx: APIContext) => { const { handleError, handleSuccess, handleNotFound, handleSSE } = this.respUtils const { req, resp, body, headers = {} } = ctx if (this.filter) { const filterResult = await this.filter(pathname, ctx) if (false === filterResult) { return false } if (typeof filterResult === 'string') { pathname = filterResult } } let data: any = null if (body && body.length > 0) { const contentTypeHeader = headers['content-type'] const contentType = (typeof contentTypeHeader === 'string' ? contentTypeHeader : Array.isArray(contentTypeHeader) ? contentTypeHeader[0] : '').toLowerCase() try { if (contentType.includes('application/json')) { // JSON 数据解析 data = safeJsonParse(body.toString()) } else if (contentType.includes('application/x-www-form-urlencoded')) { // URL 编码的表单数据 data = queryparams(body.toString()) } else if (contentType.includes('text/') || contentType.includes('application/xml') || contentType.includes('application/xhtml')) { // 文本类型数据,转换为字符串 data = body.toString() } else { // 其他类型(如 application/octet-stream、二进制文件等),保留原始 Buffer data = body } } catch (e) { logger.error('onRoute Error:', pathname, e) // 解析失败时,保留原始 Buffer data = body } } const item = this.match(pathname, ctx.method) if (item) { try { switch (item.type || 'json') { case 'none': await item.handler(data, ctx) break; case 'json': handleSuccess(ctx, '.json', JSON.stringify(await item.handler(data, ctx))) break case 'jsonp': const callback = req.getQuery('callback') || 'callback' handleSuccess(ctx, '.js', `${fixFunctionName(callback)}(${JSON.stringify(await item.handler(data, ctx))})`) break case 'sse': handleSSE(ctx, item, data) break; default: const result = await item.handler(data, ctx) if (typeof result === 'undefined') { handleNotFound(resp, pathname) } else { handleSuccess(ctx, item.sourceType || 'txt', result) } break; } } catch (e) { logger.error('onRoute Error:', pathname, e) handleError(resp, e + '') } return false } }; }