-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathserver.ts
197 lines (171 loc) · 7.62 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import * as Koa from "koa";
import * as Router from "koa-router";
import * as inversify from "inversify";
import { interfaces } from "./interfaces";
import { TYPE, METADATA_KEY, DEFAULT_ROUTING_ROOT_PATH, PARAMETER_TYPE } from "./constants";
/**
* Wrapper for the koa server.
*/
export class InversifyKoaServer {
private _router: Router;
private _container: inversify.interfaces.Container;
private _app: Koa;
private _configFn: interfaces.ConfigFunction;
private _errorConfigFn: interfaces.ConfigFunction;
private _routingConfig: interfaces.RoutingConfig;
/**
* Wrapper for the koa server.
*
* @param container Container loaded with all controllers and their dependencies.
*/
constructor(
container: inversify.interfaces.Container,
customRouter?: Router,
routingConfig?: interfaces.RoutingConfig,
customApp?: Koa
) {
this._container = container;
this._router = customRouter || new Router();
this._routingConfig = routingConfig || {
rootPath: DEFAULT_ROUTING_ROOT_PATH
};
this._app = customApp || new Koa();
}
/**
* Sets the configuration function to be applied to the application.
* Note that the config function is not actually executed until a call to InversifyKoaServer.build().
*
* This method is chainable.
*
* @param fn Function in which app-level middleware can be registered.
*/
public setConfig(fn: interfaces.ConfigFunction): InversifyKoaServer {
this._configFn = fn;
return this;
}
/**
* Sets the error handler configuration function to be applied to the application.
* Note that the error config function is not actually executed until a call to InversifyKoaServer.build().
*
* This method is chainable.
*
* @param fn Function in which app-level error handlers can be registered.
*/
public setErrorConfig(fn: interfaces.ConfigFunction): InversifyKoaServer {
this._errorConfigFn = fn;
return this;
}
/**
* Applies all routes and configuration to the server, returning the Koa application.
*/
public build(): Koa {
// register server-level middleware before anything else
if (this._configFn) {
this._configFn.apply(undefined, [this._app]);
}
this.registerControllers();
// register error handlers after controllers
if (this._errorConfigFn) {
this._errorConfigFn.apply(undefined, [this._app]);
}
return this._app;
}
private registerControllers() {
// set prefix route in config rootpath
if (this._routingConfig.rootPath !== DEFAULT_ROUTING_ROOT_PATH) {
this._router.prefix(this._routingConfig.rootPath);
}
let controllers: interfaces.Controller[] = this._container.getAll<interfaces.Controller>(TYPE.Controller);
controllers.forEach((controller: interfaces.Controller) => {
let controllerMetadata: interfaces.ControllerMetadata = Reflect.getOwnMetadata(
METADATA_KEY.controller,
controller.constructor
);
let methodMetadata: interfaces.ControllerMethodMetadata[] = Reflect.getOwnMetadata(
METADATA_KEY.controllerMethod,
controller.constructor
);
let parameterMetadata: interfaces.ControllerParameterMetadata = Reflect.getOwnMetadata(
METADATA_KEY.controllerParameter,
controller.constructor
);
if (controllerMetadata && methodMetadata) {
let controllerMiddleware = this.resolveMidleware(...controllerMetadata.middleware);
methodMetadata.forEach((metadata: interfaces.ControllerMethodMetadata) => {
let paramList: interfaces.ParameterMetadata[] = [];
if (parameterMetadata) {
paramList = parameterMetadata[metadata.key] || [];
}
let handler = this.handlerFactory(controllerMetadata.target.name, metadata.key, paramList);
let routeMiddleware = this.resolveMidleware(...metadata.middleware);
this._router[metadata.method](
`${controllerMetadata.path}${metadata.path}`,
...controllerMiddleware,
...routeMiddleware,
handler
);
});
}
});
this._app.use(this._router.routes());
}
private resolveMidleware(...middleware: interfaces.Middleware[]): interfaces.KoaRequestHandler[] {
return middleware.map(middlewareItem => {
try {
return this._container.get<interfaces.KoaRequestHandler>(middlewareItem);
} catch (_) {
return middlewareItem as interfaces.KoaRequestHandler;
}
});
}
private handlerFactory(controllerName: any, key: string,
parameterMetadata: interfaces.ParameterMetadata[]): interfaces.KoaRequestHandler {
// this function works like another top middleware to extract and inject arguments
return async (ctx: Router.IRouterContext, next: () => Promise<any>) => {
let args = this.extractParameters(ctx, next, parameterMetadata);
let result: any = await this._container.getNamed(TYPE.Controller, controllerName)[key](...args);
if (result && result instanceof Promise) {
// koa handle promises
return result;
} else if (result && !ctx.headerSent) {
ctx.body = result;
}
};
}
private extractParameters(ctx: Router.IRouterContext, next: () => Promise<any>,
params: interfaces.ParameterMetadata[]): any[] {
let args = [];
if (!params || !params.length) {
return [ctx, next];
}
for (let item of params) {
switch (item.type) {
default: args[item.index] = ctx; break; // response
case PARAMETER_TYPE.RESPONSE: args[item.index] = this.getParam(ctx.response, null, item.parameterName); break;
case PARAMETER_TYPE.REQUEST: args[item.index] = this.getParam(ctx.request, null, item.parameterName); break;
case PARAMETER_TYPE.NEXT: args[item.index] = next; break;
case PARAMETER_TYPE.CTX: args[item.index] = this.getParam(ctx, null, item.parameterName); break;
case PARAMETER_TYPE.PARAMS: args[item.index] = this.getParam(ctx, "params", item.parameterName); break;
case PARAMETER_TYPE.QUERY: args[item.index] = this.getParam(ctx, "query", item.parameterName); break;
case PARAMETER_TYPE.BODY: args[item.index] = this.getParam(ctx.request, "body", item.parameterName); break;
case PARAMETER_TYPE.HEADERS: args[item.index] = this.getParam(ctx.request, "headers", item.parameterName); break;
case PARAMETER_TYPE.COOKIES: args[item.index] = this.getParam(ctx, "cookies", item.parameterName); break;
}
}
args.push(ctx, next);
return args;
}
private getParam(source: any, paramType: string, name: string) {
let param = source[paramType] || source;
return param[name] || this.checkQueryParam(paramType, param, name);
}
private checkQueryParam(paramType: string, param: any, name: string) {
if (paramType === "query") {
return undefined;
} if (paramType === "cookies") {
return param.get(name);
} else {
return param;
}
}
}