From e2505597ff1c832f993ebe8c99004ff6af4595a2 Mon Sep 17 00:00:00 2001 From: MiaoWoo Date: Wed, 17 Jun 2020 18:34:20 +0800 Subject: [PATCH] feat: add web package Signed-off-by: MiaoWoo --- packages/web/.npmignore | 1 + packages/web/package.json | 29 ++++ packages/web/src/constants.ts | 2 + packages/web/src/decorators/index.ts | 30 ++++ packages/web/src/index.ts | 8 + packages/web/src/interfaces/context.ts | 19 +++ packages/web/src/interfaces/index.ts | 2 + packages/web/src/interfaces/metadata.ts | 14 ++ packages/web/src/server.ts | 187 ++++++++++++++++++++++++ packages/web/tsconfig.json | 7 + 10 files changed, 299 insertions(+) create mode 120000 packages/web/.npmignore create mode 100644 packages/web/package.json create mode 100644 packages/web/src/constants.ts create mode 100644 packages/web/src/decorators/index.ts create mode 100644 packages/web/src/index.ts create mode 100644 packages/web/src/interfaces/context.ts create mode 100644 packages/web/src/interfaces/index.ts create mode 100644 packages/web/src/interfaces/metadata.ts create mode 100644 packages/web/src/server.ts create mode 100644 packages/web/tsconfig.json diff --git a/packages/web/.npmignore b/packages/web/.npmignore new file mode 120000 index 00000000..b4359f69 --- /dev/null +++ b/packages/web/.npmignore @@ -0,0 +1 @@ +../../.npmignore \ No newline at end of file diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 00000000..3572ae78 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,29 @@ +{ + "name": "@ccms/web", + "version": "0.7.0", + "description": "MiaoScript web package", + "keywords": [ + "miaoscript", + "minecraft", + "bukkit", + "sponge" + ], + "author": "MiaoWoo ", + "homepage": "https://github.com/circlecloud/ms.git", + "license": "ISC", + "main": "dist/index.js", + "scripts": { + "clean": "rimraf dist", + "watch": "tsc --watch", + "build": "yarn clean && tsc", + "test": "echo \"Error: run tests from root\" && exit 1" + }, + "devDependencies": { + "reflect-metadata": "^0.1.13", + "rimraf": "^3.0.2", + "typescript": "^3.9.2" + }, + "dependencies": { + "@ccms/container": "^0.7.0" + } +} diff --git a/packages/web/src/constants.ts b/packages/web/src/constants.ts new file mode 100644 index 00000000..1a7fbe33 --- /dev/null +++ b/packages/web/src/constants.ts @@ -0,0 +1,2 @@ +export const WebProxyBeanName = 'webServerProxy' +export const FilterProxyBeanName = 'webFilterProxy' diff --git a/packages/web/src/decorators/index.ts b/packages/web/src/decorators/index.ts new file mode 100644 index 00000000..6f5b7cc0 --- /dev/null +++ b/packages/web/src/decorators/index.ts @@ -0,0 +1,30 @@ +export const Controller = () => { + return (target: TFunction): ClassDecorator => { + return + } +} +export const Header = () => { + return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): MethodDecorator => { + return + } +} +export const Post = () => { + return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): MethodDecorator => { + return + } +} +export const Get = () => { + return (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): MethodDecorator => { + return + } +} +export const Param = () => { + return (target: Object, propertyKey: string | symbol, parameterIndex: number): ParameterDecorator => { + return + } +} +export const RequestBody = () => { + return (target: Object, propertyKey: string | symbol, parameterIndex: number): ParameterDecorator => { + return + } +} diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts new file mode 100644 index 00000000..7a0db934 --- /dev/null +++ b/packages/web/src/index.ts @@ -0,0 +1,8 @@ +/// +/// +/// +/// + +export * from './server' +export * from './decorators' +export * from './interfaces' diff --git a/packages/web/src/interfaces/context.ts b/packages/web/src/interfaces/context.ts new file mode 100644 index 00000000..674bfb0f --- /dev/null +++ b/packages/web/src/interfaces/context.ts @@ -0,0 +1,19 @@ +export type RequestHandler = (ctx: Context) => any +export interface InterceptorAdapter { + name: string + preHandle?(ctx: Context): void + postHandle?(ctx: Context): void +} + +export type RequestHeader = { [key: string]: string | string[] } +export type RequestParams = { [key: string]: string | string[] } + +export interface Context { + request?: javax.servlet.http.HttpServletRequest + response?: javax.servlet.http.HttpServletResponse + header?: RequestHeader + url?: string + params?: RequestParams + body?: any + result?: any +} diff --git a/packages/web/src/interfaces/index.ts b/packages/web/src/interfaces/index.ts new file mode 100644 index 00000000..7f959aaf --- /dev/null +++ b/packages/web/src/interfaces/index.ts @@ -0,0 +1,2 @@ +export * from './context' +export * from './metadata' diff --git a/packages/web/src/interfaces/metadata.ts b/packages/web/src/interfaces/metadata.ts new file mode 100644 index 00000000..52339d48 --- /dev/null +++ b/packages/web/src/interfaces/metadata.ts @@ -0,0 +1,14 @@ +export interface BaseMetadata { + /** + * 名称 为空则为对象名称 + */ + name?: string + /** + * 支持的服务器列表 为空则代表所有 + */ + servers?: string[] +} + +export interface ControllerMetadata extends BaseMetadata { + +} diff --git a/packages/web/src/server.ts b/packages/web/src/server.ts new file mode 100644 index 00000000..ade18dfe --- /dev/null +++ b/packages/web/src/server.ts @@ -0,0 +1,187 @@ +import * as querystring from 'querystring' + +import { web } from '@ccms/api' +import { provideSingleton, JSClass, postConstruct } from '@ccms/container' + +import { WebProxyBeanName, FilterProxyBeanName } from './constants' +import { Context, InterceptorAdapter, RequestHandler } from './interfaces' + +@provideSingleton(web.Server) +export class Server { + @JSClass('pw.yumc.MiaoScript.web.WebServerProxy') + private WebServerProxy: any + @JSClass('pw.yumc.MiaoScript.web.WebFilterProxy') + private WebFilterProxy: any + + private StreamUtils = org.springframework.util.StreamUtils + private ResponseEntity = org.springframework.http.ResponseEntity + + private interceptors: Map + private handlerMapping: Map + + private beanFactory: org.springframework.beans.factory.support.DefaultListableBeanFactory + + @postConstruct() + initialization() { + this.beanFactory = base.getInstance().getAutowireCapableBeanFactory() + this.interceptors = new Map() + this.handlerMapping = new Map() + this.start() + } + + start() { + this.registryFilterProxy() + this.registryWebProxy() + } + + stop() { + try { + this.beanFactory.destroySingleton(FilterProxyBeanName) + this.beanFactory.destroySingleton(WebProxyBeanName) + } catch (error) { + console.ex(error) + } + } + + registryMapping(path: string, handler: RequestHandler) { + console.debug(`Registry Mapping ${path} to handle ${handler.name || ''} function.`) + this.handlerMapping.set(path, handler) + } + + unregistryMapping(path: string) { + this.handlerMapping.delete(path) + } + + registryInterceptor(interceptor: InterceptorAdapter) { + console.debug(`Registry ${interceptor.name} Interceptor.`) + this.interceptors.set(interceptor.name, interceptor) + } + + unregistryInterceptor(interceptor: InterceptorAdapter) { + this.interceptors.delete(interceptor.name) + } + + private registryFilterProxy() { + try { this.beanFactory.destroySingleton(FilterProxyBeanName) } catch (ex) { } + var WebFilterProxyNashorn = Java.extend(this.WebFilterProxy, { + doFilter: (servletRequest: javax.servlet.http.HttpServletRequest, servletResponse: javax.servlet.http.HttpServletResponse, filterChain: javax.servlet.FilterChain) => { + console.log('WebFilterProxyNashorn', 'doFilter', servletRequest, servletResponse) + filterChain.doFilter(servletRequest, servletResponse) + } + }) + this.beanFactory.registerSingleton(FilterProxyBeanName, new WebFilterProxyNashorn()) + } + + private registryWebProxy() { + try { this.beanFactory.destroySingleton(WebProxyBeanName) } catch (ex) { } + var WebServerProxyNashorn = Java.extend(this.WebServerProxy, { + process: (req: javax.servlet.http.HttpServletRequest, resp: javax.servlet.http.HttpServletResponse) => { + let ctx: Context = { request: req, response: resp } + ctx.url = req.getRequestURI() + // @ts-ignore + ctx.header = { __noSuchProperty__: (name: string) => req.getHeader(name) + '' } + if (req.getQueryString()) { + ctx.url += `?${req.getQueryString()}` + ctx.params = querystring.parse(req.getQueryString()) + } + if (req.getMethod() == "POST") { + ctx.body = this.StreamUtils.copyToString(req.getInputStream(), java.nio.charset.StandardCharsets.UTF_8) + if ((ctx.header['Content-Type'] || '').includes('application/json')) { + try { + ctx.body = JSON.parse(ctx.body) + } catch (error) { + return { + status: 500, + msg: `parse json body error: ${error}`, + path: ctx.url, + error: console.stack(error, false), + timestamp: Date.now() + } + } + } + } + let result = this.process(ctx) + result?.status && resp.setStatus(result.status) + return result + } + }) + this.beanFactory.registerSingleton(WebProxyBeanName, new WebServerProxyNashorn()) + } + + + private process(ctx: Context) { + let startTime = Date.now() + for (const [_, interceptor] of this.interceptors) { + if (interceptor.preHandle) { + try { + let startTime = Date.now() + ctx.result = interceptor.preHandle(ctx) + let preHandleTime = Date.now() - startTime + if (preHandleTime > 20) { + console.debug(`[WARN] Interceptor ${interceptor.name} preHandle cost time ${preHandleTime}ms!`) + } + if (ctx.result) { return ctx.result } + } catch (error) { + console.ex(error) + return { + status: 500, + msg: `Interceptor ${interceptor.name} preHandle error: ${error}`, + path: ctx.url, + error: console.stack(error, false), + timestamp: Date.now() + } + } + } + } + ctx.result = this.execRequestHandle(ctx) + for (const [_, interceptor] of this.interceptors) { + if (interceptor.postHandle) { + try { + let startTime = Date.now() + ctx.result = interceptor.postHandle(ctx) + let preHandleTime = Date.now() - startTime + if (preHandleTime > 20) { + console.debug(`[WARN] Interceptor ${interceptor.name} preHandle cost time ${preHandleTime}ms!`) + } + } catch (error) { + return { + status: 500, + msg: `Interceptor ${interceptor.name} postHandle error: ${error}`, + path: ctx.url, + error: console.stack(error, false), + timestamp: Date.now() + } + } + } + } + console.debug(` +===================== MiaoSpring ===================== +Request URL : ${ctx.url} +Response Body : ${JSON.stringify(Java.asJSONCompatible(ctx.result))} +Handle Time : ${Date.now() - startTime}ms +======================================================`) + return ctx.result + } + + private execRequestHandle(ctx: Context) { + if (!this.handlerMapping.has(ctx.request.getRequestURI())) { + return { + status: 404, + msg: "handlerMapping Not Found!", + path: ctx.url, + timestamp: Date.now() + } + } + try { + return this.handlerMapping.get(ctx.request.getRequestURI())(ctx) + } catch (error) { + return { + status: 500, + msg: '' + error, + path: ctx.url, + error: console.stack(error, false), + timestamp: Date.now() + } + } + } +} diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json new file mode 100644 index 00000000..7aae5d2b --- /dev/null +++ b/packages/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "src", + "outDir": "dist" + } +} \ No newline at end of file