http-svc
Version:
A HTTP request service for browser and node.js
254 lines (233 loc) • 8.29 kB
text/typescript
import {
IFetchConfig,
IHttpSvcMiddleware,
IMiddlewareHandler,
IHttpSvcContext,
IAssembleDispatcher,
IMiddlewareContextProvider,
IHttpSvcAssembleControl,
IRequestFunction,
BuiltInMiddlewareName
} from 'types/exports'
import { HttpSvcMiddleware } from '@http-svc/middleware'
import { HttpSvcControl } from './_'
import { HttpSvcInitCtx, HttpSvcTimeout, HttpSvcBody, HttpSvcResData, HttpSvcSprayMiddleware, HttpSvcRetry } from '../built-in'
import { createError, isMiddleware } from '../shared'
import { BUILT_IN_MIDDLEWARE, ORDER_PRIFIX } from '../const'
class AssembleDispatcher implements IAssembleDispatcher {
public middlewares: IHttpSvcMiddleware[] = []
public middlewareConfigCtxProvider: IMiddlewareContextProvider
constructor(public control: AssembleControl) {
this.middlewareConfigCtxProvider = this.control.httpSvc.configCtrl.provide()
}
public with(middleware: IHttpSvcMiddleware | IMiddlewareHandler | BuiltInMiddlewareName | string, payload?: any) {
if (!middleware) return this
if (typeof middleware === 'string') {
this.middlewares.push({
name: `${ORDER_PRIFIX}${middleware}`,
handler: HttpSvcSprayMiddleware.handler
})
if (payload) {
this.inject(middleware, payload)
}
} else if (typeof middleware === 'function') {
this.middlewares.push(new HttpSvcSprayMiddleware(middleware as IMiddlewareHandler, payload) as HttpSvcMiddleware)
} else if (isMiddleware(middleware)) {
this.middlewares.push(middleware as IHttpSvcMiddleware)
if (payload) {
this.inject(middleware.name, payload)
}
}
return this
}
/**
* 插入中间件载荷
* @param name 中间件名称
* @param payload 中间件的载荷
* @returns this
*/
public inject(name: BuiltInMiddlewareName | string, payload: any) {
// 包裹成config 再进行inject
this.middlewareConfigCtxProvider.inject(name, { payload })
return this
}
/**
* 禁用中间件
* @param name 中间件名称
* @returns this
*/
public disable(name: BuiltInMiddlewareName | string) {
this.middlewareConfigCtxProvider.disable(name)
return this
}
public request(config: IFetchConfig) {
const fn = this.control.compose(this.middlewares)
return this.control.httpSvc.requestCtrl.request(config, this.middlewareConfigCtxProvider.get(), fn)
}
}
/**
* 装配中间件控制
*/
export class AssembleControl extends HttpSvcControl implements IHttpSvcAssembleControl {
// 中间件
private middlewares: IHttpSvcMiddleware[] = []
// 判断是否重复注册
private alreadyExistsIdx(middlewareName: string) {
return this.middlewares.findIndex((m) => m.name === middlewareName)
}
// 注册中间件
public register(middlewares: IHttpSvcMiddleware[]): void {
if (Array.isArray(middlewares)) {
let reset = false
middlewares.forEach((m) => {
if (isMiddleware(m)) {
reset = true
const idx = this.alreadyExistsIdx(m.name)
if (idx > -1) {
this.middlewares[idx] = m
} else {
this.middlewares.push(m)
}
}
})
if (reset) {
this.httpSvc.requestCtrl.reset()
}
}
}
// 启动装配调度
private setup() {
return new AssembleDispatcher(this)
}
/**
* 禁用注册了的全局中间件
* @param middlewareName 中间件名称
* @returns dispatcher
*/
public disable(middlewareName: BuiltInMiddlewareName | string) {
return this.setup().disable(middlewareName)
}
/**
* 挂载临时中间件
* @param middleware 中间件
* @param payload 中间件的载荷
* @returns dispatcher
*/
public with(middleware: IHttpSvcMiddleware | IMiddlewareHandler | BuiltInMiddlewareName | string, payload: any) {
return this.setup().with(middleware, payload)
}
/**
* 组织中间件,获得组织后的请求方法
* @param middlewares 临时挂载的前中间件
* @returns 请求方法
*/
public compose(middlewares?: IHttpSvcMiddleware[]): IRequestFunction {
// 全局中间件列表拷贝
const list1: IHttpSvcMiddleware[] = [...this.middlewares]
// 临时中间件收集
const list2: IHttpSvcMiddleware[] = []
const builtInOverride: Record<string, IHttpSvcMiddleware | null> = {
[BUILT_IN_MIDDLEWARE.RETRY]: null,
[BUILT_IN_MIDDLEWARE.BODY]: null,
[BUILT_IN_MIDDLEWARE.RES_DATA]: null,
[BUILT_IN_MIDDLEWARE.TIMEOUT]: null
}
const builtInNames = Object.keys(builtInOverride)
const unique: Record<string, IHttpSvcMiddleware | null> = {}
/**
* 顺序逻辑:
* 1. 如果是内置组件,走覆盖逻辑
* 2. 非内置组件,同名中间件,with 覆盖 global;不同名中间件,global顺序靠洋葱外层,with顺序靠洋葱内层
* 3. 如果是调整global 与 with 的调用顺序,可使用with(globalMiddlewareName, payload)
*/
;(middlewares || []).forEach((m) => {
if (isMiddleware(m)) {
let middlewareName = m.name
if (builtInNames.includes(middlewareName)) {
if (!builtInOverride[middlewareName]) {
builtInOverride[middlewareName] = m
}
} else {
// 如果是顺序调整
if (middlewareName.startsWith(ORDER_PRIFIX)) {
middlewareName = middlewareName.substring(ORDER_PRIFIX.length)
// 必须是没设置过的
if (!unique[middlewareName] && !(middlewareName in builtInOverride)) {
const globalMiddlewareIndex = list1.findIndex((m) => m.name === middlewareName)
if (globalMiddlewareIndex > -1) {
// 取对应中间件
const realM = list1[globalMiddlewareIndex]
// 移除原列表
list1.splice(globalMiddlewareIndex, 1)
unique[middlewareName] = realM
list2.push(realM)
}
}
} else {
// 临时函数中间件
if (BUILT_IN_MIDDLEWARE.SPRAY === middlewareName) {
list2.push(m)
} else if (!unique[middlewareName]) {
// 没用过的中间件
unique[middlewareName] = m
list2.push(m)
}
}
}
}
})
const list: IHttpSvcMiddleware[] = []
list1.forEach((m) => {
if (builtInNames.includes(m.name)) {
if (!builtInOverride[m.name]) {
builtInOverride[m.name] = m
}
} else if (!unique[m.name]) {
list.push(m)
}
})
list.push(...list2)
const middlewareList = [
// after 倒序
// 重试
builtInOverride[BUILT_IN_MIDDLEWARE.RETRY] || new HttpSvcRetry(),
// before 正序
new HttpSvcInitCtx(),
// 临时 + 全局
...list,
// 取res data
builtInOverride[BUILT_IN_MIDDLEWARE.RES_DATA] || new HttpSvcResData(),
// 处理body
builtInOverride[BUILT_IN_MIDDLEWARE.BODY] || new HttpSvcBody(),
// 开始超时计时
builtInOverride[BUILT_IN_MIDDLEWARE.TIMEOUT] || new HttpSvcTimeout(),
// fetch
this.httpSvc.fetch as HttpSvcMiddleware
// 有兜底处理
]
return async (context: IHttpSvcContext) => {
let index = -1
const configCtrl = this.httpSvc.configCtrl
const debug = configCtrl.get(context.middleware, 'DEBUG').payload
await dispatch(0)
if (debug) {
return context
}
const disableResExtract = configCtrl.get(context.middleware, BUILT_IN_MIDDLEWARE.RES_EXTRACT).disabled
if (!disableResExtract && context.response?.ok) {
return context.response?.data || null
}
return context.response
async function dispatch(i: number): Promise<any> {
if (i === middlewareList.length) return
if (i <= index) throw createError(context, 'The next() called multiple times')
index = i
const middleware = middlewareList[index]
if (!middleware) {
throw createError(context, 'Middleware is not exist')
}
await middleware.handler(context, dispatch.bind(null, i + 1), configCtrl.get(context.middleware, middleware.name))
}
}
}
}