UNPKG

@kenote/api-proxy

Version:
133 lines (129 loc) 4.88 kB
import { resolve } from 'path' import createError from 'http-errors' import jsYaml from 'js-yaml' import ruleJudgment from 'rule-judgment' import { filterData } from 'parse-string' import { isJson, isYaml, loadConfig } from '@kenote/config' import type { TcpSocketConnectOpts } from 'net' import type { TCPSocket } from '@kenote/protobuf' import { APIProxy, NodeProxy } from '../types' import { compact, concat, merge, uniq, isPlainObject, isString, isError, isBuffer } from 'lodash' import { parsePlainObject, runService, getHeader, getServiceModules } from './utils' import { parseProps } from './api-proxy' import { shellAsCurl } from './http' import { socketRequest } from './socket' /** * 获取入口信息 * @param options * @returns */ export function getNodeEntrance (options: NodeProxy.EntranceOptions) { return async (ctx: NodeProxy.Request, pathname: string) => { let { channel, pathLabel, sandbox } = options if (!ctx?.channel || !ctx?.pathLabel) { throw createError(500, '缺少频道和路径', { code: 1000 }) } let channelPath = resolve(process.cwd(), pathname, channel) let allEntrance = loadConfig<NodeProxy.Entrance[]>([ pathname, channel, 'wss'].join('/'), { type: 'array' }) let entrance = allEntrance?.find( v => v.name === pathLabel ) if (!entrance) { throw createError(500, '当前访问的接口不存在', { code: 1000 }) } let setting = loadConfig<APIProxy.ChannelSetting>([ pathname, channel, 'setting' ].join('/'), { mode: 'merge' }) let serviceModules = await getServiceModules({ cwd: resolve(channelPath, 'js'), sandbox, alias: setting?.jsAlias }) // 处理IP白名单 let whitelist = uniq(compact(concat(setting?.whitelist, entrance?.whitelist))) if (whitelist.length > 0 && !whitelist.find( v => new RegExp(v).test(ctx.clientIP!) )) { throw createError(500, '没有访问该页面的权限 [whitelist]', { code: 1000 }) } // 鉴权用户 if (entrance.filterAuth) { let isAuth = ruleJudgment(entrance.filterAuth)(ctx.auth) if (!isAuth) { throw createError(500, '没有访问该页面的权限 [Unauthorized]', { code: 1000 }) } } let body = ctx?.body ?? {} let payload = entrance.payload ? filterData(entrance.payload, serviceModules)(body) : body payload = parseProps(entrance.props)(payload) serviceModules.payload = payload return { serviceModules, payload, entrance, setting } } } /** * 获取代理返回数据 * @param ctx * @returns */ export function getNodeResponse (entrance: NodeProxy.Entrance | undefined, payload: any) { return async (options: NodeProxy.ProxyOptions) => { let { serviceModules, logger, setting } = options let result: any = null let type: string | undefined = 'application/octet-stream' if (entrance?.service) { let { name, args } = entrance.service result = await runService(name, args)(serviceModules) } else if (entrance?.httpProxy) { let httpProxy = entrance.httpProxy if (httpProxy.method.toUpperCase() === 'GET') { httpProxy.params = merge(httpProxy.params, payload) } else { httpProxy.body = merge(httpProxy.body, payload) } let ret = await shellAsCurl(httpProxy) let [ , code ] = ret.status?.split(/\s+/) ?? [] if (code != '200') { throw createError(500, ['HttpProxy:', ret.status?.replace('404 OK', '404 Not Found')! ].join(''), { code: 1000 }) } result = ret.body type = getHeader('content-type')(ret.headers ?? []) } else if (entrance?.socketProxy) { let { msgtype, requestType, serverTag } = entrance.socketProxy let tag: string | undefined let tcpSocket: TCPSocket.Configure = { port: 8080 } let server: Array<TcpSocketConnectOpts & { key: string }> = [] if (isPlainObject(serverTag)) { tcpSocket = serverTag as TCPSocket.Configure } else if (isString(serverTag)) { tag = serverTag } if (setting) { tcpSocket = merge(tcpSocket, setting?.tcpSocket) } tcpSocket.logger = logger result = await socketRequest(msgtype, payload!, requestType)({ tcpSocket, server, tag }) } result = parsePlainObject(result, entrance?.parse)(<Record<string, Function>>serviceModules) return [ type, result ] } } /** * 转换返回结果 * @param path * @param data * @returns */ export function toResponseResult (path: string, data: any, type?: string) { let result: NodeProxy.Response = { timestamp: Date.now(), path } if (isError(data)) { result.error = data.message } else if (isBuffer(data)) { result.data = isJson(data.toString()) || isYaml(data.toString()) ? jsYaml.load(data) : data.toString() } else { result.data = data } return JSON.stringify(result) }