import { generateUuidV4 } from "#Source/identifier/uuid.ts" import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/logger.ts" import { Logger } from "#Source/log/logger.ts" import { toQueryObject } from "#Source/route/index.ts" import type { Standard } from "#Source/validation/index.ts" import type { AnyApiHost, ApiCoreHandler, ApiHostStartResult } from "./api-host.ts" import type { AnyApi } from "./api.ts" export interface ApiServerOptions extends LoggerFriendlyOptions { apiList: AnyApi[] apiHost: AnyApiHost } /** * @description {@link ApiServer} 包含若干个 {@link Api}(接口),并且需要指定一个监听端口(Port)。Server 启动 * 后会监听对应的端口,将端口接收到的请求分发给不同的接口。 * 请求 -> Server -> Api * 具体来说,当服务器收到请求时: * - 通过请求的路径(path)和方法(method)与所有的 {@link Api} 进行匹配。 * - 如果能匹配到,将请求分给第一个匹配到的接口。 * - 如果匹配不到任何接口,将返回 404。 * - 按照 {@link Api} 的处理逻辑进行处理,最终将结果返回给请求方。 */ export class ApiServer implements LoggerFriendly { readonly options: ApiServerOptions readonly logger: Logger protected readonly apiCoreHandler: ApiCoreHandler protected isStarted: boolean protected managesApiHostLifecycle: boolean constructor(options: ApiServerOptions) { this.options = options this.logger = Logger.fromOptions(options).setDefaultName("ApiServer") this.apiCoreHandler = async (apiCore) => { await this.handleApiCore(apiCore) } this.isStarted = false this.managesApiHostLifecycle = false } attach(): ApiHostStartResult { if (this.isStarted === true) { throw new Error("ApiServer has already attached.") } const apiHost = this.options.apiHost const result = apiHost.getStartResultOrThrow() apiHost.attachApiCoreHandler(this.apiCoreHandler) this.isStarted = true return result } async open(): Promise { if (this.isStarted === true) { throw new Error("ApiServer has already opened.") } const apiHost = this.options.apiHost if (apiHost.isRunning() === false) { await apiHost.start() this.managesApiHostLifecycle = true } else { this.managesApiHostLifecycle = false } try { return this.attach() } catch (exception) { if (this.managesApiHostLifecycle === true) { this.managesApiHostLifecycle = false await apiHost.close() } throw exception } } async reopen(): Promise { await this.close() return await this.open() } async close(): Promise { const apiHost = this.options.apiHost if (this.isStarted === true) { apiHost.detachApiCoreHandler() this.isStarted = false } if (this.managesApiHostLifecycle === true) { this.managesApiHostLifecycle = false await apiHost.close() } } protected async handleApiCore(apiCore: Parameters[0]): Promise { const { apiList } = this.options const path = await apiCore.getRequestPath() const method = (await apiCore.getRequestMethod()).toUpperCase() // tag every request with a unique identifier for better logging const logger = this.logger.derive().setDefaultName(`${method} ${path} ${generateUuidV4()}`) try { logger.log(`start handling request`) logger.log(`start finding target api`) // 根据请求的路径和方法找到对应的接口 const targetApi = apiList.find((api) => { const apiSchema = api.getApiSchema() const apiSchemaPath = apiSchema.getPath() const apiSchemaMethod = (apiSchema.getMethod() as string).toUpperCase() return apiSchemaPath === path && apiSchemaMethod === method }) // 如果找不到对应的接口,抛出错误 if (targetApi === undefined) { logger.error(`finding target api failed`) throw new Error(`finding target api failed`) } else { logger.log(`finding target api success`) } // 如果找到对应的接口,获取接口定义,按照接口的定义进行处理 const apiSchema = targetApi.getApiSchema() // 1. 准备 ApiHandlerContext logger.log(`start preparing api handler context`) const inputSearch = await apiCore.getRequestSearch() const inputQueryObject = toQueryObject(inputSearch) const inputQuerySchema = apiSchema.getInputQuerySchema() as Standard.Schema const validateResultQuery = await inputQuerySchema["~standard"].validate(inputQueryObject) if (validateResultQuery.issues !== undefined) { logger.error( `validating api input query failed, issues: ${JSON.stringify(validateResultQuery.issues)}`, ) throw new Error( `validating api input query failed, issues: ${JSON.stringify(validateResultQuery.issues)}`, ) } const inputQuery = validateResultQuery.value const inputBodySchema = apiSchema.getInputBodySchema() as Standard.Schema const inputBodyJson = await apiCore.getRequestBodyJson() const validateResult = await inputBodySchema["~standard"].validate(inputBodyJson) if (validateResult.issues !== undefined) { logger.error( `validating api input body failed, issues: ${JSON.stringify(validateResult.issues)}`, ) throw new Error( `validating api input body failed, issues: ${JSON.stringify(validateResult.issues)}`, ) } const inputBody = validateResult.value logger.log(`preparing api handler context success`) // 2. 执行 ApiHandler logger.log(`start executing api handler`) const apiHandler = targetApi.getApiHandler() const apiResult = await apiHandler({ ...apiSchema.getApiSchemaOptions(), inputQuery, inputBody, apiCore, logger, }) logger.log(`executing api handler success`) // 3. 处理 ApiHandler 的执行结果,这一步会将数据返回给请求方 logger.log(`start handling api result`) await apiResult.handle(apiCore) logger.log(`handling api result success`) logger.log(`handling request success`) } catch (exception) { logger.error(`handling request failed, exception: ${String(exception)}`) await apiCore.error() } } } export const createApiServer = (options: ApiServerOptions): ApiServer => { return new ApiServer(options) }